Skip to content

Repository files navigation

tarwyn

CI Release

A key/value server for FRC robots, written in Rust. It speaks NetworkTables 4.1, so AdvantageScope and other NT4 tools connect to it directly, and it ships Rust, Java and Python clients.

Start the server with:

cargo run -p tarwyn_server

Publishers, readers and control all share one WebSocket connection (tungstenite, on 5810). Control messages are protobuf Request/Reply and values are MessagePack, both chosen to keep the bytes on the wire small. The telemetry plane stays on UDP 5809, fire and forget, with no delivery guarantee.

.get and the other control reads send a binary protobuf Request over that connection and wait for the matching Reply. If the server does not answer within the request timeout, they return None.

The API reference is the rustdoc: cargo doc --workspace --open.

Benchmarks

One-way latency is measured against WPILib's ntcore, publisher and subscriber as separate processes on one host. Numbers move enough between machines and between runs that they belong next to the spread that produced them: see bench/RESULTS.md for the current tables and bench/BENCHMARK.md for the subjects and how to rerun them.

Requirements

The server and every client share one native library, so the platform rules are the same for all of them. The server and clients use tungstenite (pure-Rust) for transport, so no libzmq is needed.

Needs
Server 64-bit Linux, macOS or Windows
Rust client Rust 1.88+ (edition 2024)
Java client JDK 25+, and --enable-native-access
Python client Python 3.11-3.14

Platforms. The Rust server supports linux-x86_64, linux-aarch64, windows-x86_64, windows-aarch64, and macos-aarch64. The Java jar carries linux-x86_64, linux-aarch64, windows-x86_64, and macos-aarch64, and unpacks the right one at runtime. Linux needs glibc 2.35+.

Not supported: the roboRIO, musl distributions, anything 32-bit, JDK 24 and older.

Building from source needs nothing beyond Rust and a JDK. The Java client uses the Foreign Function & Memory API, so there is no JNI shim to compile and no C++ toolchain. The two UniFFI generators are workspace crates under tools/, so Gradle builds them from source at the version this repo pins.

Ports. WebSocket 5810 (values + control; endpoint /nt/<client name>, the name chosen by the client), UDP 5809 (telemetry). Both sit in the 5800-5810 range FIRST reserves for team use, which is the only range an FRC field's FMS leaves open between the robot and the driver station. The two live ports are configurable through Server::with_ports_and_telemetry (the 3rd and 4th arguments); the PUB/SUB and PUSH/PULL slots are kept for source compatibility but unused.

Listening address. Both planes listen on every interface, since the clients are the driver station and the coprocessors rather than anything on the robot controller itself. Neither plane authenticates its callers, so on a shared network pass --bind 127.0.0.1 to keep the WebSocket plane local, or reach it through Server::try_with_bind.

Tools

Make sure you have Rust, Python and Java installed. You do not need protoc: the protobuf definitions are compiled by protox, a pure-Rust compiler, so a clean cargo build needs no external toolchain.

Commit hooks run through pre-commit. They cover formatting and clippy. Whether the committed clients still match bindings/src/lib.rs, the tests and the Gradle build stay in CI.

Regenerate the clients after changing the bindings:

./gradlew uniffiGenerate generateWrapper pythonWheel

Example

Client::new() connects to localhost. For another machine, such as a coprocessor or the robot controller, pass its address:

let client = Client::connect("10.4.88.2");

with_config takes a Config to override the ports or the request timeout. Connecting never blocks. The client's reader thread keeps retrying in the background, so you can build a client before the server exists.

use tarwyn_client::Client;

fn main() {
    println!("Starting tarwyn client...");
    let client = Client::new();

    let _ = client.subscribe_to_logs(|logs| {
        println!("{}", logs);
    });

    let _ = client.subscribe("test", |data| {
        println!("Received data on 'test': {:?}", data);
    });
    client.start();

    client.send_bool("test", true);

    loop {
        std::thread::sleep(std::time::Duration::from_secs(5));
    }
}

Logging

Every published value can be mirrored to a WPILOG file, which AdvantageScope, Elastic and the WPILib DataLogTool open directly.

client.log_to("/home/lvuser/match.wpilog")?;

log_to_drive picks the first writable removable mount under /media, /run/media or /mnt and returns the path it chose:

let path = client.log_to_drive("match.wpilog")?;

A writer thread takes records over a bounded queue and flushes every 250 ms, so a publish never waits on the filesystem. Overflow is dropped, not queued: log_dropped() counts it, logging_healthy() reports whether the writer still succeeds. Java has logTo, logToDrive, droppedLogRecords, loggingHealthy; Python matches the Rust names.

Notices

Do not name a channel TARWYN_INTERNAL, or start one with that prefix. The server uses it for its own traffic, and yours may collide with it.

Roadmap

  • Unit Testing
  • Custom Logging
  • Server Logger Interface
  • Further Benchmarking

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages