Skip to content

Ferradermis/NetworkTablesTeensy4.1

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NT4 Client for Teensy 4.1

A lightweight NetworkTables 4 client for the Teensy 4.1, built on QNEthernet. Designed for FRC applications where NT4 connectivity is an enhancement — not a dependency — and the main loop must never be blocked by network activity.

Hardware Requirements

  • Teensy 4.1 with onboard Ethernet populated (RJ45 magnetics + PHY)
  • Standard Ethernet cable

Dependencies

Library Purpose
QNEthernet lwIP-based TCP stack for Teensy 4.1 built-in PHY
ArduinoJson v7 NT4 JSON control message parsing

Both are declared in platformio.ini and will be fetched automatically by PlatformIO.

Architecture

┌─────────────────────────────────────┐
│   Your Application  (main.cpp)      │
├─────────────────────────────────────┤
│   NT4Client                         │  topic registry, typed callbacks, value cache
├─────────────────────────────────────┤
│   NTWebSocket                       │  HTTP upgrade, RFC 6455 frame encode/decode
├─────────────────────────────────────┤
│   QNEthernet / lwIP                 │  TCP, DHCP
└─────────────────────────────────────┘

Blocking guarantee

The main loop never blocks on network activity. The only call that can block is nt.beginConnect(), which is bounded to NTWebSocket::kTcpConnectTimeoutMs (150ms, configurable). It only fires when explicitly requested — never automatically.

On a live local network, beginConnect() typically completes in under 10ms.

Quick Start

#include "NT4Client.h"

NT4Client nt;

void setup() {
    Ethernet.begin();  // DHCP, async — does not block

    nt.subscribe("/ferraui/batteryVoltage", [](double v, uint32_t ts) {
        Serial.printf("Battery: %.2fV\n", v);
    });

    nt.onConnect([]() { Serial.println("NT4 ready"); });
    nt.beginConnect(IPAddress(10, 65, 74, 2), 5810, "my-device");
}

void loop() {
    nt.loop();  // non-blocking — call every iteration
}

API Reference

Lifecycle

// TCP connect (bounded blocking) + send HTTP upgrade request.
// Handshake completes non-blockingly via loop().
bool beginConnect(const IPAddress& host, uint16_t port, const char* identity);

void disconnect();

// Drive the state machine — call every loop() iteration
void loop();

bool isConnected();
bool isHandshaking();

Per-topic subscriptions

Register a typed callback for a specific topic. Registrations persist across reconnects. The callback type is inferred from the function pointer signature.

nt.subscribe("/ferraui/batteryVoltage", [](double v,      uint32_t ts){ });
nt.subscribe("/ferraui/enabled",        [](bool v,        uint32_t ts){ });
nt.subscribe("/ferraui/matchTime",      [](float v,       uint32_t ts){ });
nt.subscribe("/ferraui/alliance",       [](const char* v, uint32_t ts){ });
nt.subscribe("/ferraui/lapCount",       [](int32_t v,     uint32_t ts){ });

nt.unsubscribe("/ferraui/batteryVoltage");

timestamp is the robot-side microsecond timestamp from the roboRIO clock.

Catch-all fallback callbacks

Fire for any topic that does not have a per-topic subscription.

nt.onDouble([](const char* name, uint32_t ts, double v)      { });
nt.onFloat ([](const char* name, uint32_t ts, float v)       { });
nt.onInt   ([](const char* name, uint32_t ts, int32_t v)     { });
nt.onBool  ([](const char* name, uint32_t ts, bool v)        { });
nt.onString([](const char* name, uint32_t ts, const char* v) { });

poll()

Fire a subscription's callback with the last received value, optionally fetching from the server if no value has arrived yet.

// Fire immediately from cache (if available), fetch from server if not
nt.poll("/ferraui/batteryVoltage");              // fetchIfMissing = true (default)

// Fire from cache only — don't contact the server
nt.poll("/ferraui/batteryVoltage", false);

// Returns true if a cached value was available and the callback fired.
// Returns false if fetching was initiated or no subscription exists.
bool result = nt.poll("/ferraui/batteryVoltage");

Connection lifecycle callbacks

nt.onConnect   ([]() { /* WebSocket open, topics incoming */ });
nt.onDisconnect([]() { /* fired on TCP drop or handshake timeout */ });

Diagnostics

nt.printTopics();         // prints announced topic table (* = has subscription)
nt.printSubscriptions();  // prints subscription table with types
nt.topicCount();          // number of currently announced topics
nt.subscriptionCount();   // number of active subscriptions

Reconnection

There are no automatic reconnects. Reconnection is always explicit — typically from a button press or the link-up detection in loop().

// Queue a reconnect attempt — safe to call from anywhere including ISRs
void requestReconnect() { _reconnectRequested = true; }

// In loop():
if (_reconnectRequested) {
    _reconnectRequested = false;
    if (ethernetReady() && !nt.isConnected() && !nt.isHandshaking()) {
        nt.beginConnect(kNTServerIP, kNTPort, kIdentity);
    }
}

This ensures a cable pull during a match never causes beginConnect()'s TCP timeout to stall the main loop unexpectedly.

Configuration

Edit the constants at the top of main.cpp:

static const IPAddress kNTServerIP(10, 65, 74, 2);  // 10.TE.AM.2 for FRC robots
static const uint16_t  kNTPort    = 5810;
static const char*     kIdentity  = "teensy-nt4";   // shown in Driver Station
static constexpr bool  kUseStaticIP = false;         // true to skip DHCP

Table sizes and buffer lengths are constants in NT4Client.h:

static constexpr uint8_t  kMaxTopics        = 48;   // max announced topics
static constexpr uint8_t  kMaxSubscriptions = 32;   // max per-topic registrations
static constexpr uint8_t  kMaxNameLen       = 96;   // max topic name length
static constexpr uint16_t kMaxStringLen     = 256;  // receive buffer for string values
static constexpr uint8_t  kCachedStringLen  = 64;   // per-subscription string cache

Increasing these costs only static RAM, of which the Teensy 4.1 has 1MB.

NT4 Protocol Notes

  • Port: 5810 (plain WebSocket, not WSS)
  • Subprotocol: networktables.first.wpi.edu — required in the HTTP upgrade header; WPILib 2023+ will reject connections without it
  • Text frames: JSON arrays of control messages (announce, unannounce, properties)
  • Binary frames: Packed MessagePack arrays of [uid, timestamp, typeId, value] — multiple updates can be packed into a single frame
  • Timestamps: Robot-side microseconds; overflows uint32_t after ~71 minutes of uptime — acceptable for match use

File Structure

platformio.ini
src/
  main.cpp          — application entry point, Ethernet init, reconnect logic
  NTWebSocket.h/.cpp — WebSocket transport (HTTP upgrade + RFC 6455 framing)
  NT4Client.h/.cpp  — NT4 protocol layer (topic registry, MsgPack decode, callbacks)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages