A TypeScript library for communication over WPILib's NetworkTables 4.1 protocol.
- NodeJS and DOM support
- Togglable auto-reconnect
- Callbacks for new data on subscriptions
- Callbacks for connection listeners
- Wildcard prefix listeners for multiple topics
- Protobuf support with optional type generation and Zod validation
- Retrying for messages queued during a connection loss
- On-the-fly server switching with resubscribing and republishing
- Generic types for Topics
- Client-side data validation using Zod
- Server-matching timestamping using RTT calculation
- Granular logging with configurable log levels per module
TypeDocs are available at https://ntcore.chrislawson.dev
This section will help get you started with sending and receiving data over NetworkTables
npm install --save @ntcore/client
The NetworkTables class is instance-based, but allows for connections to multiple teams/URIs.
Use this at the top of your file:
import { NetworkTables } from '@ntcore/client';Use this function:
NetworkTables.getInstanceByTeam(team: number, port = 5810)This creates the instance using the team number. Connects to
roborio-<team>-frc.local
Use this function:
NetworkTables.getInstanceByURI(uri: string, port?)This creates the instance using a custom URI, i.e. 127.0.0.1, localhost, google.com, etc.
To use a Topic, it must be created through the NetworkTables client using the function:
createTopic<T extends NetworkTablesTypes>(name: string, typeInfo: NetworkTablesTypeInfo, defaultValue?: T)The valid
NetworkTablesTypesarestring | number | boolean | string[] | Uint8Array | boolean[] | number[](plusobjectfor JSON topics)The valid
NetworkTablesTypeInfos are:
NetworkTablesTypeInfos.kBooleanNetworkTablesTypeInfos.kDoubleNetworkTablesTypeInfos.kIntegerNetworkTablesTypeInfos.kFloatNetworkTablesTypeInfos.kStringNetworkTablesTypeInfos.kJsonNetworkTablesTypeInfos.kUint8Array(raw binary)NetworkTablesTypeInfos.kRPCNetworkTablesTypeInfos.kMsgpackNetworkTablesTypeInfos.kProtobuf(usecreateProtobufTopicinstead ofcreateTopic)NetworkTablesTypeInfos.kBooleanArrayNetworkTablesTypeInfos.kDoubleArrayNetworkTablesTypeInfos.kIntegerArrayNetworkTablesTypeInfos.kFloatArrayNetworkTablesTypeInfos.kStringArray
Once a topic has been created, it can be used as a subscriber:
subscribe(
callback: (value: T | null, params: AnnounceMessageParams) => void,
options: SubscribeOptions = {},
id?: number,
save = true
)and/or a publisher:
await publish(properties: TopicProperties = {}, id?: number)For example, here's a subscription for a Gyro:
import { NetworkTables, NetworkTablesTypeInfos } from '@ntcore/client';
// Get or create the NT client instance
const ntcore = NetworkTables.getInstanceByTeam(973);
// Create the gyro topic
const gyroTopic = ntcore.createTopic<number>('/MyTable/Gyro', NetworkTablesTypeInfos.kDouble);
// Subscribe and immediately call the callback with the current value
gyroTopic.subscribe((value) => {
console.log(`Got Gyro Value: ${value}`);
});
// Or you can use the topic's announce parameters to get more info, like the topic ID
gyroTopic.subscribe((value, params) => {
console.log(`Got Gyro Value: ${value} at from topic id ${params.id}`);
});Or a publisher for an auto mode:
import { NetworkTables, NetworkTablesTypeInfos } from '@ntcore/client';
// Get or create the NT client instance
const ntcore = NetworkTables.getInstanceByTeam(973);
// Create the AutoMode topic w/ a default return value of 'No Auto'
const autoModeTopic = ntcore.createTopic<string>('/MyTable/AutoMode', NetworkTablesTypeInfos.kString, 'No Auto');
// Make us the publisher
await autoModeTopic.publish();
// Set a new value, this will error if we aren't the publisher!
autoModeTopic.setValue('25 Ball Auto and Climb');You can also subscribe to multiple topics by using a "wildcard" through creating a prefix topic.
For example, here's a subscription for an Accelerometer with topics /MyTable/Accelerometer/X, /MyTable/Accelerometer/Y, and /MyTable/Accelerometer/Z:
import { NetworkTables } from '@ntcore/client';
// Get or create the NT client instance
const ntcore = NetworkTables.getInstanceByTeam(973);
// Create the accelerometer prefix topic
const accelerometerTopic = ntcore.createPrefixTopic('/MyTable/Accelerometer/');
let x, y, z;
// Subscribe to all topics under the prefix /MyTable/Accelerometer/
accelerometerTopic.subscribe((value, params) => {
console.log(`Got Accelerometer Value: ${value} from topic ${params.name}`); // i.e. Got Accelerometer Value: 9.81 from topic /MyTable/Accelerometer/Y
// You can also use the topic name to determine which value to set
if (params.name.endsWith('X')) {
x = value;
} else if (params.name.endsWith('Y')) {
y = value;
} else if (params.name.endsWith('Z')) {
z = value;
}
// Since there can be many types in subtopics,
// you can use the type information for other checks...
if (params.type === 'int') {
console.warn('Hmm... the accelerometer seems low precision');
} else if (params.type === 'double') {
console.log('The accelerometer is high precision');
}
});
// x, y, and z will be updated as new values come inFor custom message types using Protocol Buffers, use createProtobufTopic instead of createTopic. The library fetches the schema from NetworkTables and can decode values in subscriber callbacks. For type-safe decoding, pass a Zod schema as a runtime validator.
Subscribing to a protobuf topic (e.g. a topic announced by the robot with a known shape):
import { NetworkTables } from '@ntcore/client';
import { z } from 'zod';
const ntcore = NetworkTables.getInstanceByTeam(973);
// Define a validator for the decoded protobuf shape
const poseSchema = z.object({
translation: z.object({ x: z.number(), y: z.number() }),
rotation: z.object({ value: z.number() }),
});
type Pose = z.infer<typeof poseSchema>;
const poseTopic = ntcore.createProtobufTopic<Pose>('/MyTable/Pose', {
validator: poseSchema,
});
poseTopic.subscribe((value) => {
console.log(`Pose: x=${value?.translation.x}, y=${value?.translation.y}`);
});Publishing to a protobuf topic (with a local .proto file so the client can encode and register the schema):
import * as path from 'path';
import { NetworkTables } from '@ntcore/client';
const ntcore = NetworkTables.getInstanceByURI('localhost');
// Type can be generated from the .proto file (e.g. with ts-proto)
const sensorTopic = ntcore.createProtobufTopic<{ timestamp: number; value: number }>('/MyTable/Sensor', {
protoFilePath: path.join(__dirname, 'sensor.proto'),
});
await sensorTopic.publish();
sensorTopic.setValue({ timestamp: Date.now(), value: 42.5 });You can also subscribe to all topics by doing the above, but with a prefix of /.
For example, here's a subscription for all topics:
import { NetworkTables } from '@ntcore/client';
// Get or create the NT client instance
const ntcore = NetworkTables.getInstanceByTeam(973);
// Create a prefix for all topics
const allTopics = ntcore.createPrefixTopic('/');
// Subscribe to all topics
allTopics.subscribe((value, params) => {
console.log(`Got Value: ${value} from topic ${params.name}`);
});The API for Topics is much more exhaustive than this quick example. Feel free to view the docs at https://ntcore.chrislawson.dev.
The library uses tslog for structured logging with granular control over log levels. Logging is configured per module (socket, messenger, pubsub) and can be adjusted at runtime.
LogLevel.TRACE- Very detailed debugging informationLogLevel.DEBUG- Detailed debugging informationLogLevel.INFO- General informational messages (default)LogLevel.WARN- Warning messagesLogLevel.ERROR- Error messagesLogLevel.FATAL- Fatal error messagesLogLevel.SILENT- Disable all logging
Set the log level for all modules:
import { NetworkTables, LogLevel } from '@ntcore/client';
// Set global log level to DEBUG
NetworkTables.setLogLevel(LogLevel.DEBUG);
// Disable all logging
NetworkTables.setLogLevel(LogLevel.SILENT);Configure log levels for specific modules to focus debugging on particular areas:
import { NetworkTables, LogLevel } from '@ntcore/client';
// Enable detailed debugging for socket connections only
NetworkTables.setModuleLogLevel('socket', LogLevel.DEBUG);
// Set messenger to only show warnings and errors
NetworkTables.setModuleLogLevel('messenger', LogLevel.WARN);
// Disable pubsub logging completely
NetworkTables.setModuleLogLevel('pubsub', LogLevel.SILENT);Available modules:
'socket'- WebSocket connection management'messenger'- Message publishing and subscription handling'pubsub'- Topic management and value updates'default'- General library logging
Check the current log level for a module:
import { NetworkTables, LogLevel } from '@ntcore/client';
const currentLevel = NetworkTables.getModuleLogLevel('socket');
console.log(`Socket log level: ${LogLevel[currentLevel]}`);By default, the library logs:
- INFO: Connection status, protocol version
- WARN: Connection issues, unhandled message types
- ERROR: WebSocket errors, connection failures
- DEBUG: Reconnection attempts, unknown topics (development only)
Example output:
2024.01.15 14:30:25:123 [INFO] SOCKET Connected on NT 4.1
2024.01.15 14:30:25:124 [INFO] SOCKET Robot Connected!
2024.01.15 14:30:30:456 [DEBUG] PUBSUB Received update for unknown topic { topicId: 42 }
You can also import and use the logger utilities directly:
import { LogLevel, setLogLevel, setModuleLogLevel, LoggerModule } from '@ntcore/client';
// Set log levels programmatically
setLogLevel(LogLevel.INFO);
setModuleLogLevel('socket' as LoggerModule, LogLevel.DEBUG);- "Raw" and other binary types (RPC, msgpack, protobuf) use
Uint8Array; the library does not useArrayBufferdirectly for topic values.
Contributions are welcome and encouraged! If you encounter a bug, please open an issue and provide as much information as possible. If you'd like to open a PR, I'll be more than happy to review it as soon as I can!