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 @@ - - -
- -
- - 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> = writable(new Map()); - - /** - * Creates the NT Svelte Client. - * Use {@link NTSvelteClient.connect()} to start the connection. - * @param uri The URI to connect to. - * @param settings Client settings. - */ - public constructor(uri: string, settings?: Partial) { - this._uri = writable(uri); - this._settings = { - appName: settings?.appName ?? `GRRDashboard`, - historyTTL: settings?.historyTTL ?? 0, - subscribers: { - all: settings?.subscribers?.all ?? true, - periodic: settings?.subscribers?.periodic ?? 100, - saveHistory: settings?.subscribers?.saveHistory ?? false, - }, - publishers: { - persistent: settings?.publishers?.persistent ?? false, - retained: settings?.publishers?.retained ?? false, - cached: settings?.publishers?.cached ?? true, - }, - }; - - setInterval(() => { - if (get(this._state) === NTSvelteClientState.CONNECTED) { - if (!this._aliveAck) return this._restart(`Timed out`); - this._aliveAck = false; - this._sendTimestamp(); - this._bitrate.set(this._usage / (NTSvelteClient._RTT_PERIOD / 1000)); - this._usage = 0; - } - }, NTSvelteClient._RTT_PERIOD); - - if (this._settings.historyTTL > 0) { - setInterval(() => { - this._subscribers.forEach((subscriber) => { - subscriber.trimHistory(Date.now() - this._settings.historyTTL); - }); - }, NTSvelteClient._HISTORY_SWEEP_PERIOD); - } - } - - /** - * Sets a new URI and restarts the client. - * @param newURI The new URI. - */ - public setURI(newURI: string) { - this._uri.set(newURI); - this._restart(`URI Changed`); - } - - /** - * Connect to the NT server. - */ - public connect(): void { - if (get(this._state) === NTSvelteClientState.IDLE) { - this._state.set(NTSvelteClientState.DISCONNECTED); - this._connect(); - } - } - - /** - * Gets the URI in use by the client, as a readable. - */ - public uriReadable(): Readable { - return readonly(this._uri); - } - - /** - * Gets the state of the client, as a readable. - */ - public stateReadable(): Readable { - return readonly(this._state); - } - - /** - * Gets the bitrate between the client and server (bits/s), as a readable. - */ - public bitrateReadable(): Readable { - return readonly(this._bitrate); - } - - /** - * Gets the latency between the client and server (ms), as a readable. - */ - public latencyReadable(): Readable { - return readonly(this._latency); - } - - /** - * Gets the server time in milliseconds, as a readable. - */ - public serverTimeReadable(): Readable { - return readonly(this._serverTime); - } - - /** - * Gets a map of topics announced by the server, as a readable. - */ - public topicsReadable(): Readable> { - return readonly(this._serverTopics); - } - - /** - * Subscribes to all topics with the `topicsonly` flag set to `true`. - * Useful for observing all topics available on the server without receiving value changes. - */ - public subAllNoValues(): void { - if (this._topicsOnlySubuid === null || this._topicsOnlySubuid < 0) { - this._topicsOnlySubuid = this._genUID(); - const sent = this._sendJSON({ - method: `subscribe`, - params: { - subuid: this._topicsOnlySubuid, - topics: [``], - options: { - periodic: this._settings.subscribers.periodic / 1000, - topicsonly: true, - prefix: true, - }, - }, - }); - if (!sent) this._topicsOnlySubuid = -1; - } - } - - /** - * Removes a subscription created from {@link NTSvelteClient.subAllNoValues()}. - */ - public unsubAllNoValues(): void { - if (this._topicsOnlySubuid !== null && this._topicsOnlySubuid >= 0) { - const subuid = this._topicsOnlySubuid; - this._topicsOnlySubuid = null; - this._sendJSON({ method: `unsubscribe`, params: { subuid } }); - } - } - - /** - * Subscribes to a NT topic. - * @param key The key of the topic to subscribe to. - * @param defaultValue The default value, returned when the topic is unavailable. - * @param settings Subscription settings. Ignored if a subscription with the same key is currently active. - * @returns The value of the topic as a readable. - */ - public subscribe(key: string, defaultValue: T, settings?: Partial): Readable { - return readable(defaultValue, (set) => { - const listener = (value: any) => set(value ?? defaultValue); - - let sub = this._subscribers.get(key); - if (sub) { - sub.addListener(listener); - } else { - sub = new NTSubscriber(key, { - all: settings?.all ?? this._settings.subscribers.all, - periodic: settings?.periodic ?? this._settings.subscribers.periodic, - saveHistory: settings?.saveHistory ?? this._settings.subscribers.saveHistory, - }); - sub.addListener(listener); - sub.updateValue(null); - const existingTopic = get(this._serverTopics).get(key); - if (existingTopic) sub.onAnnounce(existingTopic.id); - this._subscribers.set(key, sub); - this._sendJSON(sub.subscribe(this._genUID())); - } - - return () => { - if (!sub) return; - const others = sub.removeListener(listener); - if (!others) { - this._sendJSON(sub.unsubscribe()); - this._subscribers.delete(key); - } - }; - }); - } - - /** - * Publishes a NT topic. - * To modify the published topic's properties, - * @param key The key of the topic to publish. - * @param type The topic's type. - * @param initialValue The topic's initial value. If the topic is already published, its value is updated to this. - * @param settings Publish settings. Ignored if a publisher with the same key is currently active. - * @param properties Custom topic properties. Ignored if a publisher with the same key is currently active. - * @returns The publisher as a writable. Its value is updated if other writables assigned to the same publisher update their value. - */ - public publish( - key: string, - type: NTTypeString, - initialValue: T, - settings?: Partial, - properties?: Record, - ): Writable { - const store = writable(initialValue, (set) => { - const listener = (value: any) => set(value); - - let pub = this._publishers.get(key); - if (pub) { - pub.addListener(listener); - pub.updateValue(initialValue); - } else { - pub = new NTPublisher( - key, - type, - initialValue, - { - persistent: settings?.persistent ?? this._settings.publishers.persistent, - retained: settings?.retained ?? this._settings.publishers.retained, - cached: settings?.cached ?? this._settings.publishers.cached, - }, - properties, - ); - pub.addListener(listener); - pub.updateValue(initialValue); - if (typeof get(this._serverTopics).get(key)?.pubuid === `number`) pub.onAck(); - this._publishers.set(key, pub); - this._sendJSON(pub.publish(this._genUID())); - } - - if (pub.getAck()) { - const frame = pub.getBinaryFrame(this._getServerTimeUs()); - if (Array.isArray(frame)) this._sendMessagePack(frame, false); - } - - return () => { - if (!pub) return; - const others = pub.removeListener(listener); - if (!others) { - this._sendJSON(pub.unpublish()); - this._publishers.delete(key); - } - }; - }); - - return { - subscribe: store.subscribe, - update: store.update, - set: (value: T) => { - const pub = this._publishers.get(key); - pub?.updateValue(value); - if (pub?.getAck()) { - const frame = pub.getBinaryFrame(this._getServerTimeUs()); - if (Array.isArray(frame)) this._sendMessagePack(frame, false); - } - }, - }; - } - - /** - * Gets the URL used for checking if the server is alive. - */ - private get _aliveUrl(): string { - return encodeURI(`http://${get(this._uri)}:${NTSvelteClient._PORT}`); - } - - /** - * Gets the server's WebSocket URL. - */ - private get _wsUrl(): string { - return encodeURI(`ws://${get(this._uri)}:${NTSvelteClient._PORT}/nt/${this._settings.appName}`); - } - - /** - * Gets the server's current time (in microseconds). - */ - private _getServerTimeUs(): number { - return Date.now() * 1000 + this._serverOffsetUs; - } - - /** - * Sends the client's timestamp via the RTT connection. - */ - private _sendTimestamp() { - this._sendMessagePack([-1, 0, NTTypeCodes.int, Date.now() * 1000], true); - } - - /** - * Sends a message pack packet. - * @param data The data to send. - * @param rtt If the packet should be sent to the RTT connection. - * @returns If the data was sent. - */ - private _sendMessagePack(data: any[], rtt: boolean): boolean { - const ws = rtt ? this._wsRtt : this._ws; - if (ws?.readyState === WebSocket.OPEN) { - const msg = MessagePack.serialize(data); - ws.send(msg); - this._usage += msg.byteLength * 8; - return true; - } else { - return false; - } - } - - /** - * Sends a JSON message. - * Only used for the primary WS connection, not the RTT connection. - * @param method The method value to send. - * @param params JSON parameters. - * @returns If the data was sent. - */ - private _sendJSON(data: NTTextFrame): boolean { - if (this._ws?.readyState === WebSocket.OPEN) { - const msg = JSON.stringify([data]); - this._ws.send(msg); - this._usage += this._getStringSize(msg); - return true; - } else { - return false; - } - } - - /** - * Connect to the server. - * @param reconnecting If the client is reconnecting. - */ - private async _connect(reconnecting: boolean = false): Promise { - if (get(this._state) !== NTSvelteClientState.DISCONNECTED) return; - if (this._openTimeout !== null) clearTimeout(this._openTimeout); - this._state.set(NTSvelteClientState.CONNECTING); - - if (reconnecting) { - console.log(`[NTSvelte] Waiting to reconnect...`); - await new Promise((resolve) => setTimeout(resolve, NTSvelteClient._RECONNECT_DELAY)); - } - - console.log(`[NTSvelte] Connecting...`); - - let aliveResult: Response | null = null; - let length = `0.00`; - - try { - const start = Date.now(); - aliveResult = await fetch(this._aliveUrl, { signal: AbortSignal.timeout(NTSvelteClient._SERVER_AVAILABLE_TIMEOUT) }); - length = ((Date.now() - start) / 1000).toFixed(2); - } catch (_) {} - - if (!aliveResult?.ok) { - this._state.set(NTSvelteClientState.DISCONNECTED); - console.warn(`[NTSvelte] Server not responding after ${length}s while attempting to connect`); - return this._connect(true); - } - - console.log(`[NTSvelte] Server responded after ${length}s`); - - this._openTimeout = setTimeout(() => this._restart(`Timed out while connecting`), NTSvelteClient._OPEN_TIMEOUT); - - this._ws = new WebSocket(this._wsUrl, [NTSvelteClient._WS_PROTOCOL]); - this._ws.binaryType = `arraybuffer`; - - this._wsRtt = new WebSocket(this._wsUrl, [NTSvelteClient._WS_RTT_PROTOCOL]); - this._wsRtt.binaryType = `arraybuffer`; - - for (const [type, listener] of Object.entries(this._wsListeners)) { - this._ws.addEventListener(type, listener[0] as any); - this._wsRtt.addEventListener(type, listener[1] as any); - } - } - - /** - * Closes and restart the connection. - * @param reason The reason for restarting. - */ - private _restart(reason: string): void { - if (this._openTimeout !== null) clearTimeout(this._openTimeout); - this._openTimeout = null; - - for (const [type, listener] of Object.entries(this._wsListeners)) { - this._ws?.removeEventListener(type, listener[0] as any); - this._wsRtt?.removeEventListener(type, listener[1] as any); - } - - this._ws?.close(); - this._ws = null; - this._wsRtt?.close(); - this._wsRtt = null; - - this._bitrate.set(0); - this._latency.set(0); - this._serverTime.set(0); - - this._aliveAck = false; - this._serverOffsetUs = 0; - this._usage = 0; - - if (this._topicsOnlySubuid !== null) this._topicsOnlySubuid = -1; - this._subscribers.forEach((subscriber) => subscriber.onDisconnect()); - this._publishers.forEach((publisher) => publisher.onDisconnect()); - this._serverTopics.set(new Map()); - - this._state.set(NTSvelteClientState.DISCONNECTED); - console.warn(`[NTSvelte] Restarting: ${reason}`); - this._connect(true); - } - - private _onOpen(rtt: boolean): void { - console.log(`[NTSvelte] ${rtt ? `RTT ` : ``}WebSocket Connected`); - - if (this._ws?.readyState === WebSocket.OPEN && this._wsRtt?.readyState === WebSocket.OPEN) { - if (this._openTimeout !== null) clearTimeout(this._openTimeout); - this._openTimeout = null; - this._aliveAck = true; - this._state.set(NTSvelteClientState.CONNECTED); - console.log(`[NTSvelte] Connection complete`); - - this._sendTimestamp(); - - if (this._topicsOnlySubuid !== null) this.subAllNoValues(); - - this._subscribers.forEach((subscriber) => { - if (subscriber.getSubuid() === null) this._sendJSON(subscriber.subscribe(this._genUID())); - }); - - this._publishers.forEach((publisher) => { - if (publisher.getPubuid() === null) this._sendJSON(publisher.publish(this._genUID())); - }); - } - } - - private _onClose(event: CloseEvent, rtt: boolean): void { - this._restart( - `${rtt ? `RTT ` : ``}WebSocket Closed with code ${event.code}: ${event.reason?.length ? event.reason : `Unknown reason`}`, - ); - } - - private _onError(rtt: boolean): void { - this._restart(`${rtt ? `RTT ` : ``}Websocket encountered an error`); - } - - private _onMessage(event: MessageEvent, rtt: boolean): void { - const now = Date.now() * 1000; - - if (typeof event.data === `string`) { - if (rtt) return console.warn(`[NTSvelte] Received unexpected text frame from RTT connection`); - - this._usage += this._getStringSize(event.data); - let data: NTTextFrame[] = JSON.parse(event.data); - if (!Array.isArray(data)) return console.warn(`[NTSvelte] Received malformed text frame, data is not an array`); - - data.forEach((frame, i) => { - if (typeof frame !== `object`) - return console.warn(`[NTSvelte] Received malformed text frame, frame at index ${i} is not an object`); - - const method = frame.method; - const params = frame.params; - if (typeof method !== `string`) - return console.warn( - `[NTSvelte] Received malformed text frame, frame at index ${i} has invalid method of type ${typeof method}`, - ); - if (typeof params !== `object`) - return console.warn( - `[NTSvelte] Received malformed text frame, frame at index ${i} has invalid params of type ${typeof params}`, - ); - - switch (method) { - case `announce`: - this._serverTopics.set( - new Map( - get(this._serverTopics).set( - params.name, - this._deepFreeze({ - id: params.id, - name: params.name, - type: params.type, - pubuid: params.pubuid, - properties: params.properties ?? {}, - }), - ), - ), - ); - - this._subscribers.get(params.name)?.onAnnounce(params.id); - if (typeof params.pubuid === `number`) { - const pub = this._publishers.get(params.name); - if (pub?.getPubuid() === params.pubuid && !pub.getAck()) { - pub.onAck(); - const frame = pub.getBinaryFrame(this._getServerTimeUs()); - if (Array.isArray(frame)) this._sendMessagePack(frame, false); - } - } - break; - case `unannounce`: - const topics = get(this._serverTopics); - topics.delete(params.name); - this._serverTopics.set(new Map(topics)); - - this._subscribers.get(params.name)?.onUnannounce(); - break; - case `properties`: - const topic = get(this._serverTopics).get(params.name); - if (!topic) return console.warn(`[NTSvelte] Ignoring properties update, topic was not announced`); - - const properties = structuredClone(topic.properties); - for (const [k, v] of Object.entries((params.update ?? {}) as NTTopic[`properties`])) { - if (v !== null) properties[k] = v; - else delete properties[k]; - } - - this._serverTopics.set( - new Map(get(this._serverTopics).set(params.name, this._deepFreeze({ ...topic, properties }))), - ); - break; - default: - console.warn(`[NTSvelte] Received unknown method "${method}" in text frame at index ${i}`); - break; - } - }); - } else if (event.data instanceof ArrayBuffer) { - this._usage += event.data.byteLength * 8; - - MessagePack.deserialize(event.data, { multiple: true }).forEach((unpacked: any[]) => { - const topicId: number = unpacked[0]; - const timestamp: number = unpacked[1]; - const type: (typeof NTTypeCodes)[NTTypeString] = unpacked[2]; - const value: NTType = unpacked[3]; - - if (typeof topicId !== `number`) - return console.warn(`[NTSvelte] Received malformed binary frame, topic ID is not a number`); - if (typeof timestamp !== `number`) - return console.warn(`[NTSvelte] Received malformed binary frame, timestamp is not a number`); - if (typeof type !== `number`) return console.warn(`[NTSvelte] Received malformed binary frame, type index is not a number`); - - if (topicId === -1) { - this._aliveAck = true; - const usLatency = (now - (value as number)) / 2; - const serverTimeUs = timestamp + usLatency; - this._latency.set(usLatency / 1000); - this._serverTime.set(serverTimeUs / 1000); - this._serverOffsetUs = serverTimeUs - now; - return; - } - - if (rtt) return console.warn(`[NTSvelte] Received unexpected binary frame with topic ID ${topicId} on RTT connection`); - - if (topicId >= 0) { - let found = false; - this._subscribers.forEach((subscriber) => { - if (subscriber.getTopicId() === topicId) { - subscriber.updateValue(value, timestamp / 1000); - found = true; - } - }); - if (!found) console.warn(`[NTSvelte] Received binary frame with unknown topic ID ${topicId}`); - } else { - return console.warn(`[NTSvelte] Received malformed binary frame, topic ID "${topicId}" out of range`); - } - }); - } else { - console.warn(`[NTSvelte] Received unknown message type`); - } - } - - /** - * Generates a new UID. - */ - private _genUID(): number { - return this._uidNonce++; - } - - /** - * Gets the number of bits in a string. - * @param str The string. - */ - private _getStringSize(str: string): number { - return (encodeURI(str).split(/%..|./).length - 1) * 8; - } - - /** - * Deep freezes an object in place. - * Supports circular references. - * @param obj The object to freeze. - * @returns The object. - */ - private _deepFreeze>(obj: T): T { - Object.freeze(obj); - Object.values(obj) - .filter((o) => typeof o === `object` && !Object.isFrozen(o)) - .forEach((o) => this._deepFreeze(o)); - return obj; - } -} diff --git a/dashboard/src/main.ts b/dashboard/src/main.ts deleted file mode 100644 index 3bce418..0000000 --- a/dashboard/src/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -import App from "./App.svelte"; - -const app = new App({ target: document.getElementById(`app`) as any }); - -export default app; diff --git a/dashboard/src/ntStores.ts b/dashboard/src/ntStores.ts deleted file mode 100644 index f78df61..0000000 --- a/dashboard/src/ntStores.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DEFAULT_URI } from "./constants"; -import { NTSvelteClient } from "./lib/NTSvelte"; - -export const nt = new NTSvelteClient(DEFAULT_URI, { appName: `GRRDashboard` }); - -export const NTURI = nt.uriReadable(); -export const NTConnected = nt.stateReadable(); -export const NTBitrate = nt.bitrateReadable(); -export const NTLatency = nt.latencyReadable(); - -export const RobotEnabled = nt.subscribe(`/GRRDashboard/Robot/enabled`, false); -export const RobotMatchTime = nt.subscribe(`/GRRDashboard/Robot/matchTime`, 0); -export const RobotBlueAlliance = nt.subscribe(`/GRRDashboard/Robot/blueAlliance`, true); -export const RobotVoltage = nt.subscribe(`/GRRDashboard/Robot/voltage`, 0); - -export const AutosActive = nt.subscribe(`/GRRDashboard/Autos/active`, ``); -export const AutosOptions = nt.subscribe(`/GRRDashboard/Autos/options`, []); -export const AutosSelected = nt.publish(`/GRRDashboard/Autos/selected`, `string`, ``); - -nt.connect(); diff --git a/dashboard/src/tabs/AutoSelection.svelte b/dashboard/src/tabs/AutoSelection.svelte deleted file mode 100644 index f925563..0000000 --- a/dashboard/src/tabs/AutoSelection.svelte +++ /dev/null @@ -1,197 +0,0 @@ - - -
-
- {#if $options.length} - {#each $options as { id, label, points }, i} - - - {/each} - {:else} - No available auto options - {/if} -
-
- - diff --git a/dashboard/src/tabs/DriverView.svelte b/dashboard/src/tabs/DriverView.svelte deleted file mode 100644 index ce20372..0000000 --- a/dashboard/src/tabs/DriverView.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - -
-

Nothing to see here!

-
- - diff --git a/dashboard/src/vite-env.d.ts b/dashboard/src/vite-env.d.ts deleted file mode 100644 index 4078e74..0000000 --- a/dashboard/src/vite-env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/dashboard/svelte.config.js b/dashboard/svelte.config.js deleted file mode 100644 index 9a3adfb..0000000 --- a/dashboard/svelte.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; - -export default { - // Consult https://svelte.dev/docs#compile-time-svelte-preprocess - // for more information about preprocessors - preprocess: vitePreprocess(), -}; diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json deleted file mode 100644 index 3b6833a..0000000 --- a/dashboard/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "@tsconfig/svelte/tsconfig.json", - "compilerOptions": { - "strict": true, - "target": "ESNext", - "useDefineForClassFields": true, - "module": "ESNext", - "checkJs": true, - "isolatedModules": true, - }, - "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], - "references": [{ "path": "./tsconfig.node.json" }], -} diff --git a/dashboard/tsconfig.node.json b/dashboard/tsconfig.node.json deleted file mode 100644 index d1f4c67..0000000 --- a/dashboard/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler" - }, - "include": ["vite.config.ts"] -} diff --git a/dashboard/vite.config.ts b/dashboard/vite.config.ts deleted file mode 100644 index eeb0bb2..0000000 --- a/dashboard/vite.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { defineConfig } from "vite"; -import { svelte } from "@sveltejs/vite-plugin-svelte"; - -// https://vitejs.dev/config/ -export default defineConfig({ - build: { - minify: !process.env.TAURI_DEBUG ? "esbuild" : false, - outDir: `./build`, - sourcemap: !!process.env.TAURI_DEBUG, - target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13", - }, - clearScreen: false, - envPrefix: ["VITE_", "TAURI_"], - plugins: [svelte()], - server: { strictPort: true }, -}); diff --git a/src/main/java/org/team340/lib/logging/java/AtomicBooleanLogger.java b/src/main/java/org/team340/lib/logging/java/AtomicBooleanLogger.java new file mode 100644 index 0000000..8c3b173 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/java/AtomicBooleanLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.java; + +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; +import java.util.concurrent.atomic.AtomicBoolean; + +@CustomLoggerFor(AtomicBoolean.class) +public class AtomicBooleanLogger extends ClassSpecificLogger { + + public AtomicBooleanLogger() { + super(AtomicBoolean.class); + } + + @Override + public void update(EpilogueBackend backend, AtomicBoolean atomicBoolean) { + backend.log("", atomicBoolean.get()); + } +} diff --git a/src/main/java/org/team340/lib/logging/java/AtomicIntegerLogger.java b/src/main/java/org/team340/lib/logging/java/AtomicIntegerLogger.java new file mode 100644 index 0000000..a9a11e8 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/java/AtomicIntegerLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.java; + +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; +import java.util.concurrent.atomic.AtomicInteger; + +@CustomLoggerFor(AtomicInteger.class) +public class AtomicIntegerLogger extends ClassSpecificLogger { + + public AtomicIntegerLogger() { + super(AtomicInteger.class); + } + + @Override + public void update(EpilogueBackend backend, AtomicInteger atomicInteger) { + backend.log("", atomicInteger.get()); + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/CANdiLogger.java b/src/main/java/org/team340/lib/logging/phoenix/CANdiLogger.java new file mode 100644 index 0000000..b5fd4d0 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/CANdiLogger.java @@ -0,0 +1,53 @@ +package org.team340.lib.logging.phoenix; + +import com.ctre.phoenix6.BaseStatusSignal; +import com.ctre.phoenix6.hardware.CANdi; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +@CustomLoggerFor(CANdi.class) +public class CANdiLogger extends ClassSpecificLogger { + + private static final Map> cache = new HashMap<>(); + + public CANdiLogger() { + super(CANdi.class); + } + + @Override + public void update(EpilogueBackend backend, CANdi candi) { + cache + .computeIfAbsent(candi, key -> { + var pwm1Position = candi.getPWM1Position(false); + var pwm1Velocity = candi.getPWM1Velocity(false); + var pwm2Position = candi.getPWM2Position(false); + var pwm2Velocity = candi.getPWM2Velocity(false); + var s1Closed = candi.getS1Closed(false); + var s2Closed = candi.getS2Closed(false); + + BaseStatusSignal[] signals = { + pwm1Position, + pwm1Velocity, + pwm2Position, + pwm2Velocity, + s1Closed, + s2Closed + }; + + return b -> { + BaseStatusSignal.refreshAll(signals); + b.log("pwm1Position", pwm1Position.getValueAsDouble()); + b.log("pwm1Velocity", pwm1Velocity.getValueAsDouble()); + b.log("pwm2Position", pwm2Position.getValueAsDouble()); + b.log("pwm2Velocity", pwm2Velocity.getValueAsDouble()); + b.log("s1Closed", s1Closed.getValue()); + b.log("s2Closed", s2Closed.getValue()); + }; + }) + .accept(backend); + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/Pigeon2Logger.java b/src/main/java/org/team340/lib/logging/phoenix/Pigeon2Logger.java index e4451ea..0494da3 100644 --- a/src/main/java/org/team340/lib/logging/phoenix/Pigeon2Logger.java +++ b/src/main/java/org/team340/lib/logging/phoenix/Pigeon2Logger.java @@ -23,38 +23,15 @@ public void update(EpilogueBackend backend, Pigeon2 pigeon2) { cache .computeIfAbsent(pigeon2, key -> { var temperature = pigeon2.getTemperature(false); - var accelerationX = pigeon2.getAccelerationX(false); - var accelerationY = pigeon2.getAccelerationY(false); - var accelerationZ = pigeon2.getAccelerationZ(false); - var velocityX = pigeon2.getAngularVelocityXWorld(false); - var velocityY = pigeon2.getAngularVelocityYWorld(false); - var velocityZ = pigeon2.getAngularVelocityZWorld(false); var yaw = pigeon2.getYaw(false); var pitch = pigeon2.getPitch(false); var roll = pigeon2.getRoll(false); - BaseStatusSignal[] signals = { - temperature, - accelerationX, - accelerationY, - accelerationZ, - velocityX, - velocityY, - velocityZ, - yaw, - pitch, - roll - }; + BaseStatusSignal[] signals = { temperature, yaw, pitch, roll }; return b -> { BaseStatusSignal.refreshAll(signals); b.log("temperature", temperature.getValueAsDouble()); - b.log("accelerationX", accelerationX.getValueAsDouble()); - b.log("accelerationY", accelerationY.getValueAsDouble()); - b.log("accelerationZ", accelerationZ.getValueAsDouble()); - b.log("velocityX", velocityX.getValueAsDouble()); - b.log("velocityY", velocityY.getValueAsDouble()); - b.log("velocityZ", velocityZ.getValueAsDouble()); b.log("yaw", yaw.getValueAsDouble()); b.log("pitch", pitch.getValueAsDouble()); b.log("roll", roll.getValueAsDouble()); diff --git a/src/main/java/org/team340/lib/logging/phoenix/TalonFXLogger.java b/src/main/java/org/team340/lib/logging/phoenix/TalonFXLogger.java index 3e94483..b9e744a 100644 --- a/src/main/java/org/team340/lib/logging/phoenix/TalonFXLogger.java +++ b/src/main/java/org/team340/lib/logging/phoenix/TalonFXLogger.java @@ -22,44 +22,29 @@ public TalonFXLogger() { public void update(EpilogueBackend backend, TalonFX talonFX) { cache .computeIfAbsent(talonFX, key -> { - var acceleration = talonFX.getAcceleration(false); - var closedLoopError = talonFX.getClosedLoopError(false); - var closedLoopOutput = talonFX.getClosedLoopOutput(false); var closedLoopReference = talonFX.getClosedLoopReference(false); var deviceTemp = talonFX.getDeviceTemp(false); var motorVoltage = talonFX.getMotorVoltage(false); var position = talonFX.getPosition(false); var statorCurrent = talonFX.getStatorCurrent(false); - var supplyCurrent = talonFX.getSupplyCurrent(false); - var supplyVoltage = talonFX.getSupplyVoltage(false); var velocity = talonFX.getVelocity(false); BaseStatusSignal[] signals = { - acceleration, - closedLoopError, - closedLoopOutput, closedLoopReference, deviceTemp, motorVoltage, position, statorCurrent, - supplyCurrent, - supplyVoltage, velocity }; return b -> { BaseStatusSignal.refreshAll(signals); - b.log("acceleration", acceleration.getValueAsDouble()); - b.log("closedLoopError", closedLoopError.getValueAsDouble()); - b.log("closedLoopOutput", closedLoopOutput.getValueAsDouble()); b.log("closedLoopReference", closedLoopReference.getValueAsDouble()); b.log("deviceTemp", deviceTemp.getValueAsDouble()); b.log("motorVoltage", motorVoltage.getValueAsDouble()); b.log("position", position.getValueAsDouble()); b.log("statorCurrent", statorCurrent.getValueAsDouble()); - b.log("supplyCurrent", supplyCurrent.getValueAsDouble()); - b.log("supplyVoltage", supplyVoltage.getValueAsDouble()); b.log("velocity", velocity.getValueAsDouble()); }; }) diff --git a/src/main/java/org/team340/lib/logging/phoenix/TalonFXSLogger.java b/src/main/java/org/team340/lib/logging/phoenix/TalonFXSLogger.java new file mode 100644 index 0000000..b6d3a1f --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/TalonFXSLogger.java @@ -0,0 +1,53 @@ +package org.team340.lib.logging.phoenix; + +import com.ctre.phoenix6.BaseStatusSignal; +import com.ctre.phoenix6.hardware.TalonFXS; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +@CustomLoggerFor(TalonFXS.class) +public class TalonFXSLogger extends ClassSpecificLogger { + + private static final Map> cache = new HashMap<>(); + + public TalonFXSLogger() { + super(TalonFXS.class); + } + + @Override + public void update(EpilogueBackend backend, TalonFXS talonFXS) { + cache + .computeIfAbsent(talonFXS, key -> { + var closedLoopReference = talonFXS.getClosedLoopReference(false); + var deviceTemp = talonFXS.getDeviceTemp(false); + var motorVoltage = talonFXS.getMotorVoltage(false); + var position = talonFXS.getPosition(false); + var statorCurrent = talonFXS.getStatorCurrent(false); + var velocity = talonFXS.getVelocity(false); + + BaseStatusSignal[] signals = { + closedLoopReference, + deviceTemp, + motorVoltage, + position, + statorCurrent, + velocity + }; + + return b -> { + BaseStatusSignal.refreshAll(signals); + b.log("closedLoopReference", closedLoopReference.getValueAsDouble()); + b.log("deviceTemp", deviceTemp.getValueAsDouble()); + b.log("motorVoltage", motorVoltage.getValueAsDouble()); + b.log("position", position.getValueAsDouble()); + b.log("statorCurrent", statorCurrent.getValueAsDouble()); + b.log("velocity", velocity.getValueAsDouble()); + }; + }) + .accept(backend); + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/CoastOutLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/CoastOutLogger.java new file mode 100644 index 0000000..1953e57 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/CoastOutLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.CoastOut; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(CoastOut.class) +public class CoastOutLogger extends ClassSpecificLogger { + + public CoastOutLogger() { + super(CoastOut.class); + } + + @Override + public void update(EpilogueBackend backend, CoastOut control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/DutyCycleOutLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/DutyCycleOutLogger.java new file mode 100644 index 0000000..eda3331 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/DutyCycleOutLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.DutyCycleOut; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(DutyCycleOut.class) +public class DutyCycleOutLogger extends ClassSpecificLogger { + + public DutyCycleOutLogger() { + super(DutyCycleOut.class); + } + + @Override + public void update(EpilogueBackend backend, DutyCycleOut control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/DynamicMotionMagicVoltageLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/DynamicMotionMagicVoltageLogger.java new file mode 100644 index 0000000..375e056 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/DynamicMotionMagicVoltageLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.DynamicMotionMagicVoltage; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(DynamicMotionMagicVoltage.class) +public class DynamicMotionMagicVoltageLogger extends ClassSpecificLogger { + + public DynamicMotionMagicVoltageLogger() { + super(DynamicMotionMagicVoltage.class); + } + + @Override + public void update(EpilogueBackend backend, DynamicMotionMagicVoltage control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/FollowerLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/FollowerLogger.java new file mode 100644 index 0000000..01700ac --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/FollowerLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.Follower; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(Follower.class) +public class FollowerLogger extends ClassSpecificLogger { + + public FollowerLogger() { + super(Follower.class); + } + + @Override + public void update(EpilogueBackend backend, Follower control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/MotionMagicVoltageLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/MotionMagicVoltageLogger.java new file mode 100644 index 0000000..25077bd --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/MotionMagicVoltageLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.MotionMagicVoltage; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(MotionMagicVoltage.class) +public class MotionMagicVoltageLogger extends ClassSpecificLogger { + + public MotionMagicVoltageLogger() { + super(MotionMagicVoltage.class); + } + + @Override + public void update(EpilogueBackend backend, MotionMagicVoltage control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/PositionVoltageLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/PositionVoltageLogger.java new file mode 100644 index 0000000..02da2e5 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/PositionVoltageLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.PositionVoltage; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(PositionVoltage.class) +public class PositionVoltageLogger extends ClassSpecificLogger { + + public PositionVoltageLogger() { + super(PositionVoltage.class); + } + + @Override + public void update(EpilogueBackend backend, PositionVoltage control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/StrictFollowerLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/StrictFollowerLogger.java new file mode 100644 index 0000000..23a8120 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/StrictFollowerLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.StrictFollower; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(StrictFollower.class) +public class StrictFollowerLogger extends ClassSpecificLogger { + + public StrictFollowerLogger() { + super(StrictFollower.class); + } + + @Override + public void update(EpilogueBackend backend, StrictFollower control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/TorqueCurrentFOCLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/TorqueCurrentFOCLogger.java new file mode 100644 index 0000000..c7ee95d --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/TorqueCurrentFOCLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.TorqueCurrentFOC; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(TorqueCurrentFOC.class) +public class TorqueCurrentFOCLogger extends ClassSpecificLogger { + + public TorqueCurrentFOCLogger() { + super(TorqueCurrentFOC.class); + } + + @Override + public void update(EpilogueBackend backend, TorqueCurrentFOC control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/VelocityTorqueCurrentFOCLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/VelocityTorqueCurrentFOCLogger.java new file mode 100644 index 0000000..ce3bb6e --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/VelocityTorqueCurrentFOCLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.VelocityTorqueCurrentFOC; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(VelocityTorqueCurrentFOC.class) +public class VelocityTorqueCurrentFOCLogger extends ClassSpecificLogger { + + public VelocityTorqueCurrentFOCLogger() { + super(VelocityTorqueCurrentFOC.class); + } + + @Override + public void update(EpilogueBackend backend, VelocityTorqueCurrentFOC control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/VelocityVoltageLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/VelocityVoltageLogger.java new file mode 100644 index 0000000..f28f974 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/VelocityVoltageLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.VelocityVoltage; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(VelocityVoltage.class) +public class VelocityVoltageLogger extends ClassSpecificLogger { + + public VelocityVoltageLogger() { + super(VelocityVoltage.class); + } + + @Override + public void update(EpilogueBackend backend, VelocityVoltage control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/phoenix/controls/VoltageOutLogger.java b/src/main/java/org/team340/lib/logging/phoenix/controls/VoltageOutLogger.java new file mode 100644 index 0000000..d79a619 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/phoenix/controls/VoltageOutLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.phoenix.controls; + +import com.ctre.phoenix6.controls.VoltageOut; +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; + +@CustomLoggerFor(VoltageOut.class) +public class VoltageOutLogger extends ClassSpecificLogger { + + public VoltageOutLogger() { + super(VoltageOut.class); + } + + @Override + public void update(EpilogueBackend backend, VoltageOut control) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/reduxlib/CanandgyroLogger.java b/src/main/java/org/team340/lib/logging/reduxlib/CanandgyroLogger.java index 1e3ff76..1ac5c48 100644 --- a/src/main/java/org/team340/lib/logging/reduxlib/CanandgyroLogger.java +++ b/src/main/java/org/team340/lib/logging/reduxlib/CanandgyroLogger.java @@ -15,12 +15,6 @@ public CanandgyroLogger() { @Override public void update(EpilogueBackend backend, Canandgyro canandgyro) { - backend.log("accelerationX", canandgyro.getAccelerationX()); - backend.log("accelerationY", canandgyro.getAccelerationY()); - backend.log("accelerationZ", canandgyro.getAccelerationZ()); - backend.log("velocityX", canandgyro.getAngularVelocityRoll()); - backend.log("velocityY", canandgyro.getAngularVelocityPitch()); - backend.log("velocityZ", canandgyro.getAngularVelocityYaw()); backend.log("yaw", canandgyro.getYaw()); backend.log("pitch", canandgyro.getPitch()); backend.log("roll", canandgyro.getRoll()); diff --git a/src/main/java/org/team340/lib/logging/revlib/SparkFlexLogger.java b/src/main/java/org/team340/lib/logging/revlib/SparkFlexLogger.java index 6dbd899..2003595 100644 --- a/src/main/java/org/team340/lib/logging/revlib/SparkFlexLogger.java +++ b/src/main/java/org/team340/lib/logging/revlib/SparkFlexLogger.java @@ -17,11 +17,9 @@ public SparkFlexLogger() { @Override public void update(EpilogueBackend backend, SparkFlex sparkFlex) { double appliedOutput = sparkFlex.getAppliedOutput(); - double busVoltage = sparkFlex.getBusVoltage(); backend.log("appliedOutput", appliedOutput); - backend.log("appliedVoltage", appliedOutput * busVoltage); - backend.log("busVoltage", busVoltage); + backend.log("appliedVoltage", appliedOutput * sparkFlex.getBusVoltage()); backend.log("motorTemperature", sparkFlex.getMotorTemperature()); backend.log("outputCurrent", sparkFlex.getOutputCurrent()); backend.log("position", sparkFlex.getEncoder().getPosition()); diff --git a/src/main/java/org/team340/lib/logging/revlib/SparkMaxLogger.java b/src/main/java/org/team340/lib/logging/revlib/SparkMaxLogger.java index 23a7944..80f1518 100644 --- a/src/main/java/org/team340/lib/logging/revlib/SparkMaxLogger.java +++ b/src/main/java/org/team340/lib/logging/revlib/SparkMaxLogger.java @@ -17,11 +17,9 @@ public SparkMaxLogger() { @Override public void update(EpilogueBackend backend, SparkMax sparkMax) { double appliedOutput = sparkMax.getAppliedOutput(); - double busVoltage = sparkMax.getBusVoltage(); backend.log("appliedOutput", appliedOutput); - backend.log("appliedVoltage", appliedOutput * busVoltage); - backend.log("busVoltage", busVoltage); + backend.log("appliedVoltage", appliedOutput * sparkMax.getBusVoltage()); backend.log("motorTemperature", sparkMax.getMotorTemperature()); backend.log("outputCurrent", sparkMax.getOutputCurrent()); backend.log("position", sparkMax.getEncoder().getPosition()); diff --git a/src/main/java/org/team340/lib/logging/wpilibj/ADIS16470Logger.java b/src/main/java/org/team340/lib/logging/wpilibj/ADIS16470Logger.java index 51dc4f8..e98eb78 100644 --- a/src/main/java/org/team340/lib/logging/wpilibj/ADIS16470Logger.java +++ b/src/main/java/org/team340/lib/logging/wpilibj/ADIS16470Logger.java @@ -15,12 +15,6 @@ public ADIS16470Logger() { @Override public void update(EpilogueBackend backend, ADIS16470_IMU adis16470) { - backend.log("accelerationX", adis16470.getAccelX()); - backend.log("accelerationY", adis16470.getAccelY()); - backend.log("accelerationZ", adis16470.getAccelZ()); - backend.log("velocityX", adis16470.getRate(IMUAxis.kX)); - backend.log("velocityY", adis16470.getRate(IMUAxis.kY)); - backend.log("velocityZ", adis16470.getRate(IMUAxis.kZ)); backend.log("yaw", adis16470.getAngle(IMUAxis.kYaw)); backend.log("pitch", adis16470.getAngle(IMUAxis.kPitch)); backend.log("roll", adis16470.getAngle(IMUAxis.kRoll)); diff --git a/src/main/java/org/team340/lib/logging/wpilibj/AddressableLEDBufferLogger.java b/src/main/java/org/team340/lib/logging/wpilibj/AddressableLEDBufferLogger.java new file mode 100644 index 0000000..e854ea1 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/wpilibj/AddressableLEDBufferLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.wpilibj; + +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; +import edu.wpi.first.wpilibj.AddressableLEDBuffer; + +@CustomLoggerFor(AddressableLEDBuffer.class) +public class AddressableLEDBufferLogger extends ClassSpecificLogger { + + public AddressableLEDBufferLogger() { + super(AddressableLEDBuffer.class); + } + + @Override + public void update(EpilogueBackend backend, AddressableLEDBuffer buffer) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/wpilibj/AddressableLEDLogger.java b/src/main/java/org/team340/lib/logging/wpilibj/AddressableLEDLogger.java new file mode 100644 index 0000000..ab294dd --- /dev/null +++ b/src/main/java/org/team340/lib/logging/wpilibj/AddressableLEDLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.wpilibj; + +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; +import edu.wpi.first.wpilibj.AddressableLED; + +@CustomLoggerFor(AddressableLED.class) +public class AddressableLEDLogger extends ClassSpecificLogger { + + public AddressableLEDLogger() { + super(AddressableLED.class); + } + + @Override + public void update(EpilogueBackend backend, AddressableLED addressableLED) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/logging/wpimath/DebouncerLogger.java b/src/main/java/org/team340/lib/logging/wpimath/DebouncerLogger.java new file mode 100644 index 0000000..5383818 --- /dev/null +++ b/src/main/java/org/team340/lib/logging/wpimath/DebouncerLogger.java @@ -0,0 +1,19 @@ +package org.team340.lib.logging.wpimath; + +import edu.wpi.first.epilogue.CustomLoggerFor; +import edu.wpi.first.epilogue.logging.ClassSpecificLogger; +import edu.wpi.first.epilogue.logging.EpilogueBackend; +import edu.wpi.first.math.filter.Debouncer; + +@CustomLoggerFor(Debouncer.class) +public class DebouncerLogger extends ClassSpecificLogger { + + public DebouncerLogger() { + super(Debouncer.class); + } + + @Override + public void update(EpilogueBackend backend, Debouncer debouncer) { + // No-op + } +} diff --git a/src/main/java/org/team340/lib/swerve/Perspective.java b/src/main/java/org/team340/lib/swerve/Perspective.java index cca87a5..0f3d281 100644 --- a/src/main/java/org/team340/lib/swerve/Perspective.java +++ b/src/main/java/org/team340/lib/swerve/Perspective.java @@ -15,17 +15,17 @@ public enum Perspective { */ kOperator { @Override - ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return (Alliance.isBlue() ? kBlue : kRed).toRobotSpeeds(speeds, robotAngle); } @Override - ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return (Alliance.isBlue() ? kBlue : kRed).toPerspectiveSpeeds(speeds, robotAngle); } @Override - Rotation2d getTareRotation() { + public Rotation2d getTareRotation() { return (Alliance.isBlue() ? kBlue : kRed).getTareRotation(); } }, @@ -36,17 +36,17 @@ Rotation2d getTareRotation() { */ kBlue { @Override - ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return ChassisSpeeds.fromFieldRelativeSpeeds(speeds, robotAngle); } @Override - ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return ChassisSpeeds.fromRobotRelativeSpeeds(speeds, robotAngle); } @Override - Rotation2d getTareRotation() { + public Rotation2d getTareRotation() { return Rotation2d.kZero; } }, @@ -57,17 +57,17 @@ Rotation2d getTareRotation() { */ kRed { @Override - ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return ChassisSpeeds.fromFieldRelativeSpeeds(speeds, robotAngle.rotateBy(Rotation2d.kPi)); } @Override - ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return ChassisSpeeds.fromRobotRelativeSpeeds(speeds, robotAngle.rotateBy(Rotation2d.kPi)); } @Override - Rotation2d getTareRotation() { + public Rotation2d getTareRotation() { return Rotation2d.kPi; } }, @@ -78,17 +78,17 @@ Rotation2d getTareRotation() { */ kRobot { @Override - ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return speeds; } @Override - ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { + public ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle) { return speeds; } @Override - Rotation2d getTareRotation() { + public Rotation2d getTareRotation() { // Will no-op downstream. return null; } @@ -99,18 +99,18 @@ Rotation2d getTareRotation() { * @param speeds The perspective relative speeds to convert. * @param robotAngle The blue origin relative angle of the robot. */ - abstract ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle); + public abstract ChassisSpeeds toRobotSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle); /** * Converts robot relative speeds to the perspective relative speeds. * @param speeds The robot relative speeds to convert. * @param robotAngle The blue origin relative angle of the robot. */ - abstract ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle); + public abstract ChassisSpeeds toPerspectiveSpeeds(ChassisSpeeds speeds, Rotation2d robotAngle); /** * Gets the rotation to apply as the new zero when * taring the robot's rotation to the perspective. */ - abstract Rotation2d getTareRotation(); + public abstract Rotation2d getTareRotation(); } diff --git a/src/main/java/org/team340/lib/swerve/SwerveAPI.java b/src/main/java/org/team340/lib/swerve/SwerveAPI.java index 95d24da..c124fa6 100644 --- a/src/main/java/org/team340/lib/swerve/SwerveAPI.java +++ b/src/main/java/org/team340/lib/swerve/SwerveAPI.java @@ -2,15 +2,18 @@ import com.ctre.phoenix6.BaseStatusSignal; import com.ctre.phoenix6.StatusCode; +import edu.wpi.first.epilogue.Logged; +import edu.wpi.first.epilogue.Logged.Strategy; import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.Matrix; import edu.wpi.first.math.VecBuilder; -import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator; +import edu.wpi.first.math.estimator.PoseEstimator; import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.math.geometry.Translation2d; import edu.wpi.first.math.kinematics.ChassisSpeeds; import edu.wpi.first.math.kinematics.SwerveDriveKinematics; +import edu.wpi.first.math.kinematics.SwerveDriveOdometry; import edu.wpi.first.math.kinematics.SwerveModulePosition; import edu.wpi.first.math.kinematics.SwerveModuleState; import edu.wpi.first.math.numbers.N1; @@ -45,7 +48,8 @@ public class SwerveAPI implements AutoCloseable { private final SwerveModuleState[] lockedStates; private final SwerveDriveKinematics kinematics; - private final SwerveDrivePoseEstimator poseEstimator; + private final SwerveDriveOdometry odometry; + private final PoseEstimator poseEstimator; private final Lock odometryMutex = new ReentrantLock(); private final SwerveOdometryThread odometryThread; @@ -79,11 +83,10 @@ public SwerveAPI(SwerveConfig config) { state = new SwerveState(modules); kinematics = new SwerveDriveKinematics(moduleLocations); - poseEstimator = new SwerveDrivePoseEstimator( + odometry = new SwerveDriveOdometry(kinematics, Rotation2d.kZero, state.modules.positions); + poseEstimator = new PoseEstimator<>( kinematics, - Rotation2d.kZero, - state.modules.positions, - Pose2d.kZero, + odometry, config.odometryStdDevs, VecBuilder.fill(0.0, 0.0, 0.0) ); @@ -101,9 +104,11 @@ public void refresh() { odometryMutex.lock(); try { odometryThread.run(true); - state.odometry.timesync = odometryThread.timesync; - state.odometry.successes = odometryThread.successes; - state.odometry.failures = odometryThread.failures; + state.timestamp = Timer.getFPGATimestamp(); + + state.odometryThread.timesync = odometryThread.timesync; + state.odometryThread.successes = odometryThread.successes; + state.odometryThread.failures = odometryThread.failures; odometryThread.successes = 0; odometryThread.failures = 0; @@ -113,16 +118,26 @@ public void refresh() { Math2.copyInto(modules[i].getState(), state.modules.states[i]); } - state.multiturnYaw = imu.getMultiturnYaw(); state.pose = poseEstimator.getEstimatedPosition(); + + state.imu.yawMeasurements.clear(); + state.imu.yawMeasurements.addAll(odometryThread.yawMeasurements); + odometryThread.yawMeasurements.clear(); + + if (!state.imu.yawMeasurements.isEmpty()) { + state.imu.yaw = state.imu.yawMeasurements.get(state.imu.yawMeasurements.size() - 1).yaw(); + } } finally { odometryMutex.unlock(); } Math2.copyInto(kinematics.toChassisSpeeds(state.modules.states), state.speeds); state.velocity = Math.hypot(state.speeds.vxMetersPerSecond, state.speeds.vyMetersPerSecond); - state.pitch = imu.getPitch(); - state.roll = imu.getRoll(); + state.translation = state.pose.getTranslation(); + state.rotation = state.pose.getRotation(); + + state.imu.pitch = imu.getPitch(); + state.imu.roll = imu.getRoll(); imuSimHook.accept(state.speeds); } @@ -134,7 +149,7 @@ public void refresh() { public void addVisionMeasurements(VisionMeasurement... measurements) { odometryMutex.lock(); try { - for (var measurement : measurements) { + for (VisionMeasurement measurement : measurements) { if (measurement.stdDevs == null) { poseEstimator.addVisionMeasurement(measurement.visionPose, measurement.timestamp); } else { @@ -165,6 +180,7 @@ public void resetPose(Pose2d pose) { try { poseEstimator.resetPosition(odometryThread.lastYaw, state.modules.positions, pose); state.pose = poseEstimator.getEstimatedPosition(); + odometryThread.yawMeasurements.clear(); } finally { odometryMutex.unlock(); } @@ -179,10 +195,12 @@ public void resetPose(Pose2d pose) { public void tareRotation(Perspective perspective) { var rotation = perspective.getTareRotation(); if (rotation == null) return; + odometryMutex.lock(); try { poseEstimator.resetRotation(rotation); state.pose = poseEstimator.getEstimatedPosition(); + odometryThread.yawMeasurements.clear(); } finally { odometryMutex.unlock(); } @@ -207,30 +225,29 @@ public void applyDriverInput( boolean discretize, boolean ratelimit ) { - angular = MathUtil.applyDeadband(angular, config.driverAngularVelDeadband); - double angularVel = - config.driverAngularVel * Math.copySign(Math.pow(angular, config.driverAngularVelExp), angular); - applyDriverXY(x, y, angularVel, perspective, discretize, ratelimit); + applyAssistedDriverInput(x, y, angular, new ChassisSpeeds(), perspective, discretize, ratelimit); } /** - * Drives using inputs from the driver's controller, with a specified angular velocity in radians/second. - * Use this method as opposed to {@link SwerveAPI#applyDriverInput(double, double, double)} if the driver's - * input is desired only for x/y movement, and not heading. Use cases include locking the robot's heading by - * passing the output of a PID controller as the angular velocity. The {@code x} and {@code y} parameters - * expect the controller's NED (north-east-down) convention, and will automatically convert to WPILib's - * typical NWU (north-west-up) convention when applying chassis speeds. + * Drives using inputs from the driver's controller, with a specified additional chassis velocity. + * The {@code x} and {@code y} parameters expect the controller's NED (north-east-down) convention, + * and will automatically convert to WPILib's typical NWU (north-west-up) convention when applying + * chassis speeds. * @param x The X value of the driver's joystick, from {@code [-1.0, 1.0]}. * @param y The Y value of the driver's joystick, from {@code [-1.0, 1.0]}. - * @param angularVel The CCW+ angular velocity to apply, in radians/second. + * @param angular The CCW+ angular speed to apply, from {@code [-1.0, 1.0]}. + * @param assist Additional velocities to apply. Note that these speeds are + * relative to the provided perspective, and are still restricted + * by the ratelimiter if it is active. * @param perspective The forward perspective for the chassis speeds. * @param discretize If the generated speeds should be discretized. * @param ratelimit If the robot's acceleration should be constrained. */ - public void applyDriverXY( + public void applyAssistedDriverInput( double x, double y, - double angularVel, + double angular, + ChassisSpeeds assist, Perspective perspective, boolean discretize, boolean ratelimit @@ -245,9 +262,19 @@ public void applyDriverXY( x *= k; y *= k; + angular = MathUtil.applyDeadband(angular, config.driverAngularVelDeadband); double xyMult = config.driverVel * Math.pow(Math.hypot(x, y), config.driverVelExp - 1.0); - applySpeeds(new ChassisSpeeds(-y * xyMult, -x * xyMult, angularVel), perspective, discretize, ratelimit); + double angularVel = + config.driverAngularVel * Math.copySign(Math.pow(angular, config.driverAngularVelExp), angular); + + ChassisSpeeds speeds = new ChassisSpeeds( + (-y * xyMult) + assist.vxMetersPerSecond, + (-x * xyMult) + assist.vyMetersPerSecond, + angularVel + assist.omegaRadiansPerSecond + ); + + applySpeeds(speeds, perspective, discretize, ratelimit); } /** @@ -424,7 +451,8 @@ public void close() { * Represents a measurement from vision to apply to the pose estimator. * @see {@link SwerveDrivePoseEstimator#addVisionMeasurement(Pose2d, double, Matrix)}. */ - public final record VisionMeasurement(Pose2d visionPose, double timestamp, Matrix stdDevs) { + @Logged(strategy = Strategy.OPT_IN) + public static final record VisionMeasurement(Pose2d visionPose, double timestamp, Matrix stdDevs) { /** * Represents a measurement from vision to apply to the pose estimator. * @see {@link SwerveDrivePoseEstimator#addVisionMeasurement(Pose2d, double)}. @@ -434,6 +462,11 @@ public VisionMeasurement(Pose2d visionPose, double timestamp) { } } + /** + * Contains a yaw measurement alongside the timestamp of the measurement, in seconds. + */ + public static final record TimestampedYaw(Rotation2d yaw, double timestamp) {} + /** * Manages swerve odometry. Will run asynchronously at the configured odometry update * period, unless the configured period is the same or more than the main robot loop @@ -443,6 +476,7 @@ public VisionMeasurement(Pose2d visionPose, double timestamp) { */ private final class SwerveOdometryThread implements AutoCloseable { + public final List yawMeasurements = new ArrayList<>(); public Rotation2d lastYaw = Rotation2d.kZero; public boolean timesync = false; public int successes = 0; @@ -488,7 +522,7 @@ public void run(boolean sync) { StatusCode phoenixStatus = StatusCode.OK; if (!sync) { if (timesync) { - phoenixStatus = BaseStatusSignal.waitForAll(config.odometryPeriod * 2.0, signals); + phoenixStatus = BaseStatusSignal.waitForAll(config.period, signals); } else { Sleep.seconds(Math.max(0.0, config.odometryPeriod - (Timer.getFPGATimestamp() - lastTime))); lastTime = Timer.getFPGATimestamp(); @@ -500,6 +534,7 @@ public void run(boolean sync) { if (!timesync && signals.length > 0) phoenixStatus = BaseStatusSignal.refreshAll(signals); lastYaw = imu.getYaw(); + double yawTimestamp = Timer.getFPGATimestamp(); boolean readError = !phoenixStatus.isOK() || imu.readError(); for (var module : modules) { @@ -512,6 +547,7 @@ public void run(boolean sync) { } poseEstimator.update(lastYaw, positionCache); + yawMeasurements.add(new TimestampedYaw(odometry.getPoseMeters().getRotation(), yawTimestamp)); successes++; } finally { odometryMutex.unlock(); diff --git a/src/main/java/org/team340/lib/swerve/SwerveAPILogger.java b/src/main/java/org/team340/lib/swerve/SwerveAPILogger.java index 6c9483b..c1fe4c5 100644 --- a/src/main/java/org/team340/lib/swerve/SwerveAPILogger.java +++ b/src/main/java/org/team340/lib/swerve/SwerveAPILogger.java @@ -21,10 +21,12 @@ public SwerveAPILogger() { public void update(EpilogueBackend backend, SwerveAPI api) { logState(backend.getNested("state"), api.state); + var hardware = backend.getNested("hardware"); var errorHandler = Epilogue.getConfig().errorHandler; - api.imu.log(backend.getNested("imu"), errorHandler); + + api.imu.log(hardware.getNested("imu"), errorHandler); for (var m : api.modules) { - var module = backend.getNested(m.getName()); + var module = hardware.getNested(m.getName()); m.moveMotor.log(module.getNested("moveMotor"), errorHandler); m.turnMotor.log(module.getNested("turnMotor"), errorHandler); m.encoder.log(module.getNested("encoder"), errorHandler); @@ -33,22 +35,22 @@ public void update(EpilogueBackend backend, SwerveAPI api) { private void logState(EpilogueBackend backend, SwerveState state) { backend.log("speeds", state.speeds, ChassisSpeeds.struct); - backend.log("targetSpeeds", state.targetSpeeds, ChassisSpeeds.struct); backend.log("velocity", state.velocity); - backend.log("multiturnYaw", state.multiturnYaw); - backend.log("pitch", state.pitch, Rotation2d.struct); - backend.log("roll", state.roll, Rotation2d.struct); backend.log("pose", state.pose, Pose2d.struct); var modules = backend.getNested("modules"); modules.log("positions", state.modules.positions, SwerveModulePosition.struct); modules.log("states", state.modules.states, SwerveModuleState.struct); modules.log("lastTarget", state.modules.lastTarget, SwerveModuleState.struct); - modules.log("nextTarget", state.modules.nextTarget, SwerveModuleState.struct); - var odometry = backend.getNested("odometry"); - odometry.log("timesync", state.odometry.timesync); - odometry.log("successes", state.odometry.successes); - odometry.log("failures", state.odometry.failures); + var imu = backend.getNested("imu"); + imu.log("yaw", state.imu.yaw, Rotation2d.struct); + imu.log("pitch", state.imu.pitch, Rotation2d.struct); + imu.log("roll", state.imu.roll, Rotation2d.struct); + + var odometryThread = backend.getNested("odometryThread"); + odometryThread.log("timesync", state.odometryThread.timesync); + odometryThread.log("successes", state.odometryThread.successes); + odometryThread.log("failures", state.odometryThread.failures); } } diff --git a/src/main/java/org/team340/lib/swerve/SwerveModule.java b/src/main/java/org/team340/lib/swerve/SwerveModule.java index 73b3150..e6b5e2b 100644 --- a/src/main/java/org/team340/lib/swerve/SwerveModule.java +++ b/src/main/java/org/team340/lib/swerve/SwerveModule.java @@ -139,6 +139,11 @@ public SwerveModuleState getNextTarget() { * @param state The state to apply to the module. */ public void applyState(SwerveModuleState state) { + if (state.speedMetersPerSecond < config.velDeadband) { + state.speedMetersPerSecond = 0.0; + state.angle = nextTarget.angle; + } + Rotation2d angleDelta; double turnPosition; cacheMutex.lock(); @@ -159,7 +164,7 @@ public void applyState(SwerveModuleState state) { moveMotor.setVelocity(state.speedMetersPerSecond * (config.moveGearRatio / (config.wheelDiameter * Math.PI))); if (hookStatus.applyAbsolute()) { - turnMotor.setPosition(MathUtil.inputModulus(state.angle.getRotations(), -0.5, 0.5)); + turnMotor.setPosition(state.angle.getRotations()); } else { double optimizedDelta = angleDelta.getRadians() - (flipped ? Math.copySign(Math.PI, angleDelta.getRadians()) : 0.0); diff --git a/src/main/java/org/team340/lib/swerve/SwerveState.java b/src/main/java/org/team340/lib/swerve/SwerveState.java index 918b136..a8bca27 100644 --- a/src/main/java/org/team340/lib/swerve/SwerveState.java +++ b/src/main/java/org/team340/lib/swerve/SwerveState.java @@ -1,19 +1,27 @@ package org.team340.lib.swerve; +import edu.wpi.first.epilogue.Logged; +import edu.wpi.first.epilogue.Logged.Strategy; import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.geometry.Translation2d; import edu.wpi.first.math.kinematics.ChassisSpeeds; import edu.wpi.first.math.kinematics.SwerveModulePosition; import edu.wpi.first.math.kinematics.SwerveModuleState; +import java.util.ArrayList; +import java.util.List; +import org.team340.lib.swerve.SwerveAPI.TimestampedYaw; /** * Represents the state of the robot's drivetrain. */ +@Logged(strategy = Strategy.OPT_IN) public final class SwerveState { /** * Contains information about swerve module states and positions. */ + @Logged(strategy = Strategy.OPT_IN) public static final class Modules { /** The current measured module positions. */ @@ -40,9 +48,33 @@ private Modules(SwerveModule[] modules) { } /** - * Represents the state of the odometry. + * Measurements from the IMU. */ - public static final class Odometry { + @Logged(strategy = Strategy.OPT_IN) + public static final class IMU { + + /** All yaw measurements since the {@link SwerveState} has been refreshed. */ + public final List yawMeasurements; + /** The robot's yaw. */ + public Rotation2d yaw; + /** The robot's pitch. */ + public Rotation2d pitch; + /** The robot's roll. */ + public Rotation2d roll; + + private IMU() { + yawMeasurements = new ArrayList<>(); + yaw = Rotation2d.kZero; + pitch = Rotation2d.kZero; + roll = Rotation2d.kZero; + } + } + + /** + * Represents the state of the odometry thread. + */ + @Logged(strategy = Strategy.OPT_IN) + public static final class OdometryThread { /** If Phoenix timesync is being utilized. */ public boolean timesync; @@ -51,35 +83,38 @@ public static final class Odometry { /** The number of failing odometry measurements since the last loop. */ public int failures; - private Odometry() {} + private OdometryThread() {} } /** Information about module states and positions. */ public final Modules modules; - /** The state of the odometry. */ - public final Odometry odometry; + /** Measurements from the IMU. */ + public final IMU imu; + /** The state of the odometry thread. */ + public final OdometryThread odometryThread; /** The current measured robot-relative speeds. */ public final ChassisSpeeds speeds; /** The next target robot-relative speeds. Updated when using {@code applySpeeds()}. */ public final ChassisSpeeds targetSpeeds; /** The directionless measured velocity of the robot. */ public double velocity; - /** The robot's multi-turn yaw as reported by the IMU, in radians. */ - public double multiturnYaw; - /** The robot's pitch as reported by the IMU. */ - public Rotation2d pitch; - /** The robot's roll as reported by the IMU. */ - public Rotation2d roll; /** The current blue origin relative pose of the robot. */ public Pose2d pose; + /** The current blue origin relative translation of the robot. */ + public Translation2d translation; + /** The robot's rotation (yaw) as reported from the pose estimator. */ + public Rotation2d rotation; + /** The timestamp of the swerve state in seconds (FPGA time). */ + public double timestamp; SwerveState(SwerveModule[] modules) { this.modules = new Modules(modules); - odometry = new Odometry(); + imu = new IMU(); + odometryThread = new OdometryThread(); speeds = new ChassisSpeeds(); targetSpeeds = new ChassisSpeeds(); - pitch = Rotation2d.kZero; - roll = Rotation2d.kZero; pose = Pose2d.kZero; + translation = Translation2d.kZero; + rotation = Rotation2d.kZero; } } diff --git a/src/main/java/org/team340/lib/swerve/SwerveTunables.java b/src/main/java/org/team340/lib/swerve/SwerveTunables.java index d9edb30..2d33988 100644 --- a/src/main/java/org/team340/lib/swerve/SwerveTunables.java +++ b/src/main/java/org/team340/lib/swerve/SwerveTunables.java @@ -65,6 +65,7 @@ public static void initialize(String name, SwerveAPI api) { // setLimits() Tunable.doubleValue(name + "/limits/velocity", config.velocity, v -> config.velocity = v); + Tunable.doubleValue(name + "/limits/velDeadband", config.velDeadband, v -> config.velDeadband = v); Tunable.doubleValue(name + "/limits/slipAccel", config.slipAccel, v -> config.slipAccel = v); Tunable.doubleValue(name + "/limits/torqueAccel", config.torqueAccel, v -> config.torqueAccel = v); Tunable.doubleValue(name + "/limits/angularAccel", config.angularAccel, v -> config.angularAccel = v); diff --git a/src/main/java/org/team340/lib/swerve/config/SwerveConfig.java b/src/main/java/org/team340/lib/swerve/config/SwerveConfig.java index 2d06d19..9b27413 100644 --- a/src/main/java/org/team340/lib/swerve/config/SwerveConfig.java +++ b/src/main/java/org/team340/lib/swerve/config/SwerveConfig.java @@ -21,6 +21,8 @@ public class SwerveConfig { public double odometryPeriod = -1.0; /** The period to look ahead for discretizing chassis speeds in seconds. */ public double discretizationPeriod = -1.0; + /** The default frame period for unused CAN signals in seconds. */ + public double defaultFramePeriod = -1.0; /** PID gains for move motors, as a tuple of {@code [kP, kI, kD]}. */ public double[] movePID; /** Feed forward gains for move motors, as a tuple of {@code [kS, kV]}. */ @@ -33,6 +35,8 @@ public class SwerveConfig { public boolean turnBrakeMode = false; /** The maximum forward velocity the robot is capable of in meters/second. */ public double velocity = -1.0; + /** The minimum velocity required for a swerve module to accept a commanded output. */ + public double velDeadband = -1.0; /** The maximum acceleration the robot is capable of relevant to carpet slip in meters/second/second. */ public double slipAccel = -1.0; /** The maximum acceleration the robot is capable of relevant to motor torque in meters/second/second. */ @@ -53,10 +57,14 @@ public class SwerveConfig { public double driverAngularVelDeadband = -1.0; /** The robot's nominal voltage. Typically {@code 12.0}. */ public double voltage = -1.0; - /** The current limit in amps for move motors. */ - public double moveCurrentLimit = -1.0; - /** The current limit in amps for turn motors. */ - public double turnCurrentLimit = -1.0; + /** The stator current limit in amps for move motors. */ + public double moveStatorLimit = -1.0; + /** The supply current limit in amps for move motors. */ + public double moveSupplyLimit = -1.0; + /** The stator current limit in amps for turn motors. */ + public double turnStatorLimit = -1.0; + /** The supply current limit in amps for turn motors. */ + public double turnSupplyLimit = -1.0; /** The move gear ratio, in motor rotations/wheel rotation. */ public double moveGearRatio = -1.0; /** The turn gear ratio, in motor rotations/module rotation. */ @@ -85,11 +93,13 @@ public class SwerveConfig { * @param period The robot's main loop period in seconds. * @param odometry The period to update odometry in seconds. * @param discretization The period to look ahead for discretizing chassis speeds in seconds. + * @param defaultFramePeriod The default frame period for unused CAN signals in seconds. */ - public SwerveConfig setTimings(double period, double odometry, double discretization) { + public SwerveConfig setTimings(double period, double odometry, double discretization, double defaultFramePeriod) { this.period = period; odometryPeriod = odometry; discretizationPeriod = discretization; + this.defaultFramePeriod = defaultFramePeriod; return this; } @@ -146,12 +156,20 @@ public SwerveConfig setBrakeMode(boolean move, boolean turn) { * It is recommended that these values are found empirically using an actual robot. An easy way to do so is to configure infeasible limits, then analyze telemetry. * * @param velocity The maximum forward velocity the robot is capable of in meters/second. More specifically, the maximum velocity a move motor is capable of. + * @param velDeadband The minimum velocity required for a swerve module to accept a commanded output. * @param slipAccel The maximum acceleration the robot is capable of relevant to carpet slip in meters/second/second. * @param torqueAccel The maximum acceleration the robot is capable of relevant to motor torque in meters/second/second. * @param angularAccel The maximum angular acceleration the robot is capable of in radians/second/second. */ - public SwerveConfig setLimits(double velocity, double slipAccel, double torqueAccel, double angularAccel) { + public SwerveConfig setLimits( + double velocity, + double velDeadband, + double slipAccel, + double torqueAccel, + double angularAccel + ) { this.velocity = velocity; + this.velDeadband = velDeadband; this.slipAccel = slipAccel; this.torqueAccel = torqueAccel; this.angularAccel = angularAccel; @@ -187,13 +205,23 @@ public SwerveConfig setDriverProfile( /** * Sets power properties. * @param voltage The robot's nominal voltage. Typically {@code 12.0}. - * @param moveCurrentLimit The current limit in amps for move motors. - * @param turnCurrentLimit The current limit in amps for turn motors. + * @param moveStatorLimit The stator current limit in amps for move motors. + * @param moveSupplyLimit The supply current limit in amps for move motors. Note that this value is ignored for REV devices. + * @param turnStatorLimit The stator current limit in amps for turn motors. + * @param turnSupplyLimit The supply current limit in amps for turn motors. Note that this value is ignored for REV devices. */ - public SwerveConfig setPowerProperties(double voltage, double moveCurrentLimit, double turnCurrentLimit) { + public SwerveConfig setPowerProperties( + double voltage, + double moveStatorLimit, + double moveSupplyLimit, + double turnStatorLimit, + double turnSupplyLimit + ) { this.voltage = voltage; - this.moveCurrentLimit = moveCurrentLimit; - this.turnCurrentLimit = turnCurrentLimit; + this.moveStatorLimit = moveStatorLimit; + this.moveSupplyLimit = moveSupplyLimit; + this.turnStatorLimit = turnStatorLimit; + this.turnSupplyLimit = turnSupplyLimit; return this; } @@ -273,10 +301,12 @@ public void verify() { if (period == -1.0) missing.add("Period"); if (odometryPeriod == -1.0) missing.add("Odometry Period"); if (discretizationPeriod == -1.0) missing.add("Discretization Period"); + if (defaultFramePeriod == -1.0) missing.add("Default Frame Period"); if (movePID == null) missing.add("Move PID"); if (moveFF == null) missing.add("Move FF"); if (turnPID == null) missing.add("Turn PID"); if (velocity == -1.0) missing.add("Velocity"); + if (velDeadband == -1.0) missing.add("Velocity Deadband"); if (slipAccel == -1.0) missing.add("Slip Acceleration"); if (torqueAccel == -1.0) missing.add("Torque Acceleration"); if (angularAccel == -1.0) missing.add("Angular Acceleration"); @@ -287,8 +317,10 @@ public void verify() { if (driverAngularVelExp == -1.0) missing.add("Driver Angular Velocity Exponential"); if (driverAngularVelDeadband == -1.0) missing.add("Driver Angular Velocity Deadband"); if (voltage == -1.0) missing.add("Voltage"); - if (moveCurrentLimit == -1.0) missing.add("Move Current Limit"); - if (turnCurrentLimit == -1.0) missing.add("Turn Current Limit"); + if (moveStatorLimit == -1.0) missing.add("Move Stator Current Limit"); + if (moveSupplyLimit == -1.0) missing.add("Move Supply Current Limit"); + if (turnStatorLimit == -1.0) missing.add("Turn Stator Current Limit"); + if (turnSupplyLimit == -1.0) missing.add("Turn Supply Current Limit"); if (moveGearRatio == -1.0) missing.add("Move Gear Ratio"); if (turnGearRatio == -1.0) missing.add("Turn Gear Ratio"); if (couplingRatio == -1.0) missing.add("Coupling Ratio"); diff --git a/src/main/java/org/team340/lib/swerve/hardware/SwerveBaseHardware.java b/src/main/java/org/team340/lib/swerve/hardware/SwerveBaseHardware.java index 6442fde..866ab97 100644 --- a/src/main/java/org/team340/lib/swerve/hardware/SwerveBaseHardware.java +++ b/src/main/java/org/team340/lib/swerve/hardware/SwerveBaseHardware.java @@ -7,13 +7,6 @@ import org.team340.lib.swerve.SwerveAPI; interface SwerveBaseHardware extends AutoCloseable { - /** - * The CAN frame period in seconds to use for frames containing data - * utilized for telemetry that is not necessarily required for swerve - * to function. - */ - static final double kTelemetryCANPeriod = 0.2; - /** * Returns the device's underlying API. */ diff --git a/src/main/java/org/team340/lib/swerve/hardware/SwerveEncoders.java b/src/main/java/org/team340/lib/swerve/hardware/SwerveEncoders.java index 0f1404c..7a7c812 100644 --- a/src/main/java/org/team340/lib/swerve/hardware/SwerveEncoders.java +++ b/src/main/java/org/team340/lib/swerve/hardware/SwerveEncoders.java @@ -57,7 +57,7 @@ public abstract static class SwerveEncoder implements SwerveBaseHardware { public static interface Ctor extends BiFunction {} /** {@link SwerveEncoder#hookStatus()} */ - public static record HookStatus(boolean readMotor, boolean applyAbsolute) {} + public static final record HookStatus(boolean readMotor, boolean applyAbsolute) {} /** * Constructs a swerve encoder. Wraps to support simulation if applicable. @@ -231,7 +231,7 @@ public static SwerveEncoder.Ctor canandmag(int id, double offset, boolean invert .setDisableZeroButton(true) .setInvertDirection(inverted) .setPositionFramePeriod(config.odometryPeriod) - .setStatusFramePeriod(config.period) + .setStatusFramePeriod(config.defaultFramePeriod) .setVelocityFramePeriod(config.odometryPeriod) .setZeroOffset(offset); @@ -283,19 +283,19 @@ public static SwerveEncoder.Ctor canCoder(int id, double offset, boolean inverte ? SensorDirectionValue.Clockwise_Positive : SensorDirectionValue.CounterClockwise_Positive; - PhoenixUtil.run("Clear Sticky Faults", canCoder, () -> canCoder.clearStickyFaults()); - PhoenixUtil.run("Apply CANcoderConfiguration", canCoder, () -> - canCoder.getConfigurator().apply(canCoderConfig) - ); - PhoenixUtil.run("Set Update Frequency", canCoder, () -> + PhoenixUtil.run("Clear Sticky Faults", () -> canCoder.clearStickyFaults()); + PhoenixUtil.run("Apply CANcoderConfiguration", () -> canCoder.getConfigurator().apply(canCoderConfig)); + PhoenixUtil.run("Set Update Frequency", () -> BaseStatusSignal.setUpdateFrequencyForAll(1.0 / config.odometryPeriod, position, velocity) ); - PhoenixUtil.run("Optimize Bus Utilization", canCoder, () -> - canCoder.optimizeBusUtilization(1.0 / SwerveBaseHardware.kTelemetryCANPeriod, 0.05) + PhoenixUtil.run("Optimize Bus Utilization", () -> + canCoder.optimizeBusUtilization(1.0 / config.defaultFramePeriod, 0.05) ); if (turnMotor.getAPI() instanceof TalonFX talonFX) { var feedbackConfig = new FeedbackConfigs(); + talonFX.getConfigurator().refresh(feedbackConfig); + feedbackConfig.FeedbackRemoteSensorID = id; feedbackConfig.RotorToSensorRatio = config.turnGearRatio; feedbackConfig.FeedbackSensorSource = config.phoenixPro @@ -303,11 +303,11 @@ public static SwerveEncoder.Ctor canCoder(int id, double offset, boolean inverte : FeedbackSensorSourceValue.RemoteCANcoder; var closedLoopConfig = new ClosedLoopGeneralConfigs(); + talonFX.getConfigurator().refresh(closedLoopConfig); closedLoopConfig.ContinuousWrap = true; - PhoenixUtil.run("Apply FeedbackConfigs", talonFX, () -> talonFX.getConfigurator().apply(feedbackConfig) - ); - PhoenixUtil.run("Apply ClosedLoopGeneralConfigs", talonFX, () -> + PhoenixUtil.run("Apply FeedbackConfigs", () -> talonFX.getConfigurator().apply(feedbackConfig)); + PhoenixUtil.run("Apply ClosedLoopGeneralConfigs", () -> talonFX.getConfigurator().apply(closedLoopConfig) ); diff --git a/src/main/java/org/team340/lib/swerve/hardware/SwerveIMUs.java b/src/main/java/org/team340/lib/swerve/hardware/SwerveIMUs.java index 42a5aeb..d05da7e 100644 --- a/src/main/java/org/team340/lib/swerve/hardware/SwerveIMUs.java +++ b/src/main/java/org/team340/lib/swerve/hardware/SwerveIMUs.java @@ -25,7 +25,6 @@ import org.team340.lib.swerve.SwerveAPI; import org.team340.lib.swerve.config.SwerveConfig; import org.team340.lib.swerve.hardware.SwerveIMUs.SwerveIMU.IMUSimHook; -import org.team340.lib.util.Math2; import org.team340.lib.util.Mutable; import org.team340.lib.util.vendors.PhoenixUtil; import org.team340.lib.util.vendors.ReduxUtil; @@ -69,11 +68,6 @@ public static SwerveIMU construct(Ctor ctor, SwerveConfig config, IMUSimHook sim return imu; } - /** - * Gets the IMU's multi-turn yaw in radians. - */ - public abstract double getMultiturnYaw(); - /** * Gets the IMU's absolute yaw. */ @@ -110,11 +104,6 @@ public static SwerveIMU.Ctor adis16470( ADIS16470_IMU adis16470 = new ADIS16470_IMU(yawAxis, pitchAxis, rollAxis, port, calibrationTime); return new SwerveIMU() { - @Override - public double getMultiturnYaw() { - return Math.toRadians(adis16470.getAngle(adis16470.getYawAxis())); - } - @Override public Rotation2d getYaw() { return Rotation2d.fromDegrees(adis16470.getAngle(adis16470.getYawAxis())); @@ -158,27 +147,16 @@ public static SwerveIMU.Ctor canandgyro(int id) { Canandgyro canandgyro = new Canandgyro(id); var settings = new CanandgyroSettings() - .setAccelerationFramePeriod(config.period) - .setAngularPositionFramePeriod(config.period) + .setAccelerationFramePeriod(config.defaultFramePeriod) + .setAngularPositionFramePeriod(config.odometryPeriod) .setAngularVelocityFramePeriod(config.odometryPeriod) - .setStatusFramePeriod(config.period) + .setStatusFramePeriod(config.defaultFramePeriod) .setYawFramePeriod(config.odometryPeriod); canandgyro.clearStickyFaults(); ReduxUtil.applySettings(canandgyro, settings); return new SwerveIMU() { - @Override - public double getMultiturnYaw() { - return ( - ReduxUtil.latencyCompensate( - canandgyro.getMultiturnYawFrame(), - canandgyro.getAngularVelocityYaw() - ) * - Math2.kTwoPi - ); - } - @Override public Rotation2d getYaw() { return Rotation2d.fromRotations( @@ -240,23 +218,23 @@ public static SwerveIMU.Ctor pigeon2(int id) { StatusSignal pitchVelocity = pigeon2.getAngularVelocityXWorld(); StatusSignal rollVelocity = pigeon2.getAngularVelocityYWorld(); - PhoenixUtil.run("Clear Sticky Faults", pigeon2, () -> pigeon2.clearStickyFaults()); - PhoenixUtil.run("Set Update Frequency", pigeon2, () -> - BaseStatusSignal.setUpdateFrequencyForAll(1.0 / config.odometryPeriod, yaw, yawVelocity) - ); - PhoenixUtil.run("Set Update Frequency", pigeon2, () -> - BaseStatusSignal.setUpdateFrequencyForAll(1.0 / config.period, pitch, roll, pitchVelocity, rollVelocity) + PhoenixUtil.run("Clear Sticky Faults", () -> pigeon2.clearStickyFaults()); + PhoenixUtil.run("Set Update Frequency", () -> + BaseStatusSignal.setUpdateFrequencyForAll( + 1.0 / config.odometryPeriod, + yaw, + pitch, + roll, + yawVelocity, + pitchVelocity, + rollVelocity + ) ); - PhoenixUtil.run("Optimize Bus Utilization", pigeon2, () -> - pigeon2.optimizeBusUtilization(1.0 / SwerveBaseHardware.kTelemetryCANPeriod, 0.05) + PhoenixUtil.run("Optimize Bus Utilization", () -> + pigeon2.optimizeBusUtilization(1.0 / config.defaultFramePeriod, 0.05) ); return new SwerveIMU() { - @Override - public double getMultiturnYaw() { - return Math.toRadians(BaseStatusSignal.getLatencyCompensatedValueAsDouble(yaw, yawVelocity)); - } - @Override public Rotation2d getYaw() { return Rotation2d.fromDegrees( @@ -314,11 +292,6 @@ private static SwerveIMU simulate(SwerveIMU imu, SwerveConfig config, IMUSimHook }); return new SwerveIMU() { - @Override - public double getMultiturnYaw() { - return yaw.value; - } - @Override public Rotation2d getYaw() { return Rotation2d.fromRadians(yaw.value); diff --git a/src/main/java/org/team340/lib/swerve/hardware/SwerveMotors.java b/src/main/java/org/team340/lib/swerve/hardware/SwerveMotors.java index 2f002e7..585f6b5 100644 --- a/src/main/java/org/team340/lib/swerve/hardware/SwerveMotors.java +++ b/src/main/java/org/team340/lib/swerve/hardware/SwerveMotors.java @@ -2,7 +2,6 @@ import com.ctre.phoenix6.BaseStatusSignal; import com.ctre.phoenix6.StatusSignal; -import com.ctre.phoenix6.configs.Slot0Configs; import com.ctre.phoenix6.configs.TalonFXConfiguration; import com.ctre.phoenix6.controls.PositionVoltage; import com.ctre.phoenix6.controls.VelocityVoltage; @@ -109,13 +108,12 @@ public static SwerveMotor construct(Ctor ctor, SwerveConfig config, boolean isMo /** * Configures a {@link CANSparkMax Spark Max}. * @param id CAN ID of the device, as configured in the REV Hardware Client. - * @param type The motor type connected to the controller. * @param inverted If the motor is inverted. */ - public static SwerveMotor.Ctor sparkMax(int id, MotorType type, boolean inverted) { + public static SwerveMotor.Ctor sparkMax(int id, boolean inverted) { return (config, isMoveMotor) -> { var deviceLogger = new SparkMaxLogger(); - SparkMax sparkMax = new SparkMax(id, type); + SparkMax sparkMax = new SparkMax(id, MotorType.kBrushless); RelativeEncoder relativeEncoder = sparkMax.getEncoder(); SparkClosedLoopController pid = sparkMax.getClosedLoopController(); ClosedLoopSlot pidSlot = ClosedLoopSlot.kSlot0; @@ -127,7 +125,7 @@ public static SwerveMotor.Ctor sparkMax(int id, MotorType type, boolean inverted sparkConfig .voltageCompensation(config.voltage) - .smartCurrentLimit((int) (isMoveMotor ? config.moveCurrentLimit : config.turnCurrentLimit)) + .smartCurrentLimit((int) (isMoveMotor ? config.moveStatorLimit : config.turnStatorLimit)) .idleMode( (isMoveMotor ? config.moveBrakeMode : config.turnBrakeMode) ? IdleMode.kBrake : IdleMode.kCoast ) @@ -137,14 +135,12 @@ public static SwerveMotor.Ctor sparkMax(int id, MotorType type, boolean inverted .feedbackSensor(FeedbackSensor.kPrimaryEncoder) .pid(pidGains[0], pidGains[1], pidGains[2], pidSlot); - sparkConfig.encoder - .positionConversionFactor(1.0) - .velocityConversionFactor(1.0 / 60.0); + sparkConfig.encoder.positionConversionFactor(1.0).velocityConversionFactor(1.0 / 60.0); sparkConfig.signals - .appliedOutputPeriodMs((int) (config.period * 1000.0)) + .appliedOutputPeriodMs((int) (config.defaultFramePeriod * 1000.0)) .faultsAlwaysOn(true) - .faultsPeriodMs((int) (config.period * 1000.0)) + .faultsPeriodMs((int) (config.defaultFramePeriod * 1000.0)) .primaryEncoderPositionAlwaysOn(true) .primaryEncoderPositionPeriodMs((int) (config.odometryPeriod * 1000.0)) .primaryEncoderVelocityAlwaysOn(true) @@ -227,13 +223,12 @@ public void close() { /** * Configures a {@link CANSparkFlex Spark Flex}. * @param id CAN ID of the device, as configured in the REV Hardware Client. - * @param type The motor type connected to the controller. * @param inverted If the motor is inverted. */ - public static SwerveMotor.Ctor sparkFlex(int id, MotorType type, boolean inverted) { + public static SwerveMotor.Ctor sparkFlex(int id, boolean inverted) { return (config, isMoveMotor) -> { var deviceLogger = new SparkFlexLogger(); - SparkFlex sparkFlex = new SparkFlex(id, type); + SparkFlex sparkFlex = new SparkFlex(id, MotorType.kBrushless); RelativeEncoder relativeEncoder = sparkFlex.getEncoder(); SparkClosedLoopController pid = sparkFlex.getClosedLoopController(); ClosedLoopSlot pidSlot = ClosedLoopSlot.kSlot0; @@ -245,7 +240,7 @@ public static SwerveMotor.Ctor sparkFlex(int id, MotorType type, boolean inverte sparkConfig .voltageCompensation(config.voltage) - .smartCurrentLimit((int) (isMoveMotor ? config.moveCurrentLimit : config.turnCurrentLimit)) + .smartCurrentLimit((int) (isMoveMotor ? config.moveStatorLimit : config.turnStatorLimit)) .idleMode( (isMoveMotor ? config.moveBrakeMode : config.turnBrakeMode) ? IdleMode.kBrake : IdleMode.kCoast ) @@ -262,9 +257,9 @@ public static SwerveMotor.Ctor sparkFlex(int id, MotorType type, boolean inverte .quadratureAverageDepth(isMoveMotor ? 8 : 64); sparkConfig.signals - .appliedOutputPeriodMs((int) (config.period * 1000.0)) + .appliedOutputPeriodMs((int) (config.defaultFramePeriod * 1000.0)) .faultsAlwaysOn(true) - .faultsPeriodMs((int) (config.period * 1000.0)) + .faultsPeriodMs((int) (config.defaultFramePeriod * 1000.0)) .primaryEncoderPositionAlwaysOn(true) .primaryEncoderPositionPeriodMs((int) (config.odometryPeriod * 1000.0)) .primaryEncoderVelocityAlwaysOn(true) @@ -358,30 +353,32 @@ public static SwerveMotor.Ctor talonFX(int id, boolean inverted) { StatusSignal velocity = talonFX.getVelocity().clone(); boolean enableFOC = isMoveMotor ? config.phoenixMoveFOC : config.phoenixTurnFOC; - boolean timesync = config.phoenixPro && config.phoenixCanBus.isNetworkFD(); PositionVoltage positionControl = new PositionVoltage(0.0); positionControl.EnableFOC = enableFOC; - positionControl.UseTimesync = timesync; positionControl.UpdateFreqHz = 0.0; VelocityVoltage velocityControl = new VelocityVoltage(0.0); velocityControl.EnableFOC = enableFOC; - velocityControl.UseTimesync = timesync; velocityControl.UpdateFreqHz = 0.0; - VoltageOut voltageControl = new VoltageOut(0); + VoltageOut voltageControl = new VoltageOut(0.0); double[] pidGains = isMoveMotor ? config.movePID : config.turnPID; double[] ffGains = isMoveMotor ? config.moveFF : new double[] { 0.0, 0.0 }; + double statorLimit = isMoveMotor ? config.moveStatorLimit : config.turnStatorLimit; + double supplyLimit = isMoveMotor ? config.moveSupplyLimit : config.turnSupplyLimit; + var talonConfig = new TalonFXConfiguration(); - double currentLimit = isMoveMotor ? config.moveCurrentLimit : config.turnCurrentLimit; - talonConfig.CurrentLimits.StatorCurrentLimit = currentLimit; - talonConfig.CurrentLimits.SupplyCurrentLimit = currentLimit; + talonConfig.Audio.AllowMusicDurDisable = true; + + talonConfig.CurrentLimits.StatorCurrentLimit = statorLimit; + talonConfig.CurrentLimits.SupplyCurrentLimit = supplyLimit; + talonConfig.TorqueCurrent.PeakForwardTorqueCurrent = statorLimit; + talonConfig.TorqueCurrent.PeakReverseTorqueCurrent = -statorLimit; - talonConfig.MotorOutput.ControlTimesyncFreqHz = 1.0 / config.period; talonConfig.MotorOutput.Inverted = inverted ? InvertedValue.Clockwise_Positive : InvertedValue.CounterClockwise_Positive; @@ -395,16 +392,16 @@ public static SwerveMotor.Ctor talonFX(int id, boolean inverted) { talonConfig.Slot0.kS = ffGains[0]; talonConfig.Slot0.kV = ffGains[1]; - PhoenixUtil.run("Clear Sticky Faults", talonFX, () -> talonFX.clearStickyFaults()); - PhoenixUtil.run("Apply TalonFXConfiguration", talonFX, () -> talonFX.getConfigurator().apply(talonConfig)); - PhoenixUtil.run("Set Update Frequency", talonFX, () -> + PhoenixUtil.run("Clear Sticky Faults", () -> talonFX.clearStickyFaults()); + PhoenixUtil.run("Apply TalonFXConfiguration", () -> talonFX.getConfigurator().apply(talonConfig)); + PhoenixUtil.run("Set Update Frequency", () -> BaseStatusSignal.setUpdateFrequencyForAll(1.0 / config.odometryPeriod, position, velocity) ); - PhoenixUtil.run("Optimize Bus Utilization", talonFX, () -> - talonFX.optimizeBusUtilization(1.0 / SwerveBaseHardware.kTelemetryCANPeriod, 0.05) + PhoenixUtil.run("Optimize Bus Utilization", () -> + talonFX.optimizeBusUtilization(1.0 / config.defaultFramePeriod, 0.05) ); - if (isMoveMotor) PhoenixUtil.run("Zero Encoder", talonFX, () -> talonFX.setPosition(0.0)); + if (isMoveMotor) PhoenixUtil.run("Zero Rotor Encoder", () -> talonFX.setPosition(0.0)); return new SwerveMotor() { @Override @@ -437,7 +434,7 @@ public void reapplyGains() { double[] pidGains = isMoveMotor ? config.movePID : config.turnPID; double[] ffGains = isMoveMotor ? config.moveFF : new double[] { 0.0, 0.0 }; - var slot0Config = new Slot0Configs(); + var slot0Config = talonConfig.Slot0; slot0Config.kP = pidGains[0]; slot0Config.kI = pidGains[1]; slot0Config.kD = pidGains[2]; diff --git a/src/main/java/org/team340/lib/util/DisableWatchdog.java b/src/main/java/org/team340/lib/util/DisableWatchdog.java new file mode 100644 index 0000000..7533148 --- /dev/null +++ b/src/main/java/org/team340/lib/util/DisableWatchdog.java @@ -0,0 +1,50 @@ +package org.team340.lib.util; + +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.Watchdog; +import java.lang.reflect.Field; + +/** + * Utility class for disabling {@link Watchdog} instances inside objects. + */ +public final class DisableWatchdog { + + private DisableWatchdog() { + throw new AssertionError("This is a utility class!"); + } + + /** + * Disables a {@link Watchdog} instance within the specified object. + * @param obj The object containing the {@link Watchdog} to disable. + * @param fieldName The name of the field the {@link Watchdog} is declared as. + */ + public static void in(Object obj, String fieldName) { + try { + Field field = null; + Class clazz = obj.getClass(); + + while (field == null) { + try { + field = clazz.getDeclaredField(fieldName); + } catch (Exception e) { + clazz = clazz.getSuperclass(); + if (clazz == null) throw new RuntimeException(); + } + } + + field.setAccessible(true); + Watchdog watchdog = (Watchdog) field.get(obj); + watchdog.disable(); + watchdog.setTimeout(1e6); + } catch (Exception e) { + DriverStation.reportWarning( + "Unable to disable watchdog: Attempted with accessor \"" + + obj.getClass().getSimpleName() + + "." + + fieldName + + "\"", + false + ); + } + } +} diff --git a/src/main/java/org/team340/lib/util/GRRDashboard.java b/src/main/java/org/team340/lib/util/GRRDashboard.java deleted file mode 100644 index ed06950..0000000 --- a/src/main/java/org/team340/lib/util/GRRDashboard.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.team340.lib.util; - -import choreo.Choreo.TrajectoryCache; -import choreo.trajectory.SwerveSample; -import choreo.trajectory.Trajectory; -import edu.wpi.first.math.Pair; -import edu.wpi.first.networktables.NetworkTable; -import edu.wpi.first.networktables.NetworkTableInstance; -import edu.wpi.first.networktables.RawPublisher; -import edu.wpi.first.networktables.StringPublisher; -import edu.wpi.first.networktables.StringSubscriber; -import edu.wpi.first.wpilibj2.command.Command; -import edu.wpi.first.wpilibj2.command.Commands; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Utility class for interfacing with GRRDashboard. - */ -public final class GRRDashboard { - - private GRRDashboard() { - throw new AssertionError("This is a utility class!"); - } - - private static final NetworkTable nt = NetworkTableInstance.getDefault().getTable("GRRDashboard"); - - private static final String defaultAuto = "Do Nothing"; - private static final Map> autoOptions = new LinkedHashMap<>(); - private static final NetworkTable autoOptionsTable = nt.getSubTable("autos/options"); - private static final StringPublisher activeAutoPub = nt.getStringTopic("autos/active").publish(); - private static final StringSubscriber selectedAutoSub = nt.getStringTopic("autos/selected").subscribe(defaultAuto); - - private static TrajectoryCache trajectoryCache = new TrajectoryCache(); - private static Command selectedAuto = Commands.none(); - - static { - addAuto(defaultAuto, selectedAuto); - activeAutoPub.setDefault(defaultAuto); - } - - /** - * Sets the Choreo {@link TrajectoryCache} in use. Utilized for loading trajectories. - * @param cache The trajectory cache in use by the robot. - */ - public static void setTrajectoryCache(TrajectoryCache cache) { - trajectoryCache = cache; - } - - /** - * Adds an auto to the dashboard. - * @param label The label for the auto. - * @param command The auto's command. - * @return The auto's label. - */ - public static String addAuto(String label, Command command) { - return addAuto(label, List.of(), command); - } - - /** - * Adds an auto to the dashboard. - * @param label The label for the auto. - * @param trajectory The name of the trajectory utilized by the auto. - * @param command The auto's command. - * @return The auto's label. - */ - public static String addAuto(String label, String trajectory, Command command) { - return addAuto(label, List.of(trajectory), command); - } - - /** - * Adds an auto to the dashboard. - * @param label The label for the auto. - * @param trajectories A list of the names of trajectories utilized by the auto. - * @param command The auto's command. - * @return The auto's label. - */ - @SuppressWarnings("unchecked") - public static String addAuto(String label, List trajectories, Command command) { - List> loaded = new ArrayList<>(); - for (String name : trajectories) { - var trajectory = trajectoryCache.loadTrajectory(name); - loaded.add( - trajectory.isPresent() - ? (Trajectory) trajectory.get() - : new Trajectory("", List.of(), List.of(), List.of()) - ); - } - - int size = loaded.stream().mapToInt(t -> t.samples().size() * 16).sum(); - ByteBuffer serialized = ByteBuffer.allocate(size + 4); - - double t = 0.0; - for (int i = 0; i < loaded.size(); i++) { - var trajectory = loaded.get(i); - for (SwerveSample sample : trajectory.samples()) { - serialized - .putFloat((float) sample.x) - .putFloat((float) sample.y) - .putFloat((float) sample.heading) - .putFloat((float) (sample.t + t)); - } - - var last = trajectory.getFinalSample(false); - if (last.isPresent()) t += last.get().t; - } - - serialized.putFloat((float) t); - var pub = autoOptionsTable.getRawTopic(label).publish("raw"); - autoOptions.put(label, Pair.of(command, pub)); - pub.set(serialized); - - return label; - } - - /** - * Gets the command of the currently selected auto. Note that this method - * is not intended for use with triggers or command compositions, as the - * returned command will not update when the selection changes. Use - * {@link GRRDashboard#runSelectedAuto()} instead. - */ - public static Command getSelectedAuto() { - return selectedAuto; - } - - /** - * Returns a command that when scheduled will run the currently selected auto. - */ - public static Command runSelectedAuto() { - return Commands.defer(() -> selectedAuto.asProxy(), Set.of()).withName("GRRDashboard.runSelectedAuto()"); - } - - /** - * Syncs data with the dashboard. Must be called - * periodically in order for this class to function. - */ - public static void update() { - String[] selections = selectedAutoSub.readQueueValues(); - if (selections.length > 0) { - String selection = selections[selections.length - 1]; - var option = autoOptions.get(selection); - if (option != null) { - activeAutoPub.set(selection); - selectedAuto = option.getFirst(); - } - } - } -} diff --git a/src/main/java/org/team340/lib/util/Tunable.java b/src/main/java/org/team340/lib/util/Tunable.java index ee18fc9..a860576 100644 --- a/src/main/java/org/team340/lib/util/Tunable.java +++ b/src/main/java/org/team340/lib/util/Tunable.java @@ -13,6 +13,7 @@ import edu.wpi.first.epilogue.Logged.Strategy; import edu.wpi.first.math.controller.PIDController; import edu.wpi.first.math.controller.ProfiledPIDController; +import edu.wpi.first.math.filter.Debouncer; import edu.wpi.first.math.trajectory.TrapezoidProfile; import edu.wpi.first.networktables.BooleanEntry; import edu.wpi.first.networktables.DoubleEntry; @@ -76,23 +77,36 @@ public static TunableBoolean booleanValue(String name, boolean defaultValue, Boo public static final class TunableBoolean implements AutoCloseable { private final BooleanEntry entry; + private boolean value; private TunableBoolean(String name, boolean defaultValue, BooleanConsumer onChange) { entry = nt.getBooleanTopic(name).getEntry(defaultValue); entry.setDefault(defaultValue); - if (onChange != null) { - pollChanges.bind(() -> { + value = defaultValue; + + pollChanges.bind(() -> { + value = entry.get(); + if (onChange != null) { boolean[] changes = entry.readQueueValues(); if (changes.length > 0) onChange.accept(changes[changes.length - 1]); - }); - } + } + }); } /** * Returns the value of the tunable. */ public boolean value() { - return entry.get(); + return value; + } + + /** + * Sets the value of the tunable. + * @param value The new value. + */ + public void set(boolean value) { + this.value = value; + entry.set(value); } @Override @@ -127,23 +141,36 @@ public static TunableInteger intValue(String name, int defaultValue, IntConsumer public static final class TunableInteger implements AutoCloseable { private final IntegerEntry entry; + private int value; private TunableInteger(String name, int defaultValue, IntConsumer onChange) { entry = nt.getIntegerTopic(name).getEntry(defaultValue); entry.setDefault(defaultValue); - if (onChange != null) { - pollChanges.bind(() -> { + value = defaultValue; + + pollChanges.bind(() -> { + value = (int) entry.get(); + if (onChange != null) { long[] changes = entry.readQueueValues(); if (changes.length > 0) onChange.accept((int) changes[changes.length - 1]); - }); - } + } + }); } /** * Returns the value of the tunable. */ public int value() { - return (int) entry.get(); + return value; + } + + /** + * Sets the value of the tunable. + * @param value The new value. + */ + public void set(int value) { + this.value = value; + entry.set(value); } @Override @@ -178,23 +205,36 @@ public static TunableFloat floatValue(String name, float defaultValue, FloatCons public static final class TunableFloat implements AutoCloseable { private final FloatEntry entry; + private float value; private TunableFloat(String name, float defaultValue, FloatConsumer onChange) { entry = nt.getFloatTopic(name).getEntry(defaultValue); entry.setDefault(defaultValue); - if (onChange != null) { - pollChanges.bind(() -> { + value = defaultValue; + + pollChanges.bind(() -> { + value = entry.get(); + if (onChange != null) { float[] changes = entry.readQueueValues(); if (changes.length > 0) onChange.accept(changes[changes.length - 1]); - }); - } + } + }); } /** * Returns the value of the tunable. */ public float value() { - return entry.get(); + return value; + } + + /** + * Sets the value of the tunable. + * @param value The new value. + */ + public void set(float value) { + this.value = value; + entry.set(value); } @Override @@ -229,23 +269,36 @@ public static TunableDouble doubleValue(String name, double defaultValue, Double public static final class TunableDouble implements AutoCloseable { private final DoubleEntry entry; + private double value; private TunableDouble(String name, double defaultValue, DoubleConsumer onChange) { entry = nt.getDoubleTopic(name).getEntry(defaultValue); entry.setDefault(defaultValue); - if (onChange != null) { - pollChanges.bind(() -> { + value = defaultValue; + + pollChanges.bind(() -> { + value = entry.get(); + if (onChange != null) { double[] changes = entry.readQueueValues(); if (changes.length > 0) onChange.accept(changes[changes.length - 1]); - }); - } + } + }); } /** * Returns the value of the tunable. */ public double value() { - return entry.get(); + return value; + } + + /** + * Sets the value of the tunable. + * @param value The new value. + */ + public void set(double value) { + this.value = value; + entry.set(value); } @Override @@ -280,23 +333,36 @@ public static TunableString stringValue(String name, String defaultValue, Consum public static final class TunableString implements AutoCloseable { private final StringEntry entry; + private String value; private TunableString(String name, String defaultValue, Consumer onChange) { entry = nt.getStringTopic(name).getEntry(defaultValue); entry.setDefault(defaultValue); - if (onChange != null) { - pollChanges.bind(() -> { + value = defaultValue; + + pollChanges.bind(() -> { + value = entry.get(); + if (onChange != null) { String[] changes = entry.readQueueValues(); if (changes.length > 0) onChange.accept(changes[changes.length - 1]); - }); - } + } + }); } /** * Returns the value of the tunable. */ public String value() { - return entry.get(); + return value; + } + + /** + * Sets the value of the tunable. + * @param value The new value. + */ + public void set(String value) { + this.value = value; + entry.set(value); } @Override @@ -311,10 +377,10 @@ public void close() { * @param controller The PID controller. */ public static void pidController(String name, PIDController controller) { - doubleValue(name + "/kP", controller.getP(), v -> controller.setP(v)); - doubleValue(name + "/kI", controller.getI(), v -> controller.setI(v)); - doubleValue(name + "/kD", controller.getD(), v -> controller.setD(v)); - doubleValue(name + "/iZone", controller.getIZone(), v -> controller.setIZone(v)); + doubleValue(name + "/kP", controller.getP(), controller::setP); + doubleValue(name + "/kI", controller.getI(), controller::setI); + doubleValue(name + "/kD", controller.getD(), controller::setD); + doubleValue(name + "/iZone", controller.getIZone(), controller::setIZone); } /** @@ -323,10 +389,10 @@ public static void pidController(String name, PIDController controller) { * @param controller The PID controller. */ public static void pidController(String name, ProfiledPIDController controller) { - doubleValue(name + "/kP", controller.getP(), v -> controller.setP(v)); - doubleValue(name + "/kI", controller.getI(), v -> controller.setI(v)); - doubleValue(name + "/kD", controller.getD(), v -> controller.setD(v)); - doubleValue(name + "/iZone", controller.getIZone(), v -> controller.setIZone(v)); + doubleValue(name + "/kP", controller.getP(), controller::setP); + doubleValue(name + "/kI", controller.getI(), controller::setI); + doubleValue(name + "/kD", controller.getD(), controller::setD); + doubleValue(name + "/iZone", controller.getIZone(), controller::setIZone); doubleValue(name + "/maxVelocity", controller.getConstraints().maxVelocity, v -> controller.setConstraints(new TrapezoidProfile.Constraints(v, controller.getConstraints().maxAcceleration)) ); @@ -574,7 +640,7 @@ public static void motionProfile(String name, SparkFlex spark, ClosedLoopSlot sl /** * Enables tuning a {@link TalonFX}'s motion magic config. - * @param The name for the tunable. Must be unique. + * @param name The name for the tunable. Must be unique. * @param talonFX The TalonFX to tune. */ public static void motionProfile(String name, TalonFX talonFX) { @@ -607,4 +673,13 @@ public static void motionProfile(String name, TalonFX talonFX) { talonFX.getConfigurator().apply(config); }); } + + /** + * Enables tuning a {@link Debouncer}'s debounce time. + * @param name The name for the tunable. Must be unique. + * @param debouncer The debouncer to tune. + */ + public static void debounce(String name, Debouncer debouncer) { + doubleValue(name, debouncer.getDebounceTime(), debouncer::setDebounceTime); + } } diff --git a/src/main/java/org/team340/lib/util/vendors/PhoenixUtil.java b/src/main/java/org/team340/lib/util/vendors/PhoenixUtil.java index 814b96d..3e490c8 100644 --- a/src/main/java/org/team340/lib/util/vendors/PhoenixUtil.java +++ b/src/main/java/org/team340/lib/util/vendors/PhoenixUtil.java @@ -1,7 +1,6 @@ package org.team340.lib.util.vendors; import com.ctre.phoenix6.StatusCode; -import com.ctre.phoenix6.hardware.ParentDevice; import edu.wpi.first.wpilibj.DriverStation; import java.util.function.Supplier; @@ -18,23 +17,21 @@ private PhoenixUtil() { * Runs a Phoenix API call and checks for errors. Will * try up to 3 times if the target API call fails. * @param name The name of the API call. - * @param device The device the call is relevant to. * @param target The target call to run. * @return {@code true} if success ({@link StatusCode#isOK()}), {@code false} otherwise. */ - public static boolean run(String name, ParentDevice device, Supplier target) { - return run(name, device, target, 3); + public static boolean run(String name, Supplier target) { + return run(name, target, 3); } /** * Runs a Phoenix API call and checks for errors. * @param name The name of the API call. - * @param device The device the call is relevant to. * @param target The target call to run. * @param maxTries The number of times to try the call before failing. {@code 1} only runs the call once. * @return {@code true} if success ({@link StatusCode#isOK()}), {@code false} otherwise. */ - public static boolean run(String name, ParentDevice device, Supplier target, int maxTries) { + public static boolean run(String name, Supplier target, int maxTries) { String results = ""; for (int i = 0; i < maxTries; i++) { StatusCode result = target.get(); @@ -42,17 +39,7 @@ public static boolean run(String name, ParentDevice device, Supplier results += (results.isEmpty() ? "" : ", ") + result.name(); } - DriverStation.reportError( - "[PhoenixUtil] " + - device.getClass().getSimpleName() + - " (ID " + - device.getDeviceID() + - ") \"" + - name + - "\": " + - results, - false - ); + DriverStation.reportError("[PhoenixUtil] " + name + "\": " + results, false); return false; } } diff --git a/src/main/java/org/team340/robot/Constants.java b/src/main/java/org/team340/robot/Constants.java index 86783bd..98001b4 100644 --- a/src/main/java/org/team340/robot/Constants.java +++ b/src/main/java/org/team340/robot/Constants.java @@ -18,7 +18,7 @@ public final class Constants { */ public static final class RobotMap { - public static final String kSwerveCANBus = "Swerve"; + public static final String kLowerCANBus = "LowerCAN"; public static final int kFlMove = 2; public static final int kFlTurn = 3; diff --git a/src/main/java/org/team340/robot/Robot.java b/src/main/java/org/team340/robot/Robot.java index 211ec54..9af1b8d 100644 --- a/src/main/java/org/team340/robot/Robot.java +++ b/src/main/java/org/team340/robot/Robot.java @@ -5,13 +5,15 @@ import com.ctre.phoenix6.SignalLogger; import edu.wpi.first.epilogue.Epilogue; import edu.wpi.first.epilogue.Logged; +import edu.wpi.first.epilogue.NotLogged; import edu.wpi.first.wpilibj.DataLogManager; import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.Threads; import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj2.command.CommandScheduler; import edu.wpi.first.wpilibj2.command.button.CommandXboxController; import edu.wpi.first.wpilibj2.command.button.RobotModeTriggers; -import org.team340.lib.util.GRRDashboard; +import org.team340.lib.util.DisableWatchdog; import org.team340.lib.util.Profiler; import org.team340.lib.util.Tunable; import org.team340.robot.commands.Autos; @@ -21,6 +23,8 @@ @Logged public final class Robot extends TimedRobot { + private final CommandScheduler scheduler = CommandScheduler.getInstance(); + public final Swerve swerve; public final Routines routines; @@ -31,6 +35,8 @@ public final class Robot extends TimedRobot { public Robot() { DriverStation.silenceJoystickConnectionWarning(true); + DisableWatchdog.in(scheduler, "m_watchdog"); + DisableWatchdog.in(this, "m_watchdog"); // Configure logging DataLogManager.start(); @@ -49,32 +55,47 @@ public Robot() { driver = new CommandXboxController(Constants.kDriver); coDriver = new CommandXboxController(Constants.kCoDriver); - // Set default commands - swerve.setDefaultCommand( - swerve.drive( - driver::getLeftX, - driver::getLeftY, - () -> driver.getLeftTriggerAxis() - driver.getRightTriggerAxis() - ) - ); - // Create triggers - RobotModeTriggers.autonomous().whileTrue(GRRDashboard.runSelectedAuto()); + RobotModeTriggers.autonomous().whileTrue(autos.runSelectedAuto()); // Driver bindings driver.povLeft().onTrue(swerve.tareRotation()); // Co-driver bindings coDriver.a().onTrue(none()); + + // Set thread priority + Threads.setCurrentThreadPriority(true, 10); + } + + /** + * Returns the current match time in seconds. + */ + public double matchTime() { + return Math.max(DriverStation.getMatchTime(), 0.0); + } + + @NotLogged + public double driverX() { + return driver.getLeftX(); + } + + @NotLogged + public double driverY() { + return driver.getLeftY(); + } + + @NotLogged + public double driverAngular() { + return driver.getLeftTriggerAxis() - driver.getRightTriggerAxis(); } @Override public void robotPeriodic() { - Profiler.start("RobotPeriodic"); - Profiler.run("CommandScheduler", () -> CommandScheduler.getInstance().run()); - Profiler.run("Epilogue", () -> Epilogue.update(this)); - Profiler.run("GRRDashboard", GRRDashboard::update); - Profiler.run("Tunables", Tunable::update); + Profiler.start("robotPeriodic"); + Profiler.run("scheduler", scheduler::run); + Profiler.run("epilogue", () -> Epilogue.update(this)); + Profiler.run("tunables", Tunable::update); Profiler.end(); } diff --git a/src/main/java/org/team340/robot/commands/Autos.java b/src/main/java/org/team340/robot/commands/Autos.java index 5110cf5..755f677 100644 --- a/src/main/java/org/team340/robot/commands/Autos.java +++ b/src/main/java/org/team340/robot/commands/Autos.java @@ -2,13 +2,15 @@ import static edu.wpi.first.wpilibj2.command.Commands.*; +import choreo.auto.AutoChooser; +import choreo.auto.AutoChooser; import choreo.auto.AutoFactory; import choreo.auto.AutoRoutine; import choreo.auto.AutoTrajectory; import edu.wpi.first.epilogue.Logged; import edu.wpi.first.epilogue.Logged.Strategy; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import edu.wpi.first.wpilibj2.command.Command; -import org.team340.lib.util.GRRDashboard; import org.team340.robot.Robot; import org.team340.robot.subsystems.Swerve; @@ -16,6 +18,7 @@ * The Autos class declares autonomous modes, and adds them * to the dashboard to be selected by the drive team. */ +@SuppressWarnings("unused") @Logged(strategy = Strategy.OPT_IN) public final class Autos { @@ -23,6 +26,7 @@ public final class Autos { private final Routines routines; private final AutoFactory factory; + private final AutoChooser chooser; public Autos(Robot robot) { swerve = robot.swerve; @@ -30,19 +34,26 @@ public Autos(Robot robot) { // Create the auto factory factory = new AutoFactory(swerve::getPose, swerve::resetPose, swerve::followTrajectory, true, swerve); + chooser = new AutoChooser(); // Add autonomous modes to the dashboard - GRRDashboard.setTrajectoryCache(factory.cache()); - GRRDashboard.addAuto("Example", "example", example()); + chooser.addRoutine("Example", this::example); + SmartDashboard.putData("autos", chooser); } - private Command example() { + /** + * Returns a command that when scheduled will run the currently selected auto. + */ + public Command runSelectedAuto() { + return chooser.selectedCommandScheduler(); + } + + private AutoRoutine example() { AutoRoutine routine = factory.newRoutine("Example"); - AutoTrajectory exampleTraj = routine.trajectory("example"); + AutoTrajectory exampleTraj = routine.trajectory("ExampleTrajectory"); - routine.active().onTrue(sequence(exampleTraj.resetOdometry(), exampleTraj.cmd())); - exampleTraj.done().onTrue(sequence(routines.example(), swerve.finishAuto())); + routine.active().onTrue(sequence(exampleTraj.resetOdometry(), exampleTraj.spawnCmd())); - return routine.cmd(); + return routine; } } diff --git a/src/main/java/org/team340/robot/subsystems/Swerve.java b/src/main/java/org/team340/robot/subsystems/Swerve.java index a641a32..4bc821d 100644 --- a/src/main/java/org/team340/robot/subsystems/Swerve.java +++ b/src/main/java/org/team340/robot/subsystems/Swerve.java @@ -14,6 +14,7 @@ import java.util.function.DoubleSupplier; import org.team340.lib.swerve.Perspective; import org.team340.lib.swerve.SwerveAPI; +import org.team340.lib.swerve.SwerveState; import org.team340.lib.swerve.config.SwerveConfig; import org.team340.lib.swerve.config.SwerveModuleConfig; import org.team340.lib.swerve.hardware.SwerveEncoders; @@ -30,72 +31,72 @@ @Logged public final class Swerve extends GRRSubsystem { + private static final double kMoveRatio = (54.0 / 10.0) * (18.0 / 38.0) * (45.0 / 15.0); + private static final double kTurnRatio = (22.0 / 10.0) * (88.0 / 16.0); + private static final double kModuleOffset = Units.inchesToMeters(12.5); + private static final SwerveModuleConfig kFrontLeft = new SwerveModuleConfig() .setName("frontLeft") - .setLocation(0.28, 0.28) + .setLocation(kModuleOffset, kModuleOffset) .setMoveMotor(SwerveMotors.talonFX(RobotMap.kFlMove, true)) .setTurnMotor(SwerveMotors.talonFX(RobotMap.kFlTurn, true)) - .setEncoder(SwerveEncoders.canCoder(RobotMap.kFlEncoder, 0.0, true)); + .setEncoder(SwerveEncoders.canCoder(RobotMap.kFlEncoder, 0.0, false)); private static final SwerveModuleConfig kFrontRight = new SwerveModuleConfig() .setName("frontRight") - .setLocation(0.28, -0.28) + .setLocation(kModuleOffset, -kModuleOffset) .setMoveMotor(SwerveMotors.talonFX(RobotMap.kFrMove, true)) .setTurnMotor(SwerveMotors.talonFX(RobotMap.kFrTurn, true)) - .setEncoder(SwerveEncoders.canCoder(RobotMap.kFrEncoder, 0.0, true)); + .setEncoder(SwerveEncoders.canCoder(RobotMap.kFrEncoder, 0.0, false)); private static final SwerveModuleConfig kBackLeft = new SwerveModuleConfig() .setName("backLeft") - .setLocation(-0.28, 0.28) + .setLocation(-kModuleOffset, kModuleOffset) .setMoveMotor(SwerveMotors.talonFX(RobotMap.kBlMove, true)) .setTurnMotor(SwerveMotors.talonFX(RobotMap.kBlTurn, true)) - .setEncoder(SwerveEncoders.canCoder(RobotMap.kBlEncoder, 0.0, true)); + .setEncoder(SwerveEncoders.canCoder(RobotMap.kBlEncoder, 0.0, false)); private static final SwerveModuleConfig kBackRight = new SwerveModuleConfig() .setName("backRight") - .setLocation(-0.28, -0.28) + .setLocation(-kModuleOffset, -kModuleOffset) .setMoveMotor(SwerveMotors.talonFX(RobotMap.kBrMove, true)) .setTurnMotor(SwerveMotors.talonFX(RobotMap.kBrTurn, true)) - .setEncoder(SwerveEncoders.canCoder(RobotMap.kBrEncoder, 0.0, true)); + .setEncoder(SwerveEncoders.canCoder(RobotMap.kBrEncoder, 0.0, false)); private static final SwerveConfig kConfig = new SwerveConfig() - .setTimings(TimedRobot.kDefaultPeriod, 0.004, 0.02) - .setMovePID(0.01, 0.0, 0.0) - .setMoveFF(0.05, 0.1) - .setTurnPID(0.2, 0.0, 0.1) - .setBrakeMode(true, true) - .setLimits(5.0, 13.0, 7.0, 27.5) - .setDriverProfile(4.5, 1.0, 0.15, 4.2, 2.0, 0.05) - .setPowerProperties(Constants.kVoltage, 80.0, 60.0) - .setMechanicalProperties(5.4, 12.1, 4.5, Units.inchesToMeters(4.0)) + .setTimings(TimedRobot.kDefaultPeriod, 0.004, 0.02, 0.01) + .setMovePID(0.25, 0.0, 0.0) + .setMoveFF(0.0, 0.125) + .setTurnPID(100.0, 0.0, 0.2) + .setBrakeMode(false, true) + .setLimits(4.5, 0.05, 17.5, 14.0, 30.0) + .setDriverProfile(4.0, 1.5, 0.15, 4.75, 2.0, 0.05) + .setPowerProperties(Constants.kVoltage, 100.0, 80.0, 60.0, 60.0) + .setMechanicalProperties(kMoveRatio, kTurnRatio, 0.0, Units.inchesToMeters(4.0)) .setOdometryStd(0.1, 0.1, 0.1) .setIMU(SwerveIMUs.canandgyro(RobotMap.kCanandgyro)) - .setPhoenixFeatures(new CANBus(RobotMap.kSwerveCANBus), true, true, true) + .setPhoenixFeatures(new CANBus(RobotMap.kLowerCANBus), true, true, true) .setModules(kFrontLeft, kFrontRight, kBackLeft, kBackRight); - private static final double kAutoKp = 7.0; - private static final double kAutoKi = 0.0; - private static final double kAutoKd = 0.0; - - private static final double kAutoAngularKp = 5.0; - private static final double kAutoAngularKi = 0.0; - private static final double kAutoAngularKd = 0.0; - private final SwerveAPI api; + private final SwerveState state; private final PIDController autoPIDx; private final PIDController autoPIDy; private final PIDController autoPIDangular; + @SuppressWarnings("unused") private Pose2d autoLast = null; + private Pose2d autoNext = null; public Swerve() { api = new SwerveAPI(kConfig); + state = api.state; - autoPIDx = new PIDController(kAutoKp, kAutoKi, kAutoKd); - autoPIDy = new PIDController(kAutoKp, kAutoKi, kAutoKd); - autoPIDangular = new PIDController(kAutoAngularKp, kAutoAngularKi, kAutoAngularKd); + autoPIDx = new PIDController(10.0, 0.0, 0.0); + autoPIDy = new PIDController(10.0, 0.0, 0.0); + autoPIDangular = new PIDController(10.0, 0.0, 0.0); autoPIDangular.enableContinuousInput(-Math.PI, Math.PI); api.enableTunables("swerve/api"); @@ -114,7 +115,7 @@ public void periodic() { */ @NotLogged public Pose2d getPose() { - return api.state.pose; + return state.pose; } /** @@ -155,19 +156,6 @@ public Command stop(boolean lock) { return commandBuilder("Swerve.stop(" + lock + ")").onExecute(() -> api.applyStop(lock)); } - /** - * Stops the robot from moving, and cleans up auto-related telemetry. - * This command should be ran at the end of an autonomous routine. - */ - public Command finishAuto() { - return commandBuilder("Swerve.finishAuto()") - .onInitialize(() -> { - autoLast = null; - autoNext = autoLast; - }) - .onExecute(() -> api.applyStop(false)); - } - /** * Resets the pose of the robot, inherently seeding field-relative movement. This * method is not intended for use outside of creating an {@link AutoFactory}. @@ -175,6 +163,10 @@ public Command finishAuto() { */ public void resetPose(Pose2d pose) { api.resetPose(pose); + + autoPIDx.reset(); + autoPIDy.reset(); + autoPIDangular.reset(); } /** @@ -186,7 +178,7 @@ public void followTrajectory(SwerveSample sample) { autoLast = autoNext; autoNext = sample.getPose(); - Pose2d pose = api.state.pose; + Pose2d pose = state.pose; api.applySpeeds( new ChassisSpeeds( sample.vx + autoPIDx.calculate(pose.getX(), sample.x), diff --git a/vendordeps/ChoreoLib-2025.0.1.json b/vendordeps/ChoreoLib-2025.0.3.json similarity index 88% rename from vendordeps/ChoreoLib-2025.0.1.json rename to vendordeps/ChoreoLib-2025.0.3.json index 705ad16..5a8cd54 100644 --- a/vendordeps/ChoreoLib-2025.0.1.json +++ b/vendordeps/ChoreoLib-2025.0.3.json @@ -1,7 +1,7 @@ { - "fileName": "ChoreoLib-2025.0.1.json", + "fileName": "ChoreoLib-2025.0.3.json", "name": "ChoreoLib", - "version": "2025.0.1", + "version": "2025.0.3", "uuid": "b5e23f0a-dac9-4ad2-8dd6-02767c520aca", "frcYear": "2025", "mavenUrls": [ @@ -13,7 +13,7 @@ { "groupId": "choreo", "artifactId": "ChoreoLib-java", - "version": "2025.0.1" + "version": "2025.0.3" }, { "groupId": "com.google.code.gson", @@ -26,7 +26,7 @@ { "groupId": "choreo", "artifactId": "ChoreoLib-cpp", - "version": "2025.0.1", + "version": "2025.0.3", "libName": "ChoreoLib", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/Phoenix6-25.1.0.json b/vendordeps/Phoenix6-frc2025-latest.json similarity index 80% rename from vendordeps/Phoenix6-25.1.0.json rename to vendordeps/Phoenix6-frc2025-latest.json index 473f6a8..a93cd12 100644 --- a/vendordeps/Phoenix6-25.1.0.json +++ b/vendordeps/Phoenix6-frc2025-latest.json @@ -1,7 +1,7 @@ { - "fileName": "Phoenix6-25.1.0.json", + "fileName": "Phoenix6-frc2025-latest.json", "name": "CTRE-Phoenix (v6)", - "version": "25.1.0", + "version": "25.3.1", "frcYear": "2025", "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", "mavenUrls": [ @@ -19,14 +19,14 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-java", - "version": "25.1.0" + "version": "25.3.1" } ], "jniDependencies": [ { "groupId": "com.ctre.phoenix6", "artifactId": "api-cpp", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -40,7 +40,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -54,7 +54,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "api-cpp-sim", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -68,7 +68,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -82,7 +82,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -96,7 +96,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -110,7 +110,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -124,7 +124,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -138,7 +138,21 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "25.1.0", + "version": "25.3.1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFXS", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -152,7 +166,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -166,7 +180,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "25.1.0", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -180,7 +194,21 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANrange", - "version": "25.1.0", + "version": "25.3.1", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdi", + "version": "25.3.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -196,7 +224,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-cpp", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_Phoenix6_WPI", "headerClassifier": "headers", "sharedLibrary": true, @@ -212,7 +240,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_PhoenixTools", "headerClassifier": "headers", "sharedLibrary": true, @@ -228,7 +256,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "wpiapi-cpp-sim", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_Phoenix6_WPISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -244,7 +272,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_PhoenixTools_Sim", "headerClassifier": "headers", "sharedLibrary": true, @@ -260,7 +288,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimTalonSRX", "headerClassifier": "headers", "sharedLibrary": true, @@ -276,7 +304,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimVictorSPX", "headerClassifier": "headers", "sharedLibrary": true, @@ -292,7 +320,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimPigeonIMU", "headerClassifier": "headers", "sharedLibrary": true, @@ -308,7 +336,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimCANCoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -324,7 +352,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimProTalonFX", "headerClassifier": "headers", "sharedLibrary": true, @@ -337,10 +365,26 @@ ], "simMode": "swsim" }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFXS", + "version": "25.3.1", + "libName": "CTRE_SimProTalonFXS", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimProCANcoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -356,7 +400,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimProPigeon2", "headerClassifier": "headers", "sharedLibrary": true, @@ -372,7 +416,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANrange", - "version": "25.1.0", + "version": "25.3.1", "libName": "CTRE_SimProCANrange", "headerClassifier": "headers", "sharedLibrary": true, @@ -384,6 +428,22 @@ "osxuniversal" ], "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdi", + "version": "25.3.1", + "libName": "CTRE_SimProCANdi", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" } ] } \ No newline at end of file diff --git a/vendordeps/REVLib.json b/vendordeps/REVLib.json index 86ad287..ac62be8 100644 --- a/vendordeps/REVLib.json +++ b/vendordeps/REVLib.json @@ -1,7 +1,7 @@ { "fileName": "REVLib.json", "name": "REVLib", - "version": "2025.0.1", + "version": "2025.0.3", "frcYear": "2025", "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", "mavenUrls": [ @@ -12,14 +12,14 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-java", - "version": "2025.0.1" + "version": "2025.0.3" } ], "jniDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2025.0.1", + "version": "2025.0.3", "skipInvalidPlatforms": true, "isJar": false, "validPlatforms": [ @@ -36,7 +36,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-cpp", - "version": "2025.0.1", + "version": "2025.0.3", "libName": "REVLib", "headerClassifier": "headers", "sharedLibrary": false, @@ -53,7 +53,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2025.0.1", + "version": "2025.0.3", "libName": "REVLibDriver", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/ReduxLib-2025.0.0.json b/vendordeps/ReduxLib-2025.0.1.json similarity index 90% rename from vendordeps/ReduxLib-2025.0.0.json rename to vendordeps/ReduxLib-2025.0.1.json index f15ff90..6cc750e 100644 --- a/vendordeps/ReduxLib-2025.0.0.json +++ b/vendordeps/ReduxLib-2025.0.1.json @@ -1,7 +1,7 @@ { - "fileName": "ReduxLib-2025.0.0.json", + "fileName": "ReduxLib-2025.0.1.json", "name": "ReduxLib", - "version": "2025.0.0", + "version": "2025.0.1", "frcYear": "2025", "uuid": "151ecca8-670b-4026-8160-cdd2679ef2bd", "mavenUrls": [ @@ -12,14 +12,14 @@ { "groupId": "com.reduxrobotics.frc", "artifactId": "ReduxLib-java", - "version": "2025.0.0" + "version": "2025.0.1" } ], "jniDependencies": [ { "groupId": "com.reduxrobotics.frc", "artifactId": "ReduxLib-driver", - "version": "2025.0.0", + "version": "2025.0.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -36,7 +36,7 @@ { "groupId": "com.reduxrobotics.frc", "artifactId": "ReduxLib-cpp", - "version": "2025.0.0", + "version": "2025.0.1", "libName": "ReduxLib", "headerClassifier": "headers", "sourcesClassifier": "sources", @@ -54,7 +54,7 @@ { "groupId": "com.reduxrobotics.frc", "artifactId": "ReduxLib-driver", - "version": "2025.0.0", + "version": "2025.0.1", "libName": "ReduxCore", "headerClassifier": "headers", "sharedLibrary": true, diff --git a/vendordeps/photonlib.json b/vendordeps/photonlib.json index db43d6d..1219919 100644 --- a/vendordeps/photonlib.json +++ b/vendordeps/photonlib.json @@ -1,7 +1,7 @@ { "fileName": "photonlib.json", "name": "photonlib", - "version": "v2025.0.0-beta-8", + "version": "v2025.2.1", "uuid": "515fe07e-bfc6-11fa-b3de-0242ac130004", "frcYear": "2025", "mavenUrls": [ @@ -13,7 +13,7 @@ { "groupId": "org.photonvision", "artifactId": "photontargeting-cpp", - "version": "v2025.0.0-beta-8", + "version": "v2025.2.1", "skipInvalidPlatforms": true, "isJar": false, "validPlatforms": [ @@ -28,7 +28,7 @@ { "groupId": "org.photonvision", "artifactId": "photonlib-cpp", - "version": "v2025.0.0-beta-8", + "version": "v2025.2.1", "libName": "photonlib", "headerClassifier": "headers", "sharedLibrary": true, @@ -43,7 +43,7 @@ { "groupId": "org.photonvision", "artifactId": "photontargeting-cpp", - "version": "v2025.0.0-beta-8", + "version": "v2025.2.1", "libName": "photontargeting", "headerClassifier": "headers", "sharedLibrary": true, @@ -60,12 +60,12 @@ { "groupId": "org.photonvision", "artifactId": "photonlib-java", - "version": "v2025.0.0-beta-8" + "version": "v2025.2.1" }, { "groupId": "org.photonvision", "artifactId": "photontargeting-java", - "version": "v2025.0.0-beta-8" + "version": "v2025.2.1" } ] } \ No newline at end of file