nt4

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 20, 2025 License: MIT Imports: 14 Imported by: 0

README

go-nt4

CI Go Reference Go Report Card

Go implementation of the WPILib NT4 protocol

Installation

go get github.com/levifitzpatrick1/go-nt4

Usage

package main

import (
    "fmt"
    "log"
    "time"
    "github.com/levifitzpatrick1/go-nt4"
)

func main() {
    // Connect to NT4 server at 10.20.64.2
    opts := nt4.DefaultClientOptions(nt4.TeamNumberToAddress(2064))
    opts.OnConnect = func() {
        fmt.Println("NT4 Client Connected")
    }
    opts.OnDisconnect = func() {
        fmt.Println("NT4 Client Disconnected")
    }

    client := nt4.NewClient(opts)

    if err := client.Connect(); err != nil {
        log.Fatal(err)
    }
    defer client.Disconnect()

    // Publish a topic and send data
    examplePub := client.Publish("/SmartDashboard/CoolNumber", nt4.TypeInt, nil)
    client.SetValue(examplePub, int64(123456))

    // Subscribe to a topic
    exampleSub := client.Subscribe([]string{"/SmartDashboard/Example"}, nil)

    // Receive data from subscription with a callback or channel
    exampleSub.SetCallback(func(topic *nt4.Topic, timestamp int64, value any) {
        fmt.Printf("Received data from callback: %v\n", value)
    })

    for update := range exampleSub.Updates() {
        fmt.Printf("Received data from channel: %v\n", update.Value)
    }

    // Temporarily subscribe to a topic to retrieve its data
    oneTimeValue := client.SubscribeAndRetrieve("/SmartDashboard/ConstantValue", 2500*time.Millisecond)

    if oneTimeValue != nil {
        fmt.Printf("Received one time value from server: %v\n", oneTimeValue)
    } else {
        fmt.Println("Client received no value after 2.5 seconds")
    }
}
Typed Publish Methods
client.PublishDouble("/SmartDashboard/Speed", 42.5)
client.PublishString("/SmartDashboard/Mode", "teleop")
client.PublishBoolean("/SmartDashboard/Enabled", true)
client.PublishDoubleArray("/SmartDashboard/Position", []float64{1.0, 2.0, 3.0})
Typed Getters
speed, ok := client.GetDouble("/SmartDashboard/Speed", 2*time.Second)
mode, ok := client.GetString("/SmartDashboard/Mode", 2*time.Second)
enabled, ok := client.GetBoolean("/SmartDashboard/Enabled", 2*time.Second)
Prefix Subscriptions
// Subscribe to all topics starting with /SmartDashboard
sub := client.Subscribe([]string{"/SmartDashboard"}, &nt4.SubscribeOptions{
    Prefix: true,
    All:    true,
})
Connection with Retry
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := client.ConnectWithRetry(ctx); err != nil {
    log.Fatal("Failed to connect:", err)
}

Examples

See the examples/ directory for complete working examples.

Documentation

Full API documentation is available at pkg.go.dev.

License

MIT

Documentation

Overview

Package nt4 provides a Go client for the NetworkTables 4 (NT4) protocol.

NT4 is a WebSocket-based pub/sub protocol used in FIRST Robotics Competition (FRC) for real-time data exchange between robots and control systems. This implementation uses MessagePack for binary serialization and supports all NT4 message types.

Quick Start

Create a client and connect to an NT4 server:

opts := nt4.DefaultClientOptions("10.20.64.2")
client := nt4.NewClient(opts)
if err := client.Connect(); err != nil {
    log.Fatal(err)
}
defer client.Disconnect()

Publishing Topics

Publish a topic and send values:

topic := client.Publish("/robot/speed", nt4.TypeDouble, nil)
client.SetValue(topic, 42.5)

Or use convenience methods:

client.PublishDouble("/robot/speed", 42.5)

Subscribing to Topics

Subscribe to topics and receive updates:

sub := client.Subscribe([]string{"/robot/speed"}, nil)
for update := range sub.Updates() {
    fmt.Printf("%s = %v\n", update.Topic.Name, update.Value)
}

Subscribe with a callback:

opts := &nt4.SubscribeOptions{Prefix: true}
sub := client.Subscribe([]string{"/robot"}, opts)
sub.SetCallback(func(topic *Topic, timestamp int64, value any) {
    fmt.Printf("%s = %v\n", topic.Name, value)
})

Time Synchronization

The client automatically synchronizes time with the server every 3 seconds. Use GetServerTimeOffset() and GetLastRTT() to monitor synchronization.

Important Notes

- The client does NOT automatically reconnect. Use ConnectWithRetry() for retry logic. - To receive topic announcements, you MUST have an active subscription. - Published topics receive server-assigned IDs only when subscribed. - Default port is 5810. - Timestamps are in microseconds (UnixMicro).

Type System

NT4 supports the following types:

  • Primitives: boolean, int, float, double, string
  • Arrays: boolean[], int[], float[], double[], string[]
  • Binary formats: raw, msgpack, protobuf, json

Use the Type constants (TypeBoolean, TypeDouble, etc.) when publishing topics.

Index

Constants

View Source
const (
	TypeBoolean      = "boolean"
	TypeDouble       = "double"
	TypeInt          = "int"
	TypeFloat        = "float"
	TypeString       = "string"
	TypeBooleanArray = "boolean[]"
	TypeDoubleArray  = "double[]"
	TypeIntArray     = "int[]"
	TypeFloatArray   = "float[]"
	TypeStringArray  = "string[]"
	TypeRaw          = "raw"
	TypeMsgpack      = "msgpack"
	TypeProtobuf     = "protobuf"
	TypeJSON         = "json"
)

NT4 type strings.

View Source
const (
	DataTypeBoolean      = 0
	DataTypeDouble       = 1
	DataTypeInt          = 2
	DataTypeFloat        = 3
	DataTypeString       = 4
	DataTypeBinary       = 5
	DataTypeBooleanArray = 16
	DataTypeDoubleArray  = 17
	DataTypeIntArray     = 18
	DataTypeFloatArray   = 19
	DataTypeStringArray  = 20
)

NT4 type binaries.

View Source
const DefaultPort = 5810

Default port for NT4 is on 5810.

Variables

View Source
var (
	// ErrNotConnected is returned when an operation requires an active connection.
	ErrNotConnected = errors.New("not connected to server")

	// ErrAlreadyConnected is returned when attempting to connect while already connected.
	ErrAlreadyConnected = errors.New("already connected to server")

	// ErrInvalidOptions is returned when client options are invalid.
	ErrInvalidOptions = errors.New("invalid client options")

	// ErrTopicNotFound is returned when a requested topic does not exist.
	ErrTopicNotFound = errors.New("topic not found")

	// ErrInvalidTopicID is returned when a topic ID is invalid or not yet assigned.
	ErrInvalidTopicID = errors.New("invalid topic ID")

	// ErrMessageEncoding is returned when message encoding/decoding fails.
	ErrMessageEncoding = errors.New("message encoding failed")

	// ErrMessageDecoding is returned when message decoding fails.
	ErrMessageDecoding = errors.New("message decoding failed")
)

Common error types that can be checked with errors.Is().

Functions

func TeamNumberToAddress

func TeamNumberToAddress(teamNumber int) string

FRC team number to a roboRIO server address. example: 2064 becomes "10.20.64.2".

func TypeIDToString

func TypeIDToString(typeID int) string

NT4 type ID to string.

func TypeStringToID

func TypeStringToID(typeStr string) int

NT4 type string to numeric ID.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the main NT4 client that manages the WebSocket connection, topics, subscriptions, and time synchronization with the server.

func NewClient

func NewClient(options ClientOptions) *Client

NewClient creates a new NT4 client with the specified options. Use DefaultClientOptions() for sensible defaults.

Example:

opts := nt4.DefaultClientOptions("10.20.64.2")
client := nt4.NewClient(opts)

func (*Client) Connect

func (c *Client) Connect() error

Connect establishes a WebSocket connection to the NT4 server. Returns an error if the connection fails.

For automatic retry logic, use ConnectWithRetry() instead.

func (*Client) ConnectWithRetry

func (c *Client) ConnectWithRetry(ctx context.Context) error

ConnectWithRetry attempts to connect to the server with automatic retries. It will keep retrying until successful or the context is cancelled.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := client.ConnectWithRetry(ctx); err != nil {
    log.Fatal("Failed to connect:", err)
}

func (*Client) Disconnect

func (c *Client) Disconnect()

Disconnect closes the connection to the NT4 server. Safe to call multiple times.

func (*Client) GetBoolean

func (c *Client) GetBoolean(name string, timeout time.Duration) (bool, bool)

GetBoolean retrieves a boolean value from a topic. Returns (value, true) if successful, (false, false) if timeout or type mismatch.

func (*Client) GetDouble

func (c *Client) GetDouble(name string, timeout time.Duration) (float64, bool)

GetDouble retrieves a double value from a topic. Returns (value, true) if successful, (0, false) if timeout or type mismatch.

func (*Client) GetInt

func (c *Client) GetInt(name string, timeout time.Duration) (int64, bool)

GetInt retrieves an int value from a topic. Returns (value, true) if successful, (0, false) if timeout or type mismatch.

func (*Client) GetLastRTT

func (c *Client) GetLastRTT() int64

GetLastRTT returns the last measured round-trip time in microseconds.

func (*Client) GetServerTimeOffset

func (c *Client) GetServerTimeOffset() int64

GetServerTimeOffset returns the current server time offset in microseconds. This offset is used to convert local timestamps to server timestamps.

func (*Client) GetString

func (c *Client) GetString(name string, timeout time.Duration) (string, bool)

GetString retrieves a string value from a topic. Returns (value, true) if successful, ("", false) if timeout or type mismatch.

func (*Client) GetTopic

func (c *Client) GetTopic(name string) *Topic

GetTopic retrieves a topic by name, or nil if not found.

func (*Client) GetTopics

func (c *Client) GetTopics() []*Topic

GetTopics returns all currently known topics.

func (*Client) IsConnected

func (c *Client) IsConnected() bool

IsConnected returns true if the client is currently connected to the server.

func (*Client) Publish

func (c *Client) Publish(name string, typeStr string, properties map[string]any) *Topic

Publish announces a new topic to the server and returns the Topic handle. Use SetValue() to publish values to the topic.

Important: To receive the server-assigned topic ID in the announce message, you must have an active subscription. Otherwise, the topic will have ID 0.

Example:

topic := client.Publish("/robot/speed", nt4.TypeDouble, nil)
client.SetValue(topic, 42.5)

func (*Client) PublishBoolean

func (c *Client) PublishBoolean(name string, value bool) *Topic

PublishBoolean publishes a boolean topic with an initial value.

func (*Client) PublishBooleanArray

func (c *Client) PublishBooleanArray(name string, value []bool) *Topic

PublishBooleanArray publishes a boolean array topic with an initial value.

func (*Client) PublishDouble

func (c *Client) PublishDouble(name string, value float64) *Topic

PublishDouble publishes a double topic with an initial value.

func (*Client) PublishDoubleArray

func (c *Client) PublishDoubleArray(name string, value []float64) *Topic

PublishDoubleArray publishes a double array topic with an initial value.

func (*Client) PublishInt

func (c *Client) PublishInt(name string, value int64) *Topic

PublishInt publishes an int topic with an initial value.

func (*Client) PublishIntArray

func (c *Client) PublishIntArray(name string, value []int64) *Topic

PublishIntArray publishes an int array topic with an initial value.

func (*Client) PublishRaw

func (c *Client) PublishRaw(name string, value []byte) *Topic

PublishRaw publishes a raw binary topic with an initial value.

func (*Client) PublishString

func (c *Client) PublishString(name string, value string) *Topic

PublishString publishes a string topic with an initial value.

func (*Client) PublishStringArray

func (c *Client) PublishStringArray(name string, value []string) *Topic

PublishStringArray publishes a string array topic with an initial value.

func (*Client) SetProperties

func (c *Client) SetProperties(name string, properties map[string]any)

SetProperties updates properties for a topic.

func (*Client) SetValue

func (c *Client) SetValue(topic *Topic, value any)

SetValue publishes a value to a topic. The topic must have been created with Publish() or received via subscription.

func (*Client) Subscribe

func (c *Client) Subscribe(topics []string, options *SubscribeOptions) *Subscription

Subscribe creates a subscription to one or more topics. Returns a Subscription that can be used to receive updates via the Updates() channel.

The topics parameter can contain exact topic names or patterns (if Prefix option is set). Set SubscribeOptions.Prefix to true to match topic prefixes.

Example:

// Subscribe to a single topic
sub := client.Subscribe([]string{"/robot/speed"}, nil)

// Subscribe to all topics with a prefix
opts := &nt4.SubscribeOptions{Prefix: true, All: true}
sub := client.Subscribe([]string{"/robot"}, opts)

// Receive updates
for update := range sub.Updates() {
    fmt.Printf("%s = %v\n", update.Topic.Name, update.Value)
}

func (*Client) SubscribeAndRetrieve

func (c *Client) SubscribeAndRetrieve(topic string, timeout time.Duration) any

SubscribeAndRetrieve subscribes to a topic, waits for the first update, then unsubscribes. Returns nil if the timeout expires before receiving a value.

func (*Client) SubscribeWithPrefix

func (c *Client) SubscribeWithPrefix(prefix string, callback func(topic *Topic, timestamp int64, value any)) *Subscription

SubscribeWithPrefix creates a subscription with prefix matching and callback.

func (*Client) Unpublish

func (c *Client) Unpublish(topic *Topic)

Unpublish removes a published topic from the server.

func (*Client) Unsubscribe

func (c *Client) Unsubscribe(sub *Subscription)

Unsubscribe removes an active subscription.

func (*Client) WaitForConnection

func (c *Client) WaitForConnection(ctx context.Context) error

WaitForConnection blocks until the client is connected or the context is cancelled.

type ClientOptions

type ClientOptions struct {
	// Address of NT4 server (10.TE.AM.2 for rio, 127.0.0.1 for sim).
	ServerAddress string

	// NT4 Port.
	// Default: 5810 (DefaultPort)
	Port int

	// Client identifier.
	// Default: "Go-NT4-Client"
	Identity string

	// Called when the client connects to the server.
	// Optional callback.
	OnConnect func()

	// Called when the client disconnects from the server.
	// Optional callback.
	OnDisconnect func()

	// OnTopicAnnounce is called when a topic is announced by the server.
	// To receive announcements, you must have an active subscription.
	// Optional callback.
	OnTopicAnnounce func(topic *Topic)

	// OnTopicUnannounce is called when a topic is unannounced by the server.
	// Optional callback.
	OnTopicUnannounce func(topic *Topic)

	// Wait between reconnection attempts when using ConnectWithRetry.
	// Default: 1 second
	ReconnectInterval time.Duration

	// Logger instance.
	// Default: NewDefaultLogger(LogLevelInfo)
	// Use NewSilentLogger() to disable logging.
	Logger Logger
}

NT4 client config.

func DefaultClientOptions

func DefaultClientOptions(serverAddress string) ClientOptions

ClientOptions with defaults. See ClientOptions for serverAddress.

type ConnectionError

type ConnectionError struct {
	Address string
	Port    int
	Err     error
}

ConnectionError represents an error that occurred during connection establishment.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

Error implements the error interface.

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

Unwrap returns the underlying error.

type DefaultLogger

type DefaultLogger struct {
	// contains filtered or unexported fields
}

DefaultLogger provides a simple logger that writes to stdout using the standard library log package. It supports log levels and structured key-value pairs.

func NewDefaultLogger

func NewDefaultLogger(level LogLevel) *DefaultLogger

NewDefaultLogger creates a new DefaultLogger with the specified log level. Use LogLevelInfo for normal operation, LogLevelDebug for troubleshooting, or LogLevelSilent to disable all logging.

func (*DefaultLogger) Debug

func (l *DefaultLogger) Debug(msg string, keysAndValues ...any)

Debug logs a debug message if the log level is LogLevelDebug.

func (*DefaultLogger) Error

func (l *DefaultLogger) Error(msg string, keysAndValues ...any)

Error logs an error message if the log level is LogLevelError or lower.

func (*DefaultLogger) Info

func (l *DefaultLogger) Info(msg string, keysAndValues ...any)

Info logs an informational message if the log level is LogLevelInfo or lower.

func (*DefaultLogger) Warn

func (l *DefaultLogger) Warn(msg string, keysAndValues ...any)

Warn logs a warning message if the log level is LogLevelWarn or lower.

type LogLevel

type LogLevel int

LogLevel represents the severity of a log message.

const (
	// LogLevelDebug enables all logging including verbose debug messages.
	LogLevelDebug LogLevel = iota
	// LogLevelInfo enables informational messages, warnings, and errors.
	LogLevelInfo
	// LogLevelWarn enables warnings and errors only.
	LogLevelWarn
	// LogLevelError enables error messages only.
	LogLevelError
	// LogLevelSilent disables all logging.
	LogLevelSilent
)

func (LogLevel) String

func (l LogLevel) String() string

String returns the string representation of a LogLevel.

type Logger

type Logger interface {
	// Debug logs a debug message with optional key-value pairs.
	Debug(msg string, keysAndValues ...any)
	// Info logs an informational message with optional key-value pairs.
	Info(msg string, keysAndValues ...any)
	// Warn logs a warning message with optional key-value pairs.
	Warn(msg string, keysAndValues ...any)
	// Error logs an error message with optional key-value pairs.
	Error(msg string, keysAndValues ...any)
}

Logger is the interface for logging within the NT4 client. It follows a structured logging pattern with key-value pairs, similar to slog.

Example usage:

logger.Info("connected to server", "address", "10.20.64.2", "port", 5810)
logger.Error("connection failed", "error", err)

func NewSilentLogger

func NewSilentLogger() Logger

NewSilentLogger returns a logger that discards all log messages.

type ProtocolError

type ProtocolError struct {
	Message string
	Err     error
}

ProtocolError represents an error in NT4 protocol handling.

func (*ProtocolError) Error

func (e *ProtocolError) Error() string

Error implements the error interface.

func (*ProtocolError) Unwrap

func (e *ProtocolError) Unwrap() error

Unwrap returns the underlying error.

type SilentLogger

type SilentLogger struct{}

SilentLogger is a logger that discards all log messages. Useful for testing or when you want to completely disable logging.

func (*SilentLogger) Debug

func (l *SilentLogger) Debug(msg string, keysAndValues ...any)

Debug does nothing.

func (*SilentLogger) Error

func (l *SilentLogger) Error(msg string, keysAndValues ...any)

Error does nothing.

func (*SilentLogger) Info

func (l *SilentLogger) Info(msg string, keysAndValues ...any)

Info does nothing.

func (*SilentLogger) Warn

func (l *SilentLogger) Warn(msg string, keysAndValues ...any)

Warn does nothing.

type SubscribeOptions

type SubscribeOptions struct {
	// Time between updates in seconds. (0 for fastest)
	Periodic float64

	// Send all values or only latest.
	All bool

	// Send topic announcements without values.
	TopicsOnly bool

	// If true, treats items in Topics as prefixes.
	Prefix bool
}

Config for subscribers.

type Subscription

type Subscription struct {
	// Client assigned subscription ID.
	UID int32

	// List of topic paths subscribed to.
	Topics []string

	// Subscription config.
	Options SubscribeOptions
	// contains filtered or unexported fields
}

Active subscription to one or more topics.

func (*Subscription) CloseUpdates

func (sub *Subscription) CloseUpdates()

Close the update channel. Called internally when unsubscribing.

func (*Subscription) GetCallback

func (sub *Subscription) GetCallback() func(topic *Topic, timestamp int64, value any)

Get the current callback function.

func (*Subscription) InitUpdates

func (sub *Subscription) InitUpdates(bufferSize int)

Creates the update channel with specified buffer size. Called internally when creating a subscription.

func (*Subscription) SendUpdate

func (sub *Subscription) SendUpdate(update TopicUpdate) bool

Send an update to the channel. Returns true if sent, false if channel is full. Called internally.

func (*Subscription) SetCallback

func (sub *Subscription) SetCallback(callback func(topic *Topic, timestamp int64, value any))

Set callback for topic updates. Non-blocking.

func (*Subscription) Updates

func (sub *Subscription) Updates() <-chan TopicUpdate

Get the updates channel for this subscription. Updates are sent to both the channel and any registered callback.

Example:

for update := range subscription.Updates() {
    fmt.Printf("Topic: %s, Value: %v\n", update.Topic.Name, update.Value)
}

type TimeoutError

type TimeoutError struct {
	Operation string
	Err       error
}

TimeoutError represents a timeout during an operation.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

Error implements the error interface.

func (*TimeoutError) Unwrap

func (e *TimeoutError) Unwrap() error

Unwrap returns the underlying error.

type Topic

type Topic struct {
	// Server assigned topic ID.
	ID int32

	// Topic path ("/robot/speed").
	Name string

	// NT4 type string (e.g., "double", "string", "boolean[]").
	// Should be pulled from NT4 Type String constants.
	Type string

	// Type ID for binary messages.
	// Should be pulled from NT4 Type Binary constants.
	TypeID int

	// Topic metadata (e.g., "persistent", "retained").
	Properties map[string]any

	// Publisher UID.
	PubUID int32
	// contains filtered or unexported fields
}

A NT4 topic.

func (*Topic) UpdateProperties

func (t *Topic) UpdateProperties(props map[string]any)

Thread safe merge of new properties into current.

type TopicUpdate

type TopicUpdate struct {
	// Topic is the Network Table topic that was updated.
	Topic *Topic

	// Server timestamp microseconds.
	Timestamp int64

	// The value to be set. The value should align to the Topic type.
	Value any
}

TopicUpdate represents an update to a subscribed topic.

type ValidationError

type ValidationError struct {
	Field   string
	Value   any
	Message string
}

ValidationError represents invalid input or configuration.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

Directories

Path Synopsis
examples
bidirectional command
publisher command
subscriber command
team_robot command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL