diff --git a/.github/workflows/dashboard.yml b/.github/workflows/dashboard.yml
deleted file mode 100644
index ce71121..0000000
--- a/.github/workflows/dashboard.yml
+++ /dev/null
@@ -1,153 +0,0 @@
-name: Dashboard
-on: [pull_request, push]
-
-env:
- NODE_VERSION: 20
-
-jobs:
- format:
- name: Check Format
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout Repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Setup Node
- uses: actions/setup-node@v4
- with:
- node-version: ${{ env.NODE_VERSION }}
- registry-url: https://registry.npmjs.org/
-
- - name: Install Node Modules
- run: npm i --include=dev
- working-directory: dashboard
-
- - name: Check dashboard formatting
- run: npm run format:check
- working-directory: dashboard
-
- build:
- env:
- MACOSX_DEPLOYMENT_TARGET: 13.3
-
- strategy:
- fail-fast: false
- matrix:
- include:
- - artifact: Windows-x86_64
- platform: windows-2022
- build-flags: --target=x86_64-pc-windows-msvc
-
- - artifact: Windows-arm64
- platform: windows-2022
- build-flags: --target=aarch64-pc-windows-msvc
-
- - artifact: macOS-x86_64
- platform: macOS-14
- build-flags: --target=x86_64-apple-darwin
-
- - artifact: macOS-arm64
- platform: macOS-14
- build-flags: --target=aarch64-apple-darwin
-
- - artifact: Linux-x86_64
- platform: ubuntu-22.04
- build-flags: --target=x86_64-unknown-linux-gnu
-
- name: "Build ${{ matrix.artifact }}"
- runs-on: ${{ matrix.platform }}
-
- steps:
- - name: Checkout Repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Setup Node
- uses: actions/setup-node@v4
- with:
- node-version: ${{ env.NODE_VERSION }}
- registry-url: https://registry.npmjs.org/
-
- - name: Setup Rust
- uses: dtolnay/rust-toolchain@stable
-
- - name: Install System Dependencies (Ubuntu Only)
- if: startsWith(matrix.platform, 'ubuntu')
- run: |
- sudo apt-get update -q
- sudo apt-get install -y \
- libwebkit2gtk-4.1-dev \
- build-essential \
- curl \
- wget \
- file \
- libxdo-dev \
- libssl-dev \
- libayatana-appindicator3-dev \
- librsvg2-dev
-
- - name: Install Node Modules
- run: npm i --include=dev
- working-directory: dashboard
-
- - name: Install Windows aarch64 Rust compiler (Windows-arm64 Only)
- if: matrix.artifact == 'Windows-arm64'
- run: rustup target install aarch64-pc-windows-msvc
-
- - name: Install macOS x86_64 Rust compiler (macOS-x86_64 Only)
- if: matrix.artifact == 'macOS-x86_64'
- run: rustup target add x86_64-apple-darwin
-
- - name: Setup Xcode (macOS Only)
- if: startsWith(matrix.platform, 'macOS')
- run: sudo xcode-select -switch /Applications/Xcode_15.3.app
-
- - name: Run Tauri Build
- uses: tauri-apps/tauri-action@v0
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- projectPath: dashboard
- args: ${{ matrix.build-flags }}
-
- - name: Upload bundle (Windows-x86_54)
- if: matrix.artifact == 'Windows-x86_64'
- uses: actions/upload-artifact@v4
- with:
- name: GRRDashboard-${{ matrix.artifact }}
- path: dashboard/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
-
- - name: Upload bundle (Windows-arm64)
- if: matrix.artifact == 'Windows-arm64'
- uses: actions/upload-artifact@v4
- with:
- name: GRRDashboard-${{ matrix.artifact }}
- path: dashboard/src-tauri/target/aarch64-pc-windows-msvc/release/bundle/nsis/*.exe
-
- - name: Upload bundle (macOS-x86_64)
- if: matrix.artifact == 'macOS-x86_64'
- uses: actions/upload-artifact@v4
- with:
- name: GRRDashboard-${{ matrix.artifact }}
- path: dashboard/src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/*.dmg
-
- - name: Upload bundle (macOS-arm64)
- if: matrix.artifact == 'macOS-arm64'
- uses: actions/upload-artifact@v4
- with:
- name: GRRDashboard-${{ matrix.artifact }}
- path: dashboard/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
-
- - name: Upload bundle (Linux-x86_64)
- if: matrix.artifact == 'Linux-x86_64'
- uses: actions/upload-artifact@v4
- with:
- name: GRRDashboard-${{ matrix.artifact }}
- path: |
- dashboard/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
- dashboard/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
- dashboard/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
diff --git a/.github/workflows/robot-code.yml b/.github/workflows/robot-code.yml
index 2abd919..313b56e 100644
--- a/.github/workflows/robot-code.yml
+++ b/.github/workflows/robot-code.yml
@@ -1,5 +1,5 @@
name: Robot Code
-on: [pull_request, push]
+on: push
env:
NODE_VERSION: 20
diff --git a/.vscode/settings.json b/.vscode/settings.json
index c944399..1ef0336 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -64,27 +64,29 @@
"**/*~": true
},
"cSpell.words": [
- "ADIS",
"Accl",
"Accum",
+ "ADIS",
+ "Autoset",
"Backports",
"Bezier",
"Botpose",
"Brushless",
- "CANcoder",
- "CTRE",
"Canandcolor",
"Canandgyro",
"Canandmag",
"Canbus",
+ "CANcoder",
+ "CANdi",
"Checkstyle",
"Choreo",
"ChoreoLib",
"Cnfg",
- "DTheta",
+ "CTRE",
"Deadband",
"Deadbands",
"Deadzone",
+ "Debouncer",
"Decel",
"DeltAng",
"DeltVel",
@@ -97,29 +99,32 @@
"Discretize",
"Discretizing",
"Doppel",
+ "DTheta",
+ "Dunkin",
"EEPROM",
"Expdelta",
"Falsi",
"Feedforward",
"Fullscreen",
- "GRRDashboard",
- "GSON",
"Gradlew",
"Grav",
+ "GRRDashboard",
+ "GSON",
"Holonomic",
"Intaking",
"Integ",
"Interpolatable",
"Itor",
"JoystickProfiles",
+ "Kalman",
"Keepalive",
"Lerp",
"Motorcontrol",
"Msgpack",
"Mult",
"Multiturn",
- "NTURI",
"NetworkTables",
+ "NTURI",
"Odometry",
"Overcurrent",
"PIDF",
@@ -127,16 +132,17 @@
"Protobuf",
"Pubuid",
"Quasistatic",
- "REVPH",
- "REVRobotics",
"Ratelimit",
"Ratelimited",
"Ratelimiter",
"Ratelimits",
"Rawtypes",
"ReduxLib",
+ "Reefscape",
"Regula",
"RevLib",
+ "REVPH",
+ "REVRobotics",
"RoboRIO",
"Sendables",
"Setpoint",
@@ -153,12 +159,13 @@
"Topicsonly",
"Traj",
"Tunables",
- "URCL",
"Unannounce",
+ "Unjams",
"Unsub",
"Unsubscriber",
"Unsubscribers",
"Unused",
+ "URCL",
"Vbat",
"Vmax",
"WPIBlue",
diff --git a/LICENSE b/LICENSE
index 0f12b67..0fcd501 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Team 340
+Copyright (c) 2025 Team 340
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/build.gradle b/build.gradle
index af3616b..9c96e9d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
plugins {
id "java"
- id "edu.wpi.first.GradleRIO" version "2025.2.1"
- id "com.diffplug.spotless" version "6.25.0"
+ id "edu.wpi.first.GradleRIO" version "2025.3.2"
+ id "com.diffplug.spotless" version "7.0.2"
}
java {
@@ -27,7 +27,16 @@ deploy {
// First part is artifact name, 2nd is artifact type
// getTargetTypeClass is a shortcut to get the class type using a string
+ // Configure the JVM to optimize GC
+ // This should be removed if using a RoboRIO 1
frcJava(getArtifactTypeClass('FRCJavaArtifact')) {
+ jvmArgs.add("-XX:+UnlockExperimentalVMOptions")
+ jvmArgs.add("-Xmx100M")
+ jvmArgs.add("-Xms100M")
+ jvmArgs.add("-XX:GCTimeRatio=5")
+ jvmArgs.add("-XX:+UseSerialGC")
+ jvmArgs.add("-XX:MaxGCPauseMillis=50")
+ jvmArgs.add("-XX:+AlwaysPreTouch")
}
// Static files artifact
@@ -72,6 +81,8 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+
+ implementation 'com.google.code.gson:gson:2.11.0'
}
// Code formatting via spotless
@@ -86,7 +97,7 @@ spotless {
endWithNewline()
removeUnusedImports()
trimTrailingWhitespace()
- prettier(['prettier': '3.4.2', 'prettier-plugin-java': '2.6.5'])
+ prettier(['prettier': '3.5.3', 'prettier-plugin-java': '2.6.7'])
.config([
'parser': 'java',
'plugins': ['prettier-plugin-java'],
diff --git a/dashboard/.gitignore b/dashboard/.gitignore
deleted file mode 100644
index 5bbba68..0000000
--- a/dashboard/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-build/
-node_modules/
-src-tauri/target/
-src-tauri/gen/schemas/
-src-tauri/Cargo.lock
-package-lock.json
diff --git a/dashboard/.prettierrc b/dashboard/.prettierrc
deleted file mode 100644
index 7feb655..0000000
--- a/dashboard/.prettierrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "plugins": ["prettier-plugin-svelte"],
- "tabWidth": 4,
- "printWidth": 140,
- "svelteStrictMode": true,
- "svelteAllowShorthand": true
-}
diff --git a/dashboard/index.html b/dashboard/index.html
deleted file mode 100644
index 3bdc6c3..0000000
--- a/dashboard/index.html
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
-
-
- GRRDashboard
-
-
-
-
-
-
-
-
-
-
-
diff --git a/dashboard/package.json b/dashboard/package.json
deleted file mode 100644
index aadf0b3..0000000
--- a/dashboard/package.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "name": "grrdashboard",
- "version": "2025.0.0",
- "description": "",
- "type": "module",
- "scripts": {
- "build": "vite build --emptyOutDir",
- "check": "svelte-check --tsconfig ./tsconfig.json",
- "dev": "vite",
- "format": "prettier --write --plugin prettier-plugin-svelte ./src/",
- "format:check": "prettier --check --plugin prettier-plugin-svelte ./src/",
- "preview": "vite preview",
- "tauri": "tauri"
- },
- "license": "MIT",
- "dependencies": {
- "@tauri-apps/api": ">=2.0.0-rc.0",
- "@tauri-apps/plugin-shell": ">=2.0.0-rc.0"
- },
- "devDependencies": {
- "@sveltejs/adapter-static": "^3.0.5",
- "@sveltejs/vite-plugin-svelte": "^3.1.2",
- "@tauri-apps/cli": ">=2.0.0-rc.0",
- "@tsconfig/svelte": "^5.0.4",
- "prettier": "^3.3.3",
- "prettier-plugin-svelte": "^3.2.6",
- "svelte": "^4.2.19",
- "svelte-check": "^4.0.2",
- "tslib": "^2.7.0",
- "typescript": "^5.6.2",
- "vite": "^5.4.7"
- }
-}
diff --git a/dashboard/src-tauri/Cargo.toml b/dashboard/src-tauri/Cargo.toml
deleted file mode 100644
index be94c78..0000000
--- a/dashboard/src-tauri/Cargo.toml
+++ /dev/null
@@ -1,23 +0,0 @@
-[package]
-name = "grrdashboard"
-version = "2025.0.0"
-edition = "2021"
-description = "A custom built dashboard by Team 340 for the FIRST Robotics Competition"
-repository = "https://github.com/Greater-Rochester-Robotics/GRRBase.git"
-homepage = "https://team340.org"
-authors = ["Team 340"]
-license = "MIT"
-
-[lib]
-name = "grrdashboard_lib"
-crate-type = ["lib", "cdylib", "staticlib"]
-
-[build-dependencies]
-tauri-build = { version = "2.0.0-rc", features = [] }
-
-[dependencies]
-tauri = { version = "2.0.0-rc", features = [] }
-tauri-plugin-shell = "2.0.0-rc"
-serde = { version = "1", features = ["derive"] }
-serde_json = "1"
-
diff --git a/dashboard/src-tauri/build.rs b/dashboard/src-tauri/build.rs
deleted file mode 100644
index d860e1e..0000000
--- a/dashboard/src-tauri/build.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- tauri_build::build()
-}
diff --git a/dashboard/src-tauri/capabilities/default.json b/dashboard/src-tauri/capabilities/default.json
deleted file mode 100644
index 3bb4cc4..0000000
--- a/dashboard/src-tauri/capabilities/default.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "$schema": "../gen/schemas/desktop-schema.json",
- "identifier": "default",
- "description": "Capability for the main window",
- "windows": ["main"],
- "permissions": [
- "core:default",
- "shell:allow-open"
- ]
-}
diff --git a/dashboard/src-tauri/icons/128x128.png b/dashboard/src-tauri/icons/128x128.png
deleted file mode 100644
index eb27e5c..0000000
Binary files a/dashboard/src-tauri/icons/128x128.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/128x128@2x.png b/dashboard/src-tauri/icons/128x128@2x.png
deleted file mode 100644
index 739867c..0000000
Binary files a/dashboard/src-tauri/icons/128x128@2x.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/32x32.png b/dashboard/src-tauri/icons/32x32.png
deleted file mode 100644
index ba5a102..0000000
Binary files a/dashboard/src-tauri/icons/32x32.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square107x107Logo.png b/dashboard/src-tauri/icons/Square107x107Logo.png
deleted file mode 100644
index a36ae60..0000000
Binary files a/dashboard/src-tauri/icons/Square107x107Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square142x142Logo.png b/dashboard/src-tauri/icons/Square142x142Logo.png
deleted file mode 100644
index def6320..0000000
Binary files a/dashboard/src-tauri/icons/Square142x142Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square150x150Logo.png b/dashboard/src-tauri/icons/Square150x150Logo.png
deleted file mode 100644
index d005a80..0000000
Binary files a/dashboard/src-tauri/icons/Square150x150Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square284x284Logo.png b/dashboard/src-tauri/icons/Square284x284Logo.png
deleted file mode 100644
index 491498b..0000000
Binary files a/dashboard/src-tauri/icons/Square284x284Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square30x30Logo.png b/dashboard/src-tauri/icons/Square30x30Logo.png
deleted file mode 100644
index 176586c..0000000
Binary files a/dashboard/src-tauri/icons/Square30x30Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square310x310Logo.png b/dashboard/src-tauri/icons/Square310x310Logo.png
deleted file mode 100644
index 856fede..0000000
Binary files a/dashboard/src-tauri/icons/Square310x310Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square44x44Logo.png b/dashboard/src-tauri/icons/Square44x44Logo.png
deleted file mode 100644
index 5b71b88..0000000
Binary files a/dashboard/src-tauri/icons/Square44x44Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square71x71Logo.png b/dashboard/src-tauri/icons/Square71x71Logo.png
deleted file mode 100644
index d2a93a6..0000000
Binary files a/dashboard/src-tauri/icons/Square71x71Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/Square89x89Logo.png b/dashboard/src-tauri/icons/Square89x89Logo.png
deleted file mode 100644
index 914c611..0000000
Binary files a/dashboard/src-tauri/icons/Square89x89Logo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/StoreLogo.png b/dashboard/src-tauri/icons/StoreLogo.png
deleted file mode 100644
index 84a7e72..0000000
Binary files a/dashboard/src-tauri/icons/StoreLogo.png and /dev/null differ
diff --git a/dashboard/src-tauri/icons/icon.icns b/dashboard/src-tauri/icons/icon.icns
deleted file mode 100644
index df99c0e..0000000
Binary files a/dashboard/src-tauri/icons/icon.icns and /dev/null differ
diff --git a/dashboard/src-tauri/icons/icon.ico b/dashboard/src-tauri/icons/icon.ico
deleted file mode 100644
index bb39ae7..0000000
Binary files a/dashboard/src-tauri/icons/icon.ico and /dev/null differ
diff --git a/dashboard/src-tauri/icons/icon.png b/dashboard/src-tauri/icons/icon.png
deleted file mode 100644
index bc08c9c..0000000
Binary files a/dashboard/src-tauri/icons/icon.png and /dev/null differ
diff --git a/dashboard/src-tauri/src/lib.rs b/dashboard/src-tauri/src/lib.rs
deleted file mode 100644
index e89e1c4..0000000
--- a/dashboard/src-tauri/src/lib.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-#[cfg_attr(mobile, tauri::mobile_entry_point)]
-pub fn run() {
- tauri::Builder::default()
- .plugin(tauri_plugin_shell::init())
- .run(tauri::generate_context!())
- .expect("error while running tauri application");
-}
diff --git a/dashboard/src-tauri/src/main.rs b/dashboard/src-tauri/src/main.rs
deleted file mode 100644
index b72f801..0000000
--- a/dashboard/src-tauri/src/main.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-// Prevents additional console window on Windows in release, DO NOT REMOVE!!
-#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
-
-fn main() {
- grrdashboard_lib::run()
-}
diff --git a/dashboard/src-tauri/tauri.conf.json b/dashboard/src-tauri/tauri.conf.json
deleted file mode 100644
index f4e3f48..0000000
--- a/dashboard/src-tauri/tauri.conf.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "$schema": "https://schema.tauri.app/config/2.0.0-rc",
- "productName": "GRRDashboard",
- "version": "2025.0.0",
- "identifier": "org.team340.grrdashboard",
- "build": {
- "beforeDevCommand": "npm run dev",
- "devUrl": "http://localhost:5173",
- "beforeBuildCommand": "npm run build",
- "frontendDist": "../build"
- },
- "app": {
- "windows": [
- {
- "title": "GRRDashboard",
- "fullscreen": false,
- "width": 1920,
- "height": 800,
- "resizable": true
- }
- ],
- "security": {
- "csp": null
- }
- },
- "bundle": {
- "active": true,
- "targets": ["appimage", "deb", "dmg", "nsis", "rpm"],
- "shortDescription": "GRRDashboard",
- "icon": [
- "icons/32x32.png",
- "icons/128x128.png",
- "icons/128x128@2x.png",
- "icons/icon.icns",
- "icons/icon.ico"
- ]
- }
-}
diff --git a/dashboard/src/App.svelte b/dashboard/src/App.svelte
deleted file mode 100644
index 0c2fca4..0000000
--- a/dashboard/src/App.svelte
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
- {#if $NTConnected === NTSvelteClientState.CONNECTED || IGNORE_CONNECTION}
-
-
-
-
-
-
-
- {:else}
-
-
- {/if}
-
-
-
diff --git a/dashboard/src/assets/field22.png b/dashboard/src/assets/field22.png
deleted file mode 100644
index 506b2b1..0000000
Binary files a/dashboard/src/assets/field22.png and /dev/null differ
diff --git a/dashboard/src/assets/field23.png b/dashboard/src/assets/field23.png
deleted file mode 100644
index 88aa77b..0000000
Binary files a/dashboard/src/assets/field23.png and /dev/null differ
diff --git a/dashboard/src/assets/field24.png b/dashboard/src/assets/field24.png
deleted file mode 100644
index ca499cf..0000000
Binary files a/dashboard/src/assets/field24.png and /dev/null differ
diff --git a/dashboard/src/components/ConnectingDialog.svelte b/dashboard/src/components/ConnectingDialog.svelte
deleted file mode 100644
index 5bf8f7e..0000000
--- a/dashboard/src/components/ConnectingDialog.svelte
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
- Connecting...
- {$NTURI}
-
-
-
diff --git a/dashboard/src/components/NavBar.svelte b/dashboard/src/components/NavBar.svelte
deleted file mode 100644
index 4464848..0000000
--- a/dashboard/src/components/NavBar.svelte
+++ /dev/null
@@ -1,227 +0,0 @@
-
-
-
-
-
-
-
-
-
-
{($RobotVoltage ?? 0).toFixed(2)}
-
-
-
-
-
-
{new Date(Math.max($RobotMatchTime ?? 0, 0) * 1000).toISOString().substring(15, 19)}
-
-
-
-
-
-
-
- {#each tabs as tab}
-
- {/each}
-
-
-
-
-
-
-
-
-
-
-
-
Robot {$RobotEnabled ? `Enabled` : `Disabled`}
-
-
-
-
{$NTURI}
-
-
-
{($NTBitrate / 1000).toFixed(2)}kb/s | {$NTLatency.toFixed(2)}ms
-
-
-
-
-
-
diff --git a/dashboard/src/constants.ts b/dashboard/src/constants.ts
deleted file mode 100644
index cd44bd8..0000000
--- a/dashboard/src/constants.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-// Default NT URI.
-export const DEFAULT_URI = `10.3.40.2`;
-// export const DEFAULT_URI = `roborio-340-frc.local`;
-// export const DEFAULT_URI = `localhost`; // Use for simulation
-
-// Field size constants.
-// Set these values to the field's size in meters.
-export const FIELD_WIDTH = 16.541;
-export const FIELD_HEIGHT = 8.211;
-
-// Robot size in meters.
-export const ROBOT_SIZE = 0.8382;
diff --git a/dashboard/src/lib/MessagePack.ts b/dashboard/src/lib/MessagePack.ts
deleted file mode 100644
index 0d93475..0000000
--- a/dashboard/src/lib/MessagePack.ts
+++ /dev/null
@@ -1,628 +0,0 @@
-/**
- * MessagePack serializer and deserializer.
- * Adapted from https://github.com/gerth2/NetworkTablesClients/blob/main/nt4/js/src/msgpack/msgpack.js
- */
-export class MessagePack {
- private constructor() {}
-
- /**
- * Serialize data into a MessagePack byte array.
- * @param data The data to serialize.
- * @param options Serialization options.
- * @returns The serialized byte array.
- */
- public static serialize(
- data: any[],
- options?: { multiple?: boolean; typeHint?: string; invalidTypeReplacement?: (data: any) => any },
- ): Uint8Array {
- if (options && options.multiple && !Array.isArray(data)) {
- throw new TypeError(`Invalid argument type: Expected an Array to serialize multiple values.`);
- }
-
- let array = new Uint8Array(128);
-
- const th = options?.typeHint ? options.typeHint : ``;
- let floatBuffer: ArrayBuffer, floatView: DataView;
- let length = 0;
- const pow32 = 0x100000000;
-
- if (options?.multiple) {
- for (let i = 0; i < data.length; i++) append(data[i], false, th);
- } else {
- append(data, false, th);
- }
-
- return array.subarray(0, length);
-
- /**
- * Append data to the byte array.
- * @param data The data to append.
- * @param isReplacement If an invalid type should fall through the invalidTypeReplacement callback specified in options.
- * @param th Type hint for numbers.
- */
- function append(data: any, isReplacement?: boolean, th?: string): void {
- switch (typeof data) {
- case `undefined`:
- appendNull();
- break;
- case `boolean`:
- appendBoolean(data);
- break;
- case `number`:
- appendNumber(data, th);
- break;
- case `string`:
- appendString(data);
- break;
- case `object`:
- if (data === null) appendNull();
- else if (data instanceof Date) appendDate(data);
- else if (Array.isArray(data)) appendArray(data);
- else if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) appendBinaryArray(data);
- else if (
- data instanceof Int8Array ||
- data instanceof Int16Array ||
- data instanceof Uint16Array ||
- data instanceof Int32Array ||
- data instanceof Uint32Array ||
- data instanceof Float32Array ||
- data instanceof Float64Array
- )
- appendArray(data);
- else appendObject(data);
- break;
- default:
- if (!isReplacement && options && options.invalidTypeReplacement) {
- if (typeof options.invalidTypeReplacement === `function`) append(options.invalidTypeReplacement(data), true, th);
- else append(options.invalidTypeReplacement, true, th);
- } else {
- throw new Error(`Invalid argument type: The type '${typeof data}' cannot be serialized.`);
- }
- }
- }
-
- /**
- * Append a null byte to the array.
- */
- function appendNull(): void {
- appendByte(0xc0);
- }
-
- /**
- * Append a boolean to the array.
- * @param data The boolean to append.
- */
- function appendBoolean(data: boolean): void {
- appendByte(data ? 0xc3 : 0xc2);
- }
-
- /**
- * Append a number to the array.
- * @param data The number to append.
- * @param th Type hint. Should be int, double, or float.
- */
- function appendNumber(data: number, th?: string): void {
- const isInteger = th === `int` || (isFinite(data) && Math.floor(data) === data && th !== `double` && th !== `float`);
- if (isInteger) {
- if (data >= 0 && data <= 0x7f) {
- appendByte(data);
- } else if (data < 0 && data >= -0x20) {
- appendByte(data);
- } else if (data > 0 && data <= 0xff) {
- // uint8
- appendBytes([0xcc, data]);
- } else if (data >= -0x80 && data <= 0x7f) {
- // int8
- appendBytes([0xd0, data]);
- } else if (data > 0 && data <= 0xffff) {
- // uint16
- appendBytes([0xcd, data >>> 8, data]);
- } else if (data >= -0x8000 && data <= 0x7fff) {
- // int16
- appendBytes([0xd1, data >>> 8, data]);
- } else if (data > 0 && data <= 0xffffffff) {
- // uint32
- appendBytes([0xce, data >>> 24, data >>> 16, data >>> 8, data]);
- } else if (data >= -0x80000000 && data <= 0x7fffffff) {
- // int32
- appendBytes([0xd2, data >>> 24, data >>> 16, data >>> 8, data]);
- } else if (data > 0 && data <= 0xffffffffffffffff) {
- // uint64
- const hi = data / pow32;
- const lo = data % pow32;
- appendBytes([0xd3, hi >>> 24, hi >>> 16, hi >>> 8, hi, lo >>> 24, lo >>> 16, lo >>> 8, lo]);
- } else if (data >= -0x8000000000000000 && data <= 0x7fffffffffffffff) {
- // int64
- appendByte(0xd3);
- appendInt64(data);
- } else if (data < 0) {
- // below int64
- appendBytes([0xd3, 0x80, 0, 0, 0, 0, 0, 0, 0]);
- } else {
- // above uint64
- appendBytes([0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
- }
- } else {
- if (!floatView) {
- floatBuffer = new ArrayBuffer(8);
- floatView = new DataView(floatBuffer);
- }
- floatView.setFloat64(0, data);
- appendByte(0xcb);
- appendBytes(new Uint8Array(floatBuffer));
- }
- }
-
- /**
- * Append a string to the array.
- * @param data The string to append.
- */
- function appendString(data: string): void {
- const bytes = encodeUtf8(data);
- const length = bytes.length;
-
- if (length <= 0x1f) appendByte(0xa0 + length);
- else if (length <= 0xff) appendBytes([0xd9, length]);
- else if (length <= 0xffff) appendBytes([0xda, length >>> 8, length]);
- else appendBytes([0xdb, length >>> 24, length >>> 16, length >>> 8, length]);
-
- appendBytes(bytes);
- }
-
- /**
- * Append an array to the array.
- * @param data The array to append.
- */
- function appendArray(
- data: any[] | Int8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array,
- ): void {
- const length = data.length;
-
- if (length <= 0xf) appendByte(0x90 + length);
- else if (length <= 0xffff) appendBytes([0xdc, length >>> 8, length]);
- else appendBytes([0xdd, length >>> 24, length >>> 16, length >>> 8, length]);
-
- for (let index = 0; index < length; index++) {
- append(data[index]);
- }
- }
-
- /**
- * Append a binary array to the array.
- * @param data The binary array to append.
- */
- function appendBinaryArray(data: Uint8Array | Uint8ClampedArray): void {
- const length = data.length;
-
- if (length <= 0xf) appendBytes([0xc4, length]);
- else if (length <= 0xffff) appendBytes([0xc5, length >>> 8, length]);
- else appendBytes([0xc6, length >>> 24, length >>> 16, length >>> 8, length]);
-
- appendBytes(data);
- }
-
- /**
- * Append an object to the array.
- * @param data The object to append.
- */
- function appendObject(data: Record): void {
- let length = 0;
- for (const key in data) {
- if (data[key] !== undefined) {
- length++;
- }
- }
-
- if (length <= 0xf) appendByte(0x80 + length);
- else if (length <= 0xffff) appendBytes([0xde, length >>> 8, length]);
- else appendBytes([0xdf, length >>> 24, length >>> 16, length >>> 8, length]);
-
- for (const key in data) {
- const value = data[key];
- if (value !== undefined) {
- append(key);
- append(value);
- }
- }
- }
-
- /**
- * Append a date to the array.
- * @param data The date to append.
- */
- function appendDate(data: Date): void {
- const sec = data.getTime() / 1000;
- if (data.getMilliseconds() === 0 && sec >= 0 && sec < 0x100000000) {
- // 32 bit seconds.
- appendBytes([0xd6, 0xff, sec >>> 24, sec >>> 16, sec >>> 8, sec]);
- } else if (sec >= 0 && sec < 0x400000000) {
- // 30 bit nanoseconds, 34 bit seconds.
- const ns = data.getMilliseconds() * 1000000;
- appendBytes([
- 0xd7,
- 0xff,
- ns >>> 22,
- ns >>> 14,
- ns >>> 6,
- ((ns << 2) >>> 0) | (sec / pow32),
- sec >>> 24,
- sec >>> 16,
- sec >>> 8,
- sec,
- ]);
- } else {
- // 32 bit nanoseconds, 64 bit seconds, negative values allowed.
- const ns = data.getMilliseconds() * 1000000;
- appendBytes([0xc7, 12, 0xff, ns >>> 24, ns >>> 16, ns >>> 8, ns]);
- appendInt64(sec);
- }
- }
-
- /**
- * Append a byte to the array.
- * @param data The byte to append.
- */
- function appendByte(data: number): void {
- if (array.length < length + 1) {
- let newLength = array.length * 2;
- while (newLength < length + 1) newLength *= 2;
- const newArray = new Uint8Array(newLength);
- newArray.set(array);
- array = newArray;
- }
- array[length] = data;
- length++;
- }
-
- /**
- * Append multiple bytes to the array.
- * @param data The bytes to append.
- */
- function appendBytes(data: number[] | Uint8Array | Uint8ClampedArray): void {
- if (array.length < length + data.length) {
- let newLength = array.length * 2;
- while (newLength < length + data.length) newLength *= 2;
- const newArray = new Uint8Array(newLength);
- newArray.set(array);
- array = newArray;
- }
- array.set(data, length);
- length += data.length;
- }
-
- /**
- * Append an int64 to the array.
- * @param data The number to append.
- */
- function appendInt64(data: number): void {
- // Split 64-bit number into two 32-bit numbers because JavaScript only regards 32 bits for bitwise operations.
- let hi, lo;
- if (data >= 0) {
- // Same as uint64
- hi = data / pow32;
- lo = data % pow32;
- } else {
- // Split absolute value to high and low, then NOT and ADD(1) to restore negativity.
- data++;
- hi = Math.abs(data) / pow32;
- lo = Math.abs(data) % pow32;
- hi = ~hi;
- lo = ~lo;
- }
- appendBytes([hi >>> 24, hi >>> 16, hi >>> 8, hi, lo >>> 24, lo >>> 16, lo >>> 8, lo]);
- }
-
- // Encodes a string to UTF-8 bytes.
- // Based on: https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330
- function encodeUtf8(str: string): Uint8Array {
- let ascii = true;
- const length = str.length;
- for (let x = 0; x < length; x++) {
- if (str.charCodeAt(x) > 127) {
- ascii = false;
- break;
- }
- }
-
- let i = 0;
- const bytes = new Uint8Array(str.length * (ascii ? 1 : 4));
- for (let ci = 0; ci !== length; ci++) {
- let c = str.charCodeAt(ci);
- if (c < 128) {
- bytes[i++] = c;
- continue;
- }
- if (c < 2048) {
- bytes[i++] = (c >> 6) | 192;
- } else {
- if (c > 0xd7ff && c < 0xdc00) {
- if (++ci >= length) throw new Error(`UTF-8 encode: incomplete surrogate pair`);
- const c2 = str.charCodeAt(ci);
- if (c2 < 0xdc00 || c2 > 0xdfff)
- throw new Error(`UTF-8 encode: second surrogate character 0x${c2.toString(16)} at index ${ci} out of range`);
- c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff);
- bytes[i++] = (c >> 18) | 240;
- bytes[i++] = ((c >> 12) & 63) | 128;
- } else bytes[i++] = (c >> 12) | 224;
- bytes[i++] = ((c >> 6) & 63) | 128;
- }
- bytes[i++] = (c & 63) | 128;
- }
- return ascii ? bytes : bytes.subarray(0, i);
- }
- }
-
- /**
- * Deserialize a MessagePack byte array.
- * @param data The data to deserialize.
- * @param options Deserialization options.
- * @returns The deserialized array.
- */
- public static deserialize(data: ArrayBuffer | Uint8Array, options?: { multiple?: boolean }): any[] {
- const array = data instanceof ArrayBuffer || !(data instanceof Uint8Array) ? new Uint8Array(data) : data;
-
- if (!array.length) throw new Error(`Invalid argument: The byte array to deserialize is empty.`);
-
- const pow32 = 0x100000000;
- let pos = 0;
-
- let deserializedData: any[];
- if (options && options.multiple) {
- deserializedData = [];
- while (pos < array.length) {
- deserializedData.push(read());
- }
- } else {
- deserializedData = read();
- }
-
- return deserializedData;
-
- /**
- * Read the next byte in the unserialized data byte array.
- * @returns Deserialized data from the next byte.
- */
- function read(): any {
- const byte = array[pos++];
- if (byte >= 0x00 && byte <= 0x7f) return byte; // positive fixint
- if (byte >= 0x80 && byte <= 0x8f) return readMap(byte - 0x80); // fixmap
- if (byte >= 0x90 && byte <= 0x9f) return readArray(byte - 0x90); // fixarray
- if (byte >= 0xa0 && byte <= 0xbf) return readString(byte - 0xa0); // fixstr
- if (byte === 0xc0) return null; // null
- if (byte === 0xc1) throw new Error(`Invalid byte code 0xc1 found.`); // never used
- if (byte === 0xc2) return false; // false
- if (byte === 0xc3) return true; // true
- if (byte === 0xc4) return readBinary(-1, 1); // bin 8
- if (byte === 0xc5) return readBinary(-1, 2); // bin 16
- if (byte === 0xc6) return readBinary(-1, 4); // bin 32
- if (byte === 0xc7) return readExtension(-1, 1); // ext 8
- if (byte === 0xc8) return readExtension(-1, 2); // ext 16
- if (byte === 0xc9) return readExtension(-1, 4); // ext 32
- if (byte === 0xca) return readFloat(4); // float 32
- if (byte === 0xcb) return readFloat(8); // float 64
- if (byte === 0xcc) return readUInt(1); // uint 8
- if (byte === 0xcd) return readUInt(2); // uint 16
- if (byte === 0xce) return readUInt(4); // uint 32
- if (byte === 0xcf) return readUInt(8); // uint 64
- if (byte === 0xd0) return readInt(1); // int 8
- if (byte === 0xd1) return readInt(2); // int 16
- if (byte === 0xd2) return readInt(4); // int 32
- if (byte === 0xd3) return readInt(8); // int 64
- if (byte === 0xd4) return readExtension(1); // fixext 1
- if (byte === 0xd5) return readExtension(2); // fixext 2
- if (byte === 0xd6) return readExtension(4); // fixext 4
- if (byte === 0xd7) return readExtension(8); // fixext 8
- if (byte === 0xd8) return readExtension(16); // fixext 16
- if (byte === 0xd9) return readString(-1, 1); // str 8
- if (byte === 0xda) return readString(-1, 2); // str 16
- if (byte === 0xdb) return readString(-1, 4); // str 32
- if (byte === 0xdc) return readArray(-1, 2); // array 16
- if (byte === 0xdd) return readArray(-1, 4); // array 32
- if (byte === 0xde) return readMap(-1, 2); // map 16
- if (byte === 0xdf) return readMap(-1, 4); // map 32
- if (byte >= 0xe0 && byte <= 0xff) return byte - 256; // negative fixint
-
- console.debug(`MessagePack Array:`, array);
- throw new Error(
- `Invalid byte value '${byte}' at index ${pos - 1} in the MessagePack binary data (length ${
- array.length
- }): Expecting a range of 0 to 255. This is not a byte array.`,
- );
- }
-
- /**
- * Read an integer.
- * @param size The integer's size.
- * @returns The integer.
- */
- function readInt(size: number): number {
- let value = 0;
- let first = true;
- while (size-- > 0) {
- if (first) {
- const byte = array[pos++];
- value += byte & 0x7f;
- if (byte & 0x80) {
- value -= 0x80; // Treat most-significant bit as -2^i instead of 2^i
- }
- first = false;
- } else {
- value *= 256;
- value += array[pos++];
- }
- }
- return value;
- }
-
- /**
- * Read an unsigned integer.
- * @param size The integer's size.
- * @returns The integer.
- */
- function readUInt(size: number): number {
- let value = 0;
- while (size-- > 0) {
- value *= 256;
- value += array[pos++];
- }
- return value;
- }
-
- /**
- * Read a float.
- * @param size The float's size.
- * @returns The float.
- */
- function readFloat(size: number): number {
- const view = new DataView(array.buffer, pos + array.byteOffset, size);
- pos += size;
- if (size === 4) return view.getFloat32(0, false);
- if (size === 8) return view.getFloat64(0, false);
- else return 0;
- }
-
- /**
- * Read binary.
- * @param size The size of the binary value. -1 specifies to read an unsigned integer of lengthSize to determine the size.
- * @param lengthSize The size of an unsigned integer that specifies the length of the binary value.
- * @returns The binary value.
- */
- function readBinary(size: number, lengthSize?: number): Uint8Array {
- if (size < 0) {
- if (typeof lengthSize === `number`) size = readUInt(lengthSize);
- else throw new Error(`Argument mismatch`);
- }
- const data = array.subarray(pos, pos + size);
- pos += size;
- return data;
- }
-
- /**
- * Read a map.
- * @param size The size of the map. -1 specifies to read an unsigned integer of lengthSize to determine the size.
- * @param lengthSize The size of an unsigned integer that specifies the length of the map.
- * @returns The map.
- */
- function readMap(size: number, lengthSize?: number): Record {
- if (size < 0) {
- if (typeof lengthSize === `number`) size = readUInt(lengthSize);
- else throw new Error(`Argument mismatch`);
- }
- const data: Record = {};
- while (size-- > 0) {
- const key = read();
- data[key] = read();
- }
- return data;
- }
-
- /**
- * Read an array.
- * @param size The size of the array. -1 specifies to read an unsigned integer of lengthSize to determine the size.
- * @param lengthSize The size of an unsigned integer that specifies the length of the array.
- * @returns The array.
- */
- function readArray(size: number, lengthSize?: number): any[] {
- if (size < 0) {
- if (typeof lengthSize === `number`) size = readUInt(lengthSize);
- else throw new Error(`Argument mismatch`);
- }
- const data: any[] = [];
- while (size-- > 0) {
- data.push(read());
- }
- return data;
- }
-
- /**
- * Read a string.
- * @param size The size of the string. -1 specifies to read an unsigned integer of lengthSize to determine the size.
- * @param lengthSize The size of an unsigned integer that specifies the length of the string.
- * @returns The string.
- */
- function readString(size: number, lengthSize?: number): string {
- if (size < 0) {
- if (typeof lengthSize === `number`) size = readUInt(lengthSize);
- else throw new Error(`Argument mismatch`);
- }
- const start = pos;
- pos += size;
- return decodeUtf8(array, start, size);
- }
-
- /**
- * Read an extension.
- * @param size The size of the extension. -1 specifies to read an unsigned integer of lengthSize to determine the size.
- * @param lengthSize The size of an unsigned integer that specifies the length of the extension.
- * @returns The extension.
- */
- function readExtension(size: number, lengthSize?: number): Date | { type: number; data: Uint8Array } {
- if (size < 0) {
- if (typeof lengthSize === `number`) size = readUInt(lengthSize);
- else throw new Error(`Argument mismatch`);
- }
- const type = readUInt(1);
- const data = readBinary(size);
- switch (type) {
- case 255:
- if (data.length === 4) {
- const sec = ((data[0] << 24) >>> 0) + ((data[1] << 16) >>> 0) + ((data[2] << 8) >>> 0) + data[3];
- return new Date(sec * 1000);
- }
- if (data.length === 8) {
- const ns = ((data[0] << 22) >>> 0) + ((data[1] << 14) >>> 0) + ((data[2] << 6) >>> 0) + (data[3] >>> 2);
- const sec =
- (data[3] & 0x3) * pow32 + ((data[4] << 24) >>> 0) + ((data[5] << 16) >>> 0) + ((data[6] << 8) >>> 0) + data[7];
- return new Date(sec * 1000 + ns / 1000000);
- }
- if (data.length === 12) {
- const ns = ((data[0] << 24) >>> 0) + ((data[1] << 16) >>> 0) + ((data[2] << 8) >>> 0) + data[3];
- pos -= 8;
- const sec = readInt(8);
- return new Date(sec * 1000 + ns / 1000000);
- }
- throw new Error(`Invalid data length for a date value.`);
- }
- return {
- type: type,
- data: data,
- };
- }
-
- /**
- * Decodes a string from UTF-8 bytes.
- * From https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330
- * @param bytes The bytes to decode.
- * @param start The index of the byte to start with.
- * @param length The number of bytes to decode.
- * @returns The decoded string.
- */
- function decodeUtf8(bytes: Uint8Array, start: number, length: number): string {
- let i = start,
- str = ``;
- length += start;
- while (i < length) {
- let c = bytes[i++];
- if (c > 127) {
- if (c > 191 && c < 224) {
- if (i >= length) throw new Error(`UTF-8 decode: incomplete 2-byte sequence`);
- c = ((c & 31) << 6) | (bytes[i++] & 63);
- } else if (c > 223 && c < 240) {
- if (i + 1 >= length) throw new Error(`UTF-8 decode: incomplete 3-byte sequence`);
- c = ((c & 15) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63);
- } else if (c > 239 && c < 248) {
- if (i + 2 >= length) throw new Error(`UTF-8 decode: incomplete 4-byte sequence`);
- c = ((c & 7) << 18) | ((bytes[i++] & 63) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63);
- } else throw new Error(`UTF-8 decode: unknown multibyte start 0x ${c.toString(16)} at index ${i - 1}`);
- }
- if (c <= 0xffff) str += String.fromCharCode(c);
- else if (c <= 0x10ffff) {
- c -= 0x10000;
- str += String.fromCharCode((c >> 10) | 0xd800);
- str += String.fromCharCode((c & 0x3ff) | 0xdc00);
- } else throw new Error(`UTF-8 decode: code point 0x${c.toString(16)} exceeds UTF-16 reach`);
- }
- return str;
- }
- }
-}
diff --git a/dashboard/src/lib/NTSvelte.ts b/dashboard/src/lib/NTSvelte.ts
deleted file mode 100644
index 3b7056d..0000000
--- a/dashboard/src/lib/NTSvelte.ts
+++ /dev/null
@@ -1,1052 +0,0 @@
-import { get, writable, type Readable, type Writable, readonly, readable } from "svelte/store";
-import { MessagePack } from "./MessagePack";
-
-/**
- * Network tables type codes.
- */
-export const NTTypeCodes = {
- boolean: 0,
- double: 1,
- int: 2,
- float: 3,
- string: 4,
- json: 4,
- raw: 5,
- rpc: 5,
- msgpack: 5,
- protobuf: 5,
- "boolean[]": 16,
- "double[]": 17,
- "int[]": 18,
- "float[]": 19,
- "string[]": 20,
-} as const;
-
-/**
- * Types supported by Network Tables.
- */
-export type NTType = boolean | number | string | Uint8Array | boolean[] | number[] | string[];
-/**
- * A string representing a topic's type.
- */
-export type NTTypeString = keyof typeof NTTypeCodes;
-
-/**
- * A parsed text data frame.
- */
-export interface NTTextFrame {
- method: `publish` | `unpublish` | `setproperties` | `subscribe` | `unsubscribe` | `announce` | `unannounce` | `properties`;
- params: Record;
-}
-
-/**
- * An NT server topic.
- */
-export interface NTTopic {
- readonly name: string;
- readonly id: number;
- readonly type: NTTypeString;
- readonly pubuid?: number;
- readonly properties: Record>;
-}
-
-/**
- * NT Subscriber settings.
- */
-export interface NTSubscriberSettings {
- /**
- * If true, the server should send all value changes over the wire. If false, only the most recent value is sent (same as NT 3.0 behavior).
- * @default true
- */
- readonly all: boolean;
- /**
- * How frequently the server should send changes. The server may send more frequently than this (e.g. use a combined minimum period for all values) or apply a restricted range to this value.
- * Specified in milliseconds.
- * @default 100
- */
- readonly periodic: number;
- /**
- * If topic history should be saved.
- * @default false
- */
- readonly saveHistory: boolean;
-}
-
-/**
- * An NT subscription that can be subscribed to a single topic.
- */
-class NTSubscriber {
- private readonly _topicName: string;
- private readonly _settings: NTSubscriberSettings;
-
- private _history: Map = new Map();
- private _listeners: Set<(value: NTType | null) => void> = new Set();
- private _subuid: number | null = null;
- private _topicId: number | null = null;
- private _value: NTType | null = null;
- private _valueTimestamp: number | null = null;
-
- /**
- * Creates the subscriber.
- * @param topicName The name of the topic.
- * @param settings Subscriber settings.
- */
- public constructor(topicName: string, settings: NTSubscriberSettings) {
- this._topicName = topicName;
- this._settings = settings;
- }
-
- /**
- * Gets the subscribe message JSON and sets the subscriber's internal state to be subscribed.
- * @param subuid The subscription UID to use.
- */
- public subscribe(subuid: number): NTTextFrame {
- this._subuid = subuid;
- return {
- method: `subscribe`,
- params: {
- subuid,
- topics: [this._topicName],
- options: {
- all: this._settings.all,
- periodic: this._settings.periodic / 1000,
- },
- },
- };
- }
-
- /**
- * Gets the unsubscribe message JSON and sets the subscriber's internal state to be unsubscribed.
- * Additionally, this sets the subscriber's value to `null`, updates the listeners, and clears the subscriber's history.
- */
- public unsubscribe(): NTTextFrame {
- const subuid = this._subuid;
- this._subuid = null;
- this._topicId = null;
- if (this._value !== null) this.updateValue(null);
- this._history.clear();
- return {
- method: `unsubscribe`,
- params: { subuid },
- };
- }
-
- /**
- * This should be called when the subscription's topic is announced.
- * @param topicId The topic ID sent by the server.
- */
- public onAnnounce(topicId: number): void {
- this._topicId = topicId;
- }
-
- /**
- * This should be called when the subscription's topic is unannounced.
- * Resets the saved subscription UID and topic ID, sets the subscriber's value to `null`, and updates listeners.
- * Topic history is still saved.
- */
- public onUnannounce(): void {
- this._subuid = null;
- this._topicId = null;
- if (this._value !== null) this.updateValue(null);
- }
-
- /**
- * This should be called when the client disconnects.
- */
- public onDisconnect(): void {
- this._subuid = null;
- this._topicId = null;
- if (this._value !== null) this.updateValue(null);
- this._history.clear();
- }
-
- /**
- * Gets the subscriber's subscription UID sent by the client.
- * This returns `null` if the subscriber hasn't been assigned a UID (via {@link NTSubscriber.subscribe()}), or if it was unsubscribed (via {@link NTSubscriber.unsubscribe()}).
- */
- public getSubuid(): number | null {
- return this._subuid;
- }
-
- /**
- * Gets the subscriber's topic ID sent by the server.
- * This returns `null` if the topic hasn't received an `announce` message, or if the topic was unannounced.
- */
- public getTopicId(): number | null {
- return this._topicId;
- }
-
- /**
- * Updates the subscriber's value and updates its listeners with the new value.
- * Also saves the value to the subscriptions's history if enabled.
- * @param value The new value.
- * @param timestamp The value's timestamp in milliseconds.
- */
- public updateValue(value: NTType | null, timestamp: number | null = null): void {
- if (timestamp === null || timestamp > (this._valueTimestamp ?? 0)) {
- this._value = value;
- this._valueTimestamp = timestamp;
- this._listeners.forEach((listener) => listener(value));
- }
-
- if (this._settings.saveHistory && timestamp !== null) {
- this._history.set(timestamp, value);
- }
- }
-
- /**
- * Adds a listener to the subscriber and invokes it with the current value.
- * @param listener The listener to add.
- */
- public addListener(listener: (value: NTType | null) => void): void {
- this._listeners.add(listener);
- listener(this._value);
- }
-
- /**
- * Removes a listener.
- * @param listener The listener to remove.
- * @returns If the subscriber contains any other listeners.
- */
- public removeListener(listener: (value: NTType | null) => void): boolean {
- this._listeners.delete(listener);
- return this._listeners.size > 0;
- }
-
- /**
- * Gets the subscriber's history.
- */
- public getHistory(): Map {
- return this._history;
- }
-
- /**
- * Trims the subscriber's history back to a specified timestamp.
- * @param until Keep history as far back as this timestamp in milliseconds.
- */
- public trimHistory(until: number) {
- if (!this._settings.saveHistory) return;
- for (const [timestamp] of this._history) {
- if (timestamp < until) this._history.delete(timestamp);
- }
- }
-}
-
-/**
- * NT Publisher settings.
- */
-export interface NTPublisherSettings {
- /**
- * If true, the last set value will be periodically saved to persistent storage on the server and be restored during server startup. Topics with this property set to true will not be deleted by the server when the last publisher stops publishing.
- * @default false
- */
- readonly persistent: boolean;
- /**
- * Topics with this property set to true will not be deleted by the server when the last publisher stops publishing.
- * @default false
- */
- readonly retained: boolean;
- /**
- * If false, the server and clients will not store the value of the topic. This means that only value updates will be available for the topic.
- * @default true
- */
- readonly cached: boolean;
-}
-
-/**
- * An NT publisher.
- */
-class NTPublisher {
- private readonly _topicName: string;
- private readonly _type: NTTypeString;
- private readonly _properties: Record;
- private readonly _settings: NTPublisherSettings;
-
- private _listeners: Set<(value: NTType) => void> = new Set();
- private _receivedAck: boolean = false;
- private _pubuid: number | null = null;
- private _value: NTType;
-
- /**
- * Creates the publisher.
- * @param topicName The name of the topic.
- * @param type The topic's type.
- * @param initialValue The initial value of the publisher.
- * @param settings Publisher settings.
- * @param properties Custom topic properties.
- */
- public constructor(
- topicName: string,
- type: NTTypeString,
- initialValue: NTType,
- settings: NTPublisherSettings,
- properties?: Record,
- ) {
- this._topicName = topicName;
- this._type = type;
- this._value = initialValue;
- this._properties = properties ?? {};
- this._settings = settings;
- }
-
- /**
- * Gets the publish message JSON and sets the publisher's internal state to be published.
- * @param pubuid The publish UID to use.
- */
- public publish(pubuid: number): NTTextFrame {
- this._pubuid = pubuid;
- return {
- method: `publish`,
- params: {
- pubuid,
- name: this._topicName,
- type: this._type,
- properties: {
- ...this._properties,
- persistent: this._settings.persistent,
- retained: this._settings.retained,
- cached: this._settings.cached,
- },
- },
- };
- }
-
- /**
- * Gets the unpublish message JSON and sets the publisher's internal state to be unpublished.
- * This does not reset the saved value of the publisher.
- */
- public unpublish(): NTTextFrame {
- const pubuid = this._pubuid;
- this._pubuid = null;
- return {
- method: `unpublish`,
- params: { pubuid },
- };
- }
-
- /**
- * This should be called when the published topic is acknowledged by the server.
- */
- public onAck(): void {
- this._receivedAck = true;
- }
-
- /**
- * This should be called when the client disconnects.
- */
- public onDisconnect(): void {
- this._pubuid = null;
- this._receivedAck = false;
- }
-
- /**
- * Gets the publisher's publish UID sent by the client.
- * This returns `null` if the publisher hasn't been assigned a UID (via {@link NTPublisher.publish()}), or if it was unpublished (via {@link NTPublisher.unpublish()}).
- */
- public getPubuid(): number | null {
- return this._pubuid;
- }
-
- /**
- * Returns `true` if the published topic was acknowledged by the server.
- */
- public getAck(): boolean {
- return this._receivedAck;
- }
-
- /**
- * Updates the publisher's client-side value and updates its listeners with the new value.
- * @param value The new value.
- */
- public updateValue(value: NTType): void {
- this._value = value;
- this._listeners.forEach((listener) => listener(value));
- }
-
- /**
- * Adds a listener to the publisher and invokes it with the current value.
- * @param listener The listener to add.
- */
- public addListener(listener: (value: NTType) => void): void {
- this._listeners.add(listener);
- listener(this._value);
- }
-
- /**
- * Removes a listener.
- * @param listener The listener to remove.
- * @returns If the publisher contains any other listeners.
- */
- public removeListener(listener: (value: NTType) => void): boolean {
- this._listeners.delete(listener);
- return this._listeners.size > 0;
- }
-
- /**
- * Returns a binary frame representing the publisher's current value.
- * Returns `null` if a publisher UID has not been assigned to the publisher, or if it has a value of `null`.
- * @param serverTimeUs The current server time (in microseconds).
- */
- public getBinaryFrame(serverTimeUs: number): any[] | null {
- if (typeof this._pubuid !== `number` || this._value === null) return null;
- else return [this._pubuid, serverTimeUs, NTTypeCodes[this._type], this._value];
- }
-}
-
-/**
- * NT Client state.
- */
-export enum NTSvelteClientState {
- IDLE,
- DISCONNECTED,
- CONNECTING,
- CONNECTED,
-}
-
-/**
- * NT Client settings.
- */
-export interface NTSvelteClientSettings {
- /**
- * The name for the client. Used to identify the connection in robot logs.
- * @default `GRRDashboard`
- */
- readonly appName: string;
- /**
- * The TTL for a value in a topic's history in milliseconds. Has no effect when a subscriber's `saveHistory` is `false`.
- * Set to `0` to disable.
- * @default 0
- */
- readonly historyTTL: number;
- /**
- * Default subscriber settings.
- */
- readonly subscribers: NTSubscriberSettings;
- /**
- * Default publisher settings.
- */
- readonly publishers: NTPublisherSettings;
-}
-
-/**
- * A Network Tables 4.1 client with bindings for Svelte.
- */
-export class NTSvelteClient {
- private static readonly _HISTORY_SWEEP_PERIOD = 100;
- private static readonly _OPEN_TIMEOUT = 5000;
- private static readonly _PORT = 5810;
- private static readonly _RECONNECT_DELAY = 1500;
- private static readonly _RTT_PERIOD = 500;
- private static readonly _SERVER_AVAILABLE_TIMEOUT = 500;
- private static readonly _WS_PROTOCOL = `v4.1.networktables.first.wpi.edu`;
- private static readonly _WS_RTT_PROTOCOL = `rtt.networktables.first.wpi.edu`;
-
- private readonly _settings: NTSvelteClientSettings;
-
- private _bitrate: Writable = writable(0);
- private _latency: Writable = writable(0);
- private _openTimeout: number | null = null;
- private _serverTime: Writable = writable();
- private _state: Writable = writable(NTSvelteClientState.IDLE);
- private _uidNonce: number = 0;
- private _uri: Writable;
-
- private _ws: WebSocket | null = null;
- private _wsRtt: WebSocket | null = null;
- private _wsListeners = {
- open: [() => this._onOpen(false), () => this._onOpen(true)],
- close: [(event: CloseEvent) => this._onClose(event, false), (event: CloseEvent) => this._onClose(event, true)],
- error: [() => this._onError(false), () => this._onError(true)],
- message: [(event: MessageEvent) => this._onMessage(event, false), (event: MessageEvent) => this._onMessage(event, true)],
- };
-
- private _aliveAck = false;
- private _serverOffsetUs: number = 0;
- private _usage: number = 0;
-
- private _topicsOnlySubuid: number | null = null;
- private _publishers: Map = new Map();
- private _subscribers: Map = new Map();
- private _serverTopics: Writable