diff --git a/README.md b/README.md index 9d2ab64..f6766cc 100644 --- a/README.md +++ b/README.md @@ -32,20 +32,23 @@ If you would like to contribute to Jankboard 2, there's only a few simple steps - If you don't have access to a development environment that supports running standalone executables (eg. Github Codespaces), you can try running `npm run dev` instead of `npm run tauri dev`, which will open a development server at `localhost:5173` with the frontend running in the web. However, this may break at any time as critical functionality is more directly attached to the Rust backend. - If for some reason you need to install and use the Python backend while we are migrating to Rust, run `poetry install --no-root` in the root directory of the project to install dependencies. You can start the server with `poetry run flask --app app/server.py run --host localhost --port 1280` (it must be running at port `1280` for the frontend to detect it). -## Current progress +## Current progress and improvements over (original) Jankboard -- Basic UI layout complete -- Media player working with a few small issues -- App system working smoothly -- Camera feed likely working -- Frontend syncs basic telemetry data with robot through the same Socket-IO code that powered Jankboard v1 -- Notification service installed, with Toast and audio capability +- Layout, toasts/notifications, music player, and app system ported. +- Toast and audio cue system is much more robust +- Transitions added almost everywhere to make things smoother +- Tauri app created successfully, currently still using Flask backend +- Visualization vastly improved with Threlte (Three.js) powered 3D robot simulation +- Robot model ported successfully via massive optimization through polygon decimation +- Added settings app with options to disable certain features and developer tools for testing ## TODO - Camera cutout overlay - Overhaul audio player system -- Robot visualization (3D, in Threlte). -- Overhaul backend +- Overhaul visualization (especially camera) +- Overhaul backend in Rust - Further integrate telemetry (like GPWS, collision warning, etc) - Finish re-creating / adding various voice alerts and sequences +- Create dynamic voice prompt system to support new languages very easily +- Add dynamic voice prompt fallback to support incremental voice prompt migration diff --git a/client/package-lock.json b/client/package-lock.json index c3afd7d..26b404d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,8 +9,10 @@ "version": "0.0.0", "dependencies": { "@fontsource/roboto": "^5.0.8", + "@tauri-apps/api": "^1.5.3", "@threlte/core": "^7.1.0", "@threlte/extras": "^8.8.0", + "camera-controls": "^2.8.3", "howler": "^2.2.4", "material-icons": "^1.13.12", "material-symbols": "^0.15.0", @@ -810,6 +812,20 @@ "vite": "^5.0.0" } }, + "node_modules/@tauri-apps/api": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.5.3.tgz", + "integrity": "sha512-zxnDjHHKjOsrIzZm6nO5Xapb/BxqUq1tc7cGkFXsFkGTsSWgCPH1D8mm0XS9weJY2OaR73I3k3S+b7eSzJDfqA==", + "engines": { + "node": ">= 14.6.0", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, "node_modules/@tauri-apps/cli": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.5.10.tgz", @@ -1300,6 +1316,14 @@ "node": ">= 6" } }, + "node_modules/camera-controls": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.8.3.tgz", + "integrity": "sha512-zFjqUR6onLkG+z1A6vAWfzovxZxWVSvp6e5t3lfZgfgPZtX3n74aykNAUaoRbq8Y3tOxadHkDjbfGDOP9hFf2w==", + "peerDependencies": { + "three": ">=0.126.1" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001588", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", diff --git a/client/package.json b/client/package.json index 634fbe2..4b054fa 100644 --- a/client/package.json +++ b/client/package.json @@ -29,8 +29,10 @@ }, "dependencies": { "@fontsource/roboto": "^5.0.8", + "@tauri-apps/api": "^1.5.3", "@threlte/core": "^7.1.0", "@threlte/extras": "^8.8.0", + "camera-controls": "^2.8.3", "howler": "^2.2.4", "material-icons": "^1.13.12", "material-symbols": "^0.15.0", diff --git a/client/public/static/voices/en/adaptive-cruise-control.wav b/client/public/static/voices/en-US/adaptive-cruise-control.wav similarity index 100% rename from client/public/static/voices/en/adaptive-cruise-control.wav rename to client/public/static/voices/en-US/adaptive-cruise-control.wav diff --git a/client/public/static/voices/en/all-brakes-disengaged.wav b/client/public/static/voices/en-US/all-brakes-disengaged.wav similarity index 100% rename from client/public/static/voices/en/all-brakes-disengaged.wav rename to client/public/static/voices/en-US/all-brakes-disengaged.wav diff --git a/client/public/static/voices/en/autonomous-period-started.wav b/client/public/static/voices/en-US/autonomous-period-started.wav similarity index 100% rename from client/public/static/voices/en/autonomous-period-started.wav rename to client/public/static/voices/en-US/autonomous-period-started.wav diff --git a/client/public/static/voices/en/battery-critically-low.wav b/client/public/static/voices/en-US/battery-critically-low.wav similarity index 100% rename from client/public/static/voices/en/battery-critically-low.wav rename to client/public/static/voices/en-US/battery-critically-low.wav diff --git a/client/public/static/voices/en/battery-faults-detected.wav b/client/public/static/voices/en-US/battery-faults-detected.wav similarity index 100% rename from client/public/static/voices/en/battery-faults-detected.wav rename to client/public/static/voices/en-US/battery-faults-detected.wav diff --git a/client/public/static/voices/en/battery-good.wav b/client/public/static/voices/en-US/battery-good.wav similarity index 100% rename from client/public/static/voices/en/battery-good.wav rename to client/public/static/voices/en-US/battery-good.wav diff --git a/client/public/static/voices/en/battery-low.wav b/client/public/static/voices/en-US/battery-low.wav similarity index 100% rename from client/public/static/voices/en/battery-low.wav rename to client/public/static/voices/en-US/battery-low.wav diff --git a/client/public/static/voices/en/breaching-1323-mainframe.wav b/client/public/static/voices/en-US/breaching-1323-mainframe.wav similarity index 100% rename from client/public/static/voices/en/breaching-1323-mainframe.wav rename to client/public/static/voices/en-US/breaching-1323-mainframe.wav diff --git a/client/public/static/voices/en/breaching-254-mainframe.wav b/client/public/static/voices/en-US/breaching-254-mainframe.wav similarity index 100% rename from client/public/static/voices/en/breaching-254-mainframe.wav rename to client/public/static/voices/en-US/breaching-254-mainframe.wav diff --git a/client/public/static/voices/en/breaching-fms.wav b/client/public/static/voices/en-US/breaching-fms.wav similarity index 100% rename from client/public/static/voices/en/breaching-fms.wav rename to client/public/static/voices/en-US/breaching-fms.wav diff --git a/client/public/static/voices/en/breaching-monte-vista.wav b/client/public/static/voices/en-US/breaching-monte-vista.wav similarity index 100% rename from client/public/static/voices/en/breaching-monte-vista.wav rename to client/public/static/voices/en-US/breaching-monte-vista.wav diff --git a/client/public/static/voices/en/bullying-rohan.wav b/client/public/static/voices/en-US/bullying-rohan.wav similarity index 100% rename from client/public/static/voices/en/bullying-rohan.wav rename to client/public/static/voices/en-US/bullying-rohan.wav diff --git a/client/public/static/voices/en/bypassing-coprocessor-restrictions.wav b/client/public/static/voices/en-US/bypassing-coprocessor-restrictions.wav similarity index 100% rename from client/public/static/voices/en/bypassing-coprocessor-restrictions.wav rename to client/public/static/voices/en-US/bypassing-coprocessor-restrictions.wav diff --git a/client/public/static/voices/en/collision-detected.wav b/client/public/static/voices/en-US/collision-detected.wav similarity index 100% rename from client/public/static/voices/en/collision-detected.wav rename to client/public/static/voices/en-US/collision-detected.wav diff --git a/client/public/static/voices/en/collision-imminent.wav b/client/public/static/voices/en-US/collision-imminent.wav similarity index 100% rename from client/public/static/voices/en/collision-imminent.wav rename to client/public/static/voices/en-US/collision-imminent.wav diff --git a/client/public/static/voices/en/critical-robot-failure.wav b/client/public/static/voices/en-US/critical-robot-failure.wav similarity index 100% rename from client/public/static/voices/en/critical-robot-failure.wav rename to client/public/static/voices/en-US/critical-robot-failure.wav diff --git a/client/public/static/voices/en/cruise-control-engaged.wav b/client/public/static/voices/en-US/cruise-control-engaged.wav similarity index 100% rename from client/public/static/voices/en/cruise-control-engaged.wav rename to client/public/static/voices/en-US/cruise-control-engaged.wav diff --git a/client/public/static/voices/en/deep-bozo-buffering.wav b/client/public/static/voices/en-US/deep-bozo-buffering.wav similarity index 100% rename from client/public/static/voices/en/deep-bozo-buffering.wav rename to client/public/static/voices/en-US/deep-bozo-buffering.wav diff --git a/client/public/static/voices/en/deep-bozo-has-detected.wav b/client/public/static/voices/en-US/deep-bozo-has-detected.wav similarity index 100% rename from client/public/static/voices/en/deep-bozo-has-detected.wav rename to client/public/static/voices/en-US/deep-bozo-has-detected.wav diff --git a/client/public/static/voices/en/deep-bozo-has-sentience.wav b/client/public/static/voices/en-US/deep-bozo-has-sentience.wav similarity index 100% rename from client/public/static/voices/en/deep-bozo-has-sentience.wav rename to client/public/static/voices/en-US/deep-bozo-has-sentience.wav diff --git a/client/public/static/voices/en/doom-engaged.wav b/client/public/static/voices/en-US/doom-engaged.wav similarity index 100% rename from client/public/static/voices/en/doom-engaged.wav rename to client/public/static/voices/en-US/doom-engaged.wav diff --git a/client/public/static/voices/en/downloading-copyrighted-music.wav b/client/public/static/voices/en-US/downloading-copyrighted-music.wav similarity index 100% rename from client/public/static/voices/en/downloading-copyrighted-music.wav rename to client/public/static/voices/en-US/downloading-copyrighted-music.wav diff --git a/client/public/static/voices/en/e-brakes-engaged.wav b/client/public/static/voices/en-US/e-brakes-engaged.wav similarity index 100% rename from client/public/static/voices/en/e-brakes-engaged.wav rename to client/public/static/voices/en-US/e-brakes-engaged.wav diff --git a/client/public/static/voices/en/e-stop-and.wav b/client/public/static/voices/en-US/e-stop-and.wav similarity index 100% rename from client/public/static/voices/en/e-stop-and.wav rename to client/public/static/voices/en-US/e-stop-and.wav diff --git a/client/public/static/voices/en/e-stop-automatically.wav b/client/public/static/voices/en-US/e-stop-automatically.wav similarity index 100% rename from client/public/static/voices/en/e-stop-automatically.wav rename to client/public/static/voices/en-US/e-stop-automatically.wav diff --git a/client/public/static/voices/en/e-stop-temporarily.wav b/client/public/static/voices/en-US/e-stop-temporarily.wav similarity index 100% rename from client/public/static/voices/en/e-stop-temporarily.wav rename to client/public/static/voices/en-US/e-stop-temporarily.wav diff --git a/client/public/static/voices/en/emergency-speedbost-engaged.wav b/client/public/static/voices/en-US/emergency-speedbost-engaged.wav similarity index 100% rename from client/public/static/voices/en/emergency-speedbost-engaged.wav rename to client/public/static/voices/en-US/emergency-speedbost-engaged.wav diff --git a/client/public/static/voices/en/follow-mode-engaged.wav b/client/public/static/voices/en-US/follow-mode-engaged.wav similarity index 100% rename from client/public/static/voices/en/follow-mode-engaged.wav rename to client/public/static/voices/en-US/follow-mode-engaged.wav diff --git a/client/public/static/voices/en/full-self-driving-automatically.wav b/client/public/static/voices/en-US/full-self-driving-automatically.wav similarity index 100% rename from client/public/static/voices/en/full-self-driving-automatically.wav rename to client/public/static/voices/en-US/full-self-driving-automatically.wav diff --git a/client/public/static/voices/en/full-self-driving-disengaged.wav b/client/public/static/voices/en-US/full-self-driving-disengaged.wav similarity index 100% rename from client/public/static/voices/en/full-self-driving-disengaged.wav rename to client/public/static/voices/en-US/full-self-driving-disengaged.wav diff --git a/client/public/static/voices/en/full-self-driving-engaged.wav b/client/public/static/voices/en-US/full-self-driving-engaged.wav similarity index 100% rename from client/public/static/voices/en/full-self-driving-engaged.wav rename to client/public/static/voices/en-US/full-self-driving-engaged.wav diff --git a/client/public/static/voices/en/full-self-driving-refuses.wav b/client/public/static/voices/en-US/full-self-driving-refuses.wav similarity index 100% rename from client/public/static/voices/en/full-self-driving-refuses.wav rename to client/public/static/voices/en-US/full-self-driving-refuses.wav diff --git a/client/public/static/voices/en/hello-virtual-assistant.wav b/client/public/static/voices/en-US/hello-virtual-assistant.wav similarity index 100% rename from client/public/static/voices/en/hello-virtual-assistant.wav rename to client/public/static/voices/en-US/hello-virtual-assistant.wav diff --git a/client/public/static/voices/en/infotainment-system-buffering.wav b/client/public/static/voices/en-US/infotainment-system-buffering.wav similarity index 100% rename from client/public/static/voices/en/infotainment-system-buffering.wav rename to client/public/static/voices/en-US/infotainment-system-buffering.wav diff --git a/client/public/static/voices/en/infotainment-system-online.wav b/client/public/static/voices/en-US/infotainment-system-online.wav similarity index 100% rename from client/public/static/voices/en/infotainment-system-online.wav rename to client/public/static/voices/en-US/infotainment-system-online.wav diff --git a/client/public/static/voices/en/jankboard-initialized.wav b/client/public/static/voices/en-US/jankboard-initialized.wav similarity index 100% rename from client/public/static/voices/en/jankboard-initialized.wav rename to client/public/static/voices/en-US/jankboard-initialized.wav diff --git a/client/public/static/voices/en/littenos-is-online.wav b/client/public/static/voices/en-US/littenos-is-online.wav similarity index 100% rename from client/public/static/voices/en/littenos-is-online.wav rename to client/public/static/voices/en-US/littenos-is-online.wav diff --git a/client/public/static/voices/en/loading-pirated-nintendo.wav b/client/public/static/voices/en-US/loading-pirated-nintendo.wav similarity index 100% rename from client/public/static/voices/en/loading-pirated-nintendo.wav rename to client/public/static/voices/en-US/loading-pirated-nintendo.wav diff --git a/client/public/static/voices/en/max-vestrapren-do.wav b/client/public/static/voices/en-US/max-vestrapren-do.wav similarity index 100% rename from client/public/static/voices/en/max-vestrapren-do.wav rename to client/public/static/voices/en-US/max-vestrapren-do.wav diff --git a/client/public/static/voices/en/neutral-brakes-disengaged.wav b/client/public/static/voices/en-US/neutral-brakes-disengaged.wav similarity index 100% rename from client/public/static/voices/en/neutral-brakes-disengaged.wav rename to client/public/static/voices/en-US/neutral-brakes-disengaged.wav diff --git a/client/public/static/voices/en/overspeed.wav b/client/public/static/voices/en-US/overspeed.wav similarity index 100% rename from client/public/static/voices/en/overspeed.wav rename to client/public/static/voices/en-US/overspeed.wav diff --git a/client/public/static/voices/en/parked-brakes-engaged.wav b/client/public/static/voices/en-US/parked-brakes-engaged.wav similarity index 100% rename from client/public/static/voices/en/parked-brakes-engaged.wav rename to client/public/static/voices/en-US/parked-brakes-engaged.wav diff --git a/client/public/static/voices/en/rapid-deceleration-detected.wav b/client/public/static/voices/en-US/rapid-deceleration-detected.wav similarity index 100% rename from client/public/static/voices/en/rapid-deceleration-detected.wav rename to client/public/static/voices/en-US/rapid-deceleration-detected.wav diff --git a/client/public/static/voices/en/rapidly-approaching-speed.wav b/client/public/static/voices/en-US/rapidly-approaching-speed.wav similarity index 100% rename from client/public/static/voices/en/rapidly-approaching-speed.wav rename to client/public/static/voices/en-US/rapidly-approaching-speed.wav diff --git a/client/public/static/voices/en/retard.wav b/client/public/static/voices/en-US/retard.wav similarity index 100% rename from client/public/static/voices/en/retard.wav rename to client/public/static/voices/en-US/retard.wav diff --git a/client/public/static/voices/en/reverse.wav b/client/public/static/voices/en-US/reverse.wav similarity index 100% rename from client/public/static/voices/en/reverse.wav rename to client/public/static/voices/en-US/reverse.wav diff --git a/client/public/static/voices/en/self-destruct-countdown.wav b/client/public/static/voices/en-US/self-destruct-countdown.wav similarity index 100% rename from client/public/static/voices/en/self-destruct-countdown.wav rename to client/public/static/voices/en-US/self-destruct-countdown.wav diff --git a/client/public/static/voices/en/sentry-mode-engaged.wav b/client/public/static/voices/en-US/sentry-mode-engaged.wav similarity index 100% rename from client/public/static/voices/en/sentry-mode-engaged.wav rename to client/public/static/voices/en-US/sentry-mode-engaged.wav diff --git a/client/public/static/voices/en/set-acceleration-profile-chill.wav b/client/public/static/voices/en-US/set-acceleration-profile-chill.wav similarity index 100% rename from client/public/static/voices/en/set-acceleration-profile-chill.wav rename to client/public/static/voices/en-US/set-acceleration-profile-chill.wav diff --git a/client/public/static/voices/en/set-acceleration-profile-ludicrous.wav b/client/public/static/voices/en-US/set-acceleration-profile-ludicrous.wav similarity index 100% rename from client/public/static/voices/en/set-acceleration-profile-ludicrous.wav rename to client/public/static/voices/en-US/set-acceleration-profile-ludicrous.wav diff --git a/client/public/static/voices/en/shifted-into-automatic.wav b/client/public/static/voices/en-US/shifted-into-automatic.wav similarity index 100% rename from client/public/static/voices/en/shifted-into-automatic.wav rename to client/public/static/voices/en-US/shifted-into-automatic.wav diff --git a/client/public/static/voices/en/shifted-into-drive.wav b/client/public/static/voices/en-US/shifted-into-drive.wav similarity index 100% rename from client/public/static/voices/en/shifted-into-drive.wav rename to client/public/static/voices/en-US/shifted-into-drive.wav diff --git a/client/public/static/voices/en/shifted-into-low.wav b/client/public/static/voices/en-US/shifted-into-low.wav similarity index 100% rename from client/public/static/voices/en/shifted-into-low.wav rename to client/public/static/voices/en-US/shifted-into-low.wav diff --git a/client/public/static/voices/en/shut-down-sequence.wav b/client/public/static/voices/en-US/shut-down-sequence.wav similarity index 100% rename from client/public/static/voices/en/shut-down-sequence.wav rename to client/public/static/voices/en-US/shut-down-sequence.wav diff --git a/client/public/static/voices/en/teleoperated-period-started.wav b/client/public/static/voices/en-US/teleoperated-period-started.wav similarity index 100% rename from client/public/static/voices/en/teleoperated-period-started.wav rename to client/public/static/voices/en-US/teleoperated-period-started.wav diff --git a/client/public/static/voices/en/terrain-pull-up.wav b/client/public/static/voices/en-US/terrain-pull-up.wav similarity index 100% rename from client/public/static/voices/en/terrain-pull-up.wav rename to client/public/static/voices/en-US/terrain-pull-up.wav diff --git a/client/public/static/voices/en/user-error-detected.wav b/client/public/static/voices/en-US/user-error-detected.wav similarity index 100% rename from client/public/static/voices/en/user-error-detected.wav rename to client/public/static/voices/en-US/user-error-detected.wav diff --git a/client/src-tauri/Cargo.lock b/client/src-tauri/Cargo.lock index a771765..e0ab805 100644 --- a/client/src-tauri/Cargo.lock +++ b/client/src-tauri/Cargo.lock @@ -66,10 +66,14 @@ checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" name = "app" version = "0.1.0" dependencies = [ + "network-tables", "serde", "serde_json", "tauri", "tauri-build", + "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -788,6 +792,12 @@ dependencies = [ "syn 2.0.50", ] +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "futures-task" version = "0.3.30" @@ -802,6 +812,7 @@ checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-macro", + "futures-sink", "futures-task", "pin-project-lite", "pin-utils", @@ -1184,6 +1195,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -1528,6 +1545,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "ndk" version = "0.6.0" @@ -1556,6 +1584,26 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "network-tables" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d34242b4ee3505f5d9f6eeb8cc409cfa1f18a517825c78e6001fe304c3977b" +dependencies = [ + "futures-util", + "parking_lot", + "rand 0.8.5", + "rmp", + "rmp-serde", + "rmpv", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", + "tracing", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -1721,6 +1769,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2142,6 +2196,40 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rmpv" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0e0214a4a2b444ecce41a4025792fc31f77c7bb89c46d253953ea8c65701ec" +dependencies = [ + "num-traits", + "rmp", + "serde", + "serde_bytes", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2247,6 +2335,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.197" @@ -2352,6 +2449,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2372,6 +2480,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2399,6 +2516,16 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "soup2" version = "0.2.1" @@ -2895,8 +3022,38 @@ checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", + "libc", + "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", ] [[package]] @@ -3037,6 +3194,25 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/client/src-tauri/Cargo.toml b/client/src-tauri/Cargo.toml index fe4aa80..dcf8132 100644 --- a/client/src-tauri/Cargo.toml +++ b/client/src-tauri/Cargo.toml @@ -18,6 +18,10 @@ tauri-build = { version = "1.5.1", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.6.0", features = [] } +tokio = { version = "1.23.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } +network-tables = { version = "=0.1.3", features = ["client-v4"] } [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/client/src-tauri/build.rs b/client/src-tauri/build.rs index 795b9b7..d860e1e 100644 --- a/client/src-tauri/build.rs +++ b/client/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/client/src-tauri/src/main.rs b/client/src-tauri/src/main.rs index f5c5be2..a9aca44 100644 --- a/client/src-tauri/src/main.rs +++ b/client/src-tauri/src/main.rs @@ -1,8 +1,30 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -fn main() { - tauri::Builder::default() - .run(tauri::generate_context!()) - .expect("error while running tauri application"); +use tauri::Manager; +mod telemetry; + +#[derive(Clone, serde::Serialize)] +struct Payload { + message: String, +} + +fn main() { + let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); + + rt.block_on(async { + tauri::Builder::default() + .setup(|app| { + // create app handle and send it to our event listeners + let app_handle = app.app_handle(); + + tokio::spawn(async move { + crate::telemetry::subscribe_topics(app_handle.clone()).await; + }); + + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("failed to run app") + }) } diff --git a/client/src-tauri/src/telemetry.rs b/client/src-tauri/src/telemetry.rs new file mode 100644 index 0000000..3be2175 --- /dev/null +++ b/client/src-tauri/src/telemetry.rs @@ -0,0 +1,75 @@ +use network_tables::v4::client_config::Config; +use network_tables::v4::{Client, SubscriptionOptions}; +use serde_json::to_string; +use std::net::{Ipv4Addr, SocketAddrV4}; +use tauri::{AppHandle, Manager}; +use tokio::time::{sleep, Duration}; + +const NTABLE_IP: (u8, u8, u8, u8) = (10, 12, 80, 2); +const NTABLE_PORT: u16 = 5810; + +pub async fn subscribe_topics(app_handle: AppHandle) { + loop { + // I hope this doesn't lead to a catastrophic infinite loop failure + let client = loop { + match Client::try_new_w_config( + SocketAddrV4::new( + Ipv4Addr::new(NTABLE_IP.0, NTABLE_IP.1, NTABLE_IP.2, NTABLE_IP.3), + NTABLE_PORT, + ), + Config { + ..Default::default() + }, + ) + .await + { + Ok(client) => { + println!("Client created"); + app_handle + .emit_all("telemetry_connected", "connected") + .expect("Failed to emit telemetry_status connected event"); + break client; // Exit the loop if the client is successfully created + } + Err(e) => { + println!("Failed to create client: {}. Retrying in 3 seconds...", e); + app_handle + .emit_all("telemetry_status", "disconnected") + .expect("Failed to emit telemetry_status disconnected event"); + + sleep(Duration::from_secs(3)).await; // Wait for 3 seconds before retrying + continue; // Continue the loop to retry + } + }; + }; + + let mut subscription = client + .subscribe_w_options( + &["/SmartDashboard"], + Some(SubscriptionOptions { + all: Some(true), + prefix: Some(true), + ..Default::default() + }), + ) + .await + .expect("Failed to subscribe"); + while let Some(message) = subscription.next().await { + let mut modified_message = message.clone(); + + if let Some(stripped) = modified_message.topic_name.strip_prefix("/SmartDashboard/") { + modified_message.topic_name = stripped.to_string(); + } + + let json_message = to_string(&modified_message).expect("Failed to serialize message"); + app_handle + .emit_all("telemetry_data", json_message.clone()) + .expect("Failed to send telemetry message"); + + println!("{}", json_message); + } + println!("disconnected"); + app_handle + .emit_all("telemetry_status", "disconnected") + .expect("Failed to emit telemetry_disconnected event"); + } +} diff --git a/client/src/App.svelte b/client/src/App.svelte index e534fa0..8c9aa48 100644 --- a/client/src/App.svelte +++ b/client/src/App.svelte @@ -6,13 +6,14 @@ import AppBar from './lib/Apps/AppBar.svelte' import { appList } from './lib/Apps/appList' import { initializeTelemetry } from './lib/utils/initializeTelemetry' - import { onMount } from 'svelte' + import { onDestroy, onMount } from 'svelte' import { Toaster } from 'svelte-french-toast' import { initializationSequence } from './lib/Sequences/sequences' import Loading from './lib/Loading/Loading.svelte' import { settingsStore } from './lib/stores/settingsStore' import getSettings from './lib/utils/getSettings' import { Canvas } from '@threlte/core' + import { emit } from '@tauri-apps/api/event' let activeApp: App = 'camera' let topics: TelemetryTopics = { @@ -32,6 +33,7 @@ } let loading = $settingsStore.fastStartup ? false : true + let unlistenAll: () => void onMount(() => { let savedSettings = getSettings() @@ -40,11 +42,21 @@ } window.ResizeObserver = ResizeObserver // disabled while migrating away from python - initializeTelemetry(topics, 200) + initializeTelemetry(topics, 200).then((unsubFunction: () => void) => { + unlistenAll = unsubFunction + }) setTimeout(() => { loading = false initializationSequence() }, 3000) + + settingsStore.subscribe(value => { + localStorage.setItem('settings', JSON.stringify(value)) + }) + }) + + onDestroy(() => { + unlistenAll && unlistenAll() }) diff --git a/client/src/app.css b/client/src/app.css index f7782b4..2898b55 100644 --- a/client/src/app.css +++ b/client/src/app.css @@ -21,6 +21,8 @@ overflow: auto; /* or 'scroll' if you always want scrollability */ scrollbar-width: none; /* Hide scrollbar for Firefox */ -ms-overflow-style: none; /* Hide scrollbar for IE 10+ and Edge */ + + overscroll-behavior: none; } body::-webkit-scrollbar { display: none; diff --git a/client/src/globals.d.ts b/client/src/globals.d.ts index 2a3f57e..444d7c6 100644 --- a/client/src/globals.d.ts +++ b/client/src/globals.d.ts @@ -49,11 +49,12 @@ interface TelemetryData { 'jerk-x': number 'jerk-y': number 'voltage': number - 'acc-profile': Mode | '-999' - 'gear': Gear | '-999' + 'acc-profile': Mode + 'gear': Gear 'ebrake': boolean 'reorient': boolean 'gpws': boolean + 'connected': boolean } type CardinalDirection = diff --git a/client/src/lib/Apps/DevTools/DevTools.svelte b/client/src/lib/Apps/DevTools/DevTools.svelte index f75c868..6ee8aec 100644 --- a/client/src/lib/Apps/DevTools/DevTools.svelte +++ b/client/src/lib/Apps/DevTools/DevTools.svelte @@ -1,4 +1,9 @@ Simulate random motion + diff --git a/client/src/lib/Dashboard/Speedometer.svelte b/client/src/lib/Dashboard/Speedometer.svelte index 3895744..b3a2a59 100644 --- a/client/src/lib/Dashboard/Speedometer.svelte +++ b/client/src/lib/Dashboard/Speedometer.svelte @@ -2,6 +2,7 @@ @component @param speed - Speed in meters per second + @param placeholder - Whether or not to show the placeholder skeleton Displays the speed in miles per hour --> @@ -10,10 +11,9 @@ import { mps2mph } from '../utils/unitConversions' export let speed: number = 0.0 + export let placeholder: boolean $: formatted = mps2mph(speed).toFixed(1) - - $: placeholder = speed === Math.hypot(-999, -999)
@@ -24,10 +24,5 @@ > {placeholder ? '-----' : formatted}
-
- MPH -
+
MPH
diff --git a/client/src/lib/Dashboard/TopBar/BatteryDisplay.svelte b/client/src/lib/Dashboard/TopBar/BatteryDisplay.svelte index 26f1af3..ba756cf 100644 --- a/client/src/lib/Dashboard/TopBar/BatteryDisplay.svelte +++ b/client/src/lib/Dashboard/TopBar/BatteryDisplay.svelte @@ -1,9 +1,8 @@ diff --git a/client/src/lib/Dashboard/TopBar/GearSelector.svelte b/client/src/lib/Dashboard/TopBar/GearSelector.svelte index a1bdac0..89fb5d3 100644 --- a/client/src/lib/Dashboard/TopBar/GearSelector.svelte +++ b/client/src/lib/Dashboard/TopBar/GearSelector.svelte @@ -1,18 +1,53 @@
-
P
-
R
-
N
-
L
-
A
-
D
+
P
+
R
+
N
+
L
+
A
+
D
diff --git a/client/src/lib/Dashboard/TopBar/ModeSelector.svelte b/client/src/lib/Dashboard/TopBar/ModeSelector.svelte index adb2e18..e2c6d89 100644 --- a/client/src/lib/Dashboard/TopBar/ModeSelector.svelte +++ b/client/src/lib/Dashboard/TopBar/ModeSelector.svelte @@ -2,29 +2,45 @@ @component @param selectedMode - Selected mode + @param placeholder - Whether or not to show the placeholder skeleton Displays the drive mode --> diff --git a/client/src/lib/Dashboard/TopBar/TopBar.svelte b/client/src/lib/Dashboard/TopBar/TopBar.svelte index ec644e3..22d4b2a 100644 --- a/client/src/lib/Dashboard/TopBar/TopBar.svelte +++ b/client/src/lib/Dashboard/TopBar/TopBar.svelte @@ -4,6 +4,7 @@ @param selectedGear - Selected gear @param selectedMode - Selected mode @param voltage - Battery voltage + @param placeholder - Whether or not to show placeholder skeleton UIs Displays the top bar of the dashboard --> @@ -13,19 +14,20 @@ import GearSelector from './GearSelector.svelte' import ModeSelector from './ModeSelector.svelte' - export let selectedGear: Gear | '-999' - export let selectedMode: Mode | '-999' + export let selectedGear: Gear + export let selectedMode: Mode export let voltage: number + export let placeholder: boolean
- +
- +
- +
diff --git a/client/src/lib/Dashboard/Visualization/CameraControls/CameraControls.svelte b/client/src/lib/Dashboard/Visualization/CameraControls/CameraControls.svelte new file mode 100644 index 0000000..d518d54 --- /dev/null +++ b/client/src/lib/Dashboard/Visualization/CameraControls/CameraControls.svelte @@ -0,0 +1,103 @@ + + + + + { + autoRotate = false + }} + on:controlend={() => { + autoRotate = true + }} + {...$$restProps} + bind:this={$forwardingComponent} +> + + diff --git a/client/src/lib/Dashboard/Visualization/CameraControls/CameraControls.svelte.d.ts b/client/src/lib/Dashboard/Visualization/CameraControls/CameraControls.svelte.d.ts new file mode 100644 index 0000000..6bc8f0e --- /dev/null +++ b/client/src/lib/Dashboard/Visualization/CameraControls/CameraControls.svelte.d.ts @@ -0,0 +1,16 @@ +import type { Events, Props, Slots } from '@threlte/core' +import CC from 'camera-controls' +import type { SvelteComponent } from 'svelte' + +export type CameraControlsProps = Props & { + autoRotate?: boolean + autoRotateSpeed?: number +} +export type CameraControlsEvents = Events +export type CameraControlsSlots = Slots + +export default class CameraControls extends SvelteComponent< + CameraControlsProps, + CameraControlsEvents, + CameraControlsSlots +> {} diff --git a/client/src/lib/Dashboard/Visualization/CameraControls/utils/cameraStore.ts b/client/src/lib/Dashboard/Visualization/CameraControls/utils/cameraStore.ts new file mode 100644 index 0000000..0406984 --- /dev/null +++ b/client/src/lib/Dashboard/Visualization/CameraControls/utils/cameraStore.ts @@ -0,0 +1,33 @@ +import type CameraControls from 'camera-controls' +import { writable } from 'svelte/store' +import type { Mesh } from 'three' + +export const cameraControls = writable() +export const mesh = writable() + +type CameraMode = + | 'orbit' + | 'follow-facing' + | 'follow-direction' + | 'follow-position' + | 'showcase' + +interface CameraState { + mode: CameraMode +} + +const { set, update, subscribe } = writable({ + mode: 'orbit', +}) + +const createCameraState = () => { + return { + update, + subscribe, + set: (prop: keyof CameraState, val: any) => + update(state => ({ ...state, [prop]: val })), + reset: () => set({ mode: 'orbit' }), + } +} + +export const cameraState = createCameraState() diff --git a/client/src/lib/Dashboard/Visualization/CameraControls/utils/useControlsContext.ts b/client/src/lib/Dashboard/Visualization/CameraControls/utils/useControlsContext.ts new file mode 100644 index 0000000..c8862b9 --- /dev/null +++ b/client/src/lib/Dashboard/Visualization/CameraControls/utils/useControlsContext.ts @@ -0,0 +1,20 @@ +import { useThrelteUserContext } from '@threlte/core' +import { writable, type Writable } from 'svelte/store' +import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' + +type ControlsContext = { + orbitControls: Writable +} + +/** + * ### `useControlsContext` + * + * This hook is used to register the `OrbitControls` instance with the + * `ControlsContext`. We're using this context to enable and disable the + * controls when the user is interacting with the TransformControls. + */ +export const useControlsContext = (): ControlsContext => { + return useThrelteUserContext('threlte-controls', { + orbitControls: writable(undefined), + }) +} diff --git a/client/src/lib/Dashboard/Visualization/Controls.svelte b/client/src/lib/Dashboard/Visualization/Controls.svelte deleted file mode 100644 index 64e3ed0..0000000 --- a/client/src/lib/Dashboard/Visualization/Controls.svelte +++ /dev/null @@ -1,165 +0,0 @@ - diff --git a/client/src/lib/Dashboard/Visualization/Scene.svelte b/client/src/lib/Dashboard/Visualization/Scene.svelte index b60c18e..be0ab7e 100644 --- a/client/src/lib/Dashboard/Visualization/Scene.svelte +++ b/client/src/lib/Dashboard/Visualization/Scene.svelte @@ -1,155 +1,154 @@ - - + { + $cameraControls = ref + }} + autoRotateSpeed={3} /> - - - + + + + { + // @ts-expect-error + $mesh = ref + }} +/> - - - - diff --git a/client/src/lib/Dashboard/Visualization/smoothMotionController.ts b/client/src/lib/Dashboard/Visualization/smoothMotionController.ts deleted file mode 100644 index ee296c2..0000000 --- a/client/src/lib/Dashboard/Visualization/smoothMotionController.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Vector2 } from 'three' - -interface Velocity { - x: number - y: number -} - -export class SmoothMotionController { - private currentPosition: Vector2 - private currentVelocity: Vector2 - private targetVelocity: Velocity - private dampingFactor: number - - constructor( - initialPosition: Vector2, - initialVelocity: Velocity, - dampingFactor: number = 0.1 - ) { - this.currentPosition = initialPosition - this.currentVelocity = new Vector2(initialVelocity.x, initialVelocity.y) - this.targetVelocity = { ...initialVelocity } - this.dampingFactor = dampingFactor - } - - setTargetVelocity(velocity: Velocity) { - this.targetVelocity = velocity - } - - update(delta: number) { - // Apply cubic interpolation to smoothly transition the current velocity towards the target velocity - this.currentVelocity.x += - (this.targetVelocity.x - this.currentVelocity.x) * - this.dampingFactor * - delta - this.currentVelocity.y += - (this.targetVelocity.y - this.currentVelocity.y) * - this.dampingFactor * - delta - - // Update position based on the current velocity and the time delta - this.currentPosition.x += this.currentVelocity.x * delta * 3 - this.currentPosition.y += this.currentVelocity.y * delta * 3 - } - - getPosition(): Vector2 { - return this.currentPosition - } - - public reset() { - this.currentPosition = new Vector2(0, 0) - this.currentVelocity = new Vector2(0, 0) - this.targetVelocity = { x: 0, y: 0 } - } -} diff --git a/client/src/lib/Dashboard/Visualization/utils.ts b/client/src/lib/Dashboard/Visualization/utils.ts deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/lib/Notifications/notifications.ts b/client/src/lib/Notifications/notifications.ts index 3cf2338..7262e74 100644 --- a/client/src/lib/Notifications/notifications.ts +++ b/client/src/lib/Notifications/notifications.ts @@ -1,113 +1,147 @@ -import { toast } from 'svelte-french-toast' -import type { ToastOptions } from 'svelte-french-toast' -import InfoIcon from './InfoIcon.svelte' -import { Howl } from 'howler' -import WarnIcon from './WarnIcon.svelte' +import { toast } from "svelte-french-toast"; +import type { ToastOptions } from "svelte-french-toast"; +import InfoIcon from "./InfoIcon.svelte"; +import { Howl } from "howler"; +import WarnIcon from "./WarnIcon.svelte"; interface NotificationOptions extends ToastOptions { - withAudio?: boolean - src?: string - onComplete?: () => void + withAudio?: boolean; + src?: string; + onComplete?: () => void; } // get colors from https://tailwindcss.com/docs/customizing-colors export class Notifications { - private static readonly defaultDuration = 3000 + private static readonly defaultDuration = 3000; public static success(message: string, options?: NotificationOptions) { if (options?.withAudio && !options.src) - throw new Error('No audio source provided') + throw new Error("No audio source provided"); const onComplete = () => { - if (options?.onComplete) options.onComplete() - } + if (options?.onComplete) options.onComplete(); + }; const sendToast = (duration: number) => { toast.success(message, { style: - 'padding: 25px; font-size: 1.5rem; background-color: #15803d; color: #fafafa; gap: 0.5rem; user-select: none; max-width: 70vw;', + "padding: 25px; font-size: 1.5rem; background-color: #15803d; color: #fafafa; gap: 0.5rem; user-select: none; max-width: 70vw;", duration, ...options, - }) - } + }); + }; if (options?.withAudio && options?.src) { - let sound: Howl + let sound: Howl; sound = new Howl({ src: [options.src], preload: true, autoplay: true, onload: () => { - let duration = sound.duration() * 1000 - sendToast(duration) - setTimeout(onComplete, duration) + let duration = sound.duration() * 1000; + sendToast(duration); + setTimeout(onComplete, duration); }, - }) + }); } else { - sendToast(this.defaultDuration) - setTimeout(onComplete, this.defaultDuration) + sendToast(this.defaultDuration); + setTimeout(onComplete, this.defaultDuration); + } + } + public static error(message: string, options?: NotificationOptions) { + if (options?.withAudio && !options.src) + throw new Error("No audio source provided"); + + const onComplete = () => { + if (options?.onComplete) options.onComplete(); + }; + + const sendToast = (duration: number) => { + toast.error(message, { + style: + "padding: 25px; font-size: 1.5rem; background-color: #dc2626; color: #fafafa; gap: 0.5rem; user-select: none; max-width: 70vw;", + duration, + ...options, + }); + }; + + if (options?.withAudio && options?.src) { + let sound: Howl; + sound = new Howl({ + src: [options.src], + preload: true, + autoplay: true, + onload: () => { + let duration = sound.duration() * 1000; + sendToast(duration); + setTimeout(onComplete, duration); + }, + }); + } else { + sendToast(this.defaultDuration); + setTimeout(onComplete, this.defaultDuration); } } public static info(message: string, options?: NotificationOptions) { const onComplete = () => { - if (options?.onComplete) options.onComplete() - } + if (options?.onComplete) options.onComplete(); + }; const sendToast = (duration: number) => { toast(message, { style: - 'padding: 25px; font-size: 1.5rem; gap: 0.5rem; user-select: none; max-width-600px; max-width: 70vw;', + "padding: 25px; font-size: 1.5rem; gap: 0.5rem; user-select: none; max-width-600px; max-width: 70vw;", icon: InfoIcon, duration, ...options, - }) - } + }); + }; if (options?.withAudio && options?.src) { - let sound: Howl + let sound: Howl; sound = new Howl({ src: [options.src], preload: true, autoplay: true, onload: () => { - let duration = sound.duration() * 1000 - sendToast(duration) - setTimeout(onComplete, duration) + let duration = sound.duration() * 1000; + sendToast(duration); + setTimeout(onComplete, duration); }, - }) + }); } else { - sendToast(this.defaultDuration) - setTimeout(onComplete, this.defaultDuration) + sendToast(this.defaultDuration); + setTimeout(onComplete, this.defaultDuration); } } public static warn(message: string, options?: NotificationOptions) { const onComplete = () => { - if (options?.onComplete) options.onComplete() - } + if (options?.onComplete) options.onComplete(); + }; const sendToast = (duration: number) => { toast(message, { style: - 'padding: 25px; font-size: 1.5rem; background-color: #f59e0b; color: #fafafa; gap: 0.5rem; user-select: none; max-width: 70vw;', + "padding: 25px; font-size: 1.5rem; background-color: #f59e0b; color: #fafafa; gap: 0.5rem; user-select: none; max-width: 70vw;", icon: WarnIcon, duration, ...options, - }) - } + }); + }; if (options?.withAudio && options?.src) { - let sound: Howl + let sound: Howl; sound = new Howl({ src: [options.src], preload: true, autoplay: true, onload: () => { - let duration = sound.duration() * 1000 - sendToast(duration) - setTimeout(onComplete, duration) + let duration = sound.duration() * 1000; + sendToast(duration); + setTimeout(onComplete, duration); }, - }) + }); } else { - sendToast(this.defaultDuration) - setTimeout(onComplete, this.defaultDuration) + sendToast(this.defaultDuration); + setTimeout(onComplete, this.defaultDuration); } } public static playAudio(src: string, onComplete: () => void = () => {}) { @@ -116,8 +150,8 @@ export class Notifications { preload: true, autoplay: true, onload: () => { - setTimeout(onComplete, 1000 * sound.duration()) + setTimeout(onComplete, 1000 * sound.duration()); }, - }) + }); } } diff --git a/client/src/lib/Sequences/sequences.ts b/client/src/lib/Sequences/sequences.ts index ee6a911..b3436f9 100644 --- a/client/src/lib/Sequences/sequences.ts +++ b/client/src/lib/Sequences/sequences.ts @@ -20,6 +20,7 @@ import { settingsStore } from '../stores/settingsStore' import { get } from 'svelte/store' import getVoicePath from '../utils/getVoicePath' import { tick } from 'svelte' +import { cameraState } from '../Dashboard/Visualization/CameraControls/utils/cameraStore' // await a "tick" (a svelte update frame) at the start of every sequence so that // state is synced and no weird side effects occur @@ -28,30 +29,34 @@ export const initializationSequence = async () => { await tick() Notifications.info('Jankboard initialized!', { withAudio: true, - src: getVoicePath('jankboard-initialized', 'en'), - }) - setTimeout(() => { - if (get(settingsStore).goWoke) return - Notifications.success('LittenOS is online', { - withAudio: true, - src: getVoicePath('littenos-is-online', 'en'), - }) - setTimeout(() => { - Notifications.warn('Breaching Monte Vista codebase', { + src: getVoicePath('jankboard-initialized'), + onComplete: () => { + if (get(settingsStore).goWoke) { + sequenceStore.update('initializationComplete', true) + periodicSequence() + return + } + Notifications.success('LittenOS is online', { withAudio: true, - src: getVoicePath('breaching-monte-vista', 'en'), + src: getVoicePath('littenos-is-online'), + onComplete: () => { + Notifications.warn('Breaching Monte Vista codebase', { + withAudio: true, + src: getVoicePath('breaching-monte-vista'), + onComplete: () => { + Notifications.playAudio( + getVoicePath('hello-virtual-assistant'), + () => { + sequenceStore.update('initializationComplete', true) + periodicSequence() + } + ) + }, + }) + }, }) - setTimeout(() => { - Notifications.playAudio( - getVoicePath('hello-virtual-assistant', 'en'), - () => { - sequenceStore.update('initializationComplete', true) - periodicSequence() - } - ) - }, 3000) - }, 3000) - }, 3000) + }, + }) } let counter = 1 @@ -108,7 +113,7 @@ export const criticalFailureIminentSequence = async () => { await tick() Notifications.error('Critical robot failure imminent', { withAudio: true, - src: getVoicePath('critical-robot-failure', 'en'), + src: getVoicePath('critical-robot-failure'), }) } @@ -116,7 +121,7 @@ export const collisionDetectedSequence = async () => { await tick() Notifications.error('Collision detected', { withAudio: true, - src: getVoicePath('collision-detected', 'en'), + src: getVoicePath('collision-detected'), }) } @@ -124,7 +129,7 @@ export const collisionImminentSequence = async () => { await tick() Notifications.error('Collision imminent', { withAudio: true, - src: getVoicePath('collision-imminent', 'en'), + src: getVoicePath('collision-imminent'), }) } @@ -133,7 +138,7 @@ export const cruiseControlEngagedSequence = async () => { await tick() Notifications.success('Cruise control engaged', { withAudio: true, - src: getVoicePath('cruise-control-engaged', 'en'), + src: getVoicePath('cruise-control-engaged'), }) } @@ -142,7 +147,7 @@ export const retardSequence = async () => { await tick() Notifications.warn('Retard', { withAudio: true, - src: getVoicePath('retard', 'en'), + src: getVoicePath('retard'), }) } @@ -151,7 +156,7 @@ const breaching254Sequence = async () => { await tick() Notifications.warn('Breaching 254 mainframe', { withAudio: true, - src: getVoicePath('breaching-254-mainframe', 'en'), + src: getVoicePath('breaching-254-mainframe'), }) } @@ -160,7 +165,7 @@ const breaching1323Sequence = async () => { await tick() Notifications.warn('Breaching 1323 mainframe', { withAudio: true, - src: getVoicePath('breaching-1323-mainframe', 'en'), + src: getVoicePath('breaching-1323-mainframe'), }) } @@ -169,7 +174,7 @@ const bullyingRohanSequence = async () => { await tick() Notifications.info('Bullying Rohan', { withAudio: true, - src: getVoicePath('bullying-rohan', 'en'), + src: getVoicePath('bullying-rohan'), }) } @@ -177,7 +182,7 @@ export const userErrorDetectedSequence = async () => { await tick() Notifications.error('User error detected', { withAudio: true, - src: getVoicePath('user-error-detected', 'en'), + src: getVoicePath('user-error-detected'), }) } @@ -188,8 +193,9 @@ export const infotainmentBootupSequence = async () => { get(sequenceStore).infotainmentStartedFirstTime || get(settingsStore).disableAnnoyances || infotainmentStarted - ) + ) { return + } infotainmentStarted = true await tick() @@ -197,17 +203,17 @@ export const infotainmentBootupSequence = async () => { const sequence = () => { Notifications.info('Infotainment system buffering', { withAudio: true, - src: getVoicePath('infotainment-system-buffering', 'en'), + src: getVoicePath('infotainment-system-buffering'), + onComplete: () => { + Notifications.success('Infotainment system online', { + withAudio: true, + src: getVoicePath('infotainment-system-online'), + onComplete: () => { + sequenceStore.update('infotainmentStartedFirstTime', true) + }, + }) + }, }) - setTimeout(() => { - Notifications.success('Infotainment system online', { - withAudio: true, - src: getVoicePath('infotainment-system-online', 'en'), - onComplete: () => { - sequenceStore.update('infotainmentStartedFirstTime', true) - }, - }) - }, 3000) } if (!get(sequenceStore).initializationComplete) { @@ -256,7 +262,7 @@ export const musicPlayerBootupSequence = async () => { waitForInfotainmentBootup(() => { Notifications.info('Downloading copyrighted music...', { withAudio: true, - src: getVoicePath('downloading-copyrighted-music', 'en'), + src: getVoicePath('downloading-copyrighted-music'), }) }) } @@ -274,7 +280,7 @@ export const gbaEmulatorBootupSequence = async () => { waitForInfotainmentBootup(() => { Notifications.info('Loading pirated Nintendo ROMs', { withAudio: true, - src: getVoicePath('loading-pirated-nintendo', 'en'), + src: getVoicePath('loading-pirated-nintendo'), }) }) } @@ -292,16 +298,135 @@ export const doomBootupSequence = async () => { waitForInfotainmentBootup(() => { Notifications.success('Doom Engaged', { withAudio: true, - src: getVoicePath('doom-engaged', 'en'), + src: getVoicePath('doom-engaged'), }) }) } const bypassCoprocessorRestrictionsSequence = async () => { - if (get(settingsStore).disableAnnoyances) return + if ( + get(settingsStore).disableAnnoyances || + get(sequenceStore).initializationComplete + ) + return await tick() Notifications.warn('Bypassing coprocessor restrictions', { withAudio: true, - src: getVoicePath('bypassing-coprocessor-restrictions', 'en'), + src: getVoicePath('bypassing-coprocessor-restrictions'), }) } + +export const shiftedInParkSequence = async () => { + await tick() + cameraState.set('mode', 'orbit') + + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + Notifications.playAudio(getVoicePath('parked-brakes-engaged'), () => { + if (!get(settingsStore).sentry) return + + Notifications.playAudio(getVoicePath('sentry-mode-engaged')) + Notifications.warn('Sentry mode engaged. Threats will be neutralized') + }) +} + +export const shiftedInReverseSequence = async () => { + await tick() + + cameraState.set('mode', 'follow-direction') + + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + Notifications.playAudio(getVoicePath('reverse')) +} + +export const shiftedInNeutralSequence = async () => { + await tick() + + cameraState.set('mode', 'orbit') + + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + Notifications.playAudio(getVoicePath('neutral-brakes-engaged')) +} + +export const shiftedInLowSequence = async () => { + await tick() + + cameraState.set('mode', 'follow-facing') + + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + Notifications.playAudio(getVoicePath('shifted-into-low')) +} + +export const shiftedInAutoSequence = async () => { + await tick() + + cameraState.set('mode', 'follow-direction') + + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + Notifications.playAudio(getVoicePath('shifted-into-automatic')) +} + +export const shiftedInDriveSequence = async () => { + await tick() + + cameraState.set('mode', 'follow-facing') + + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + Notifications.playAudio(getVoicePath('shifted-into-drive')) +} + +export const modeChillSequence = async () => { + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + await tick() + + Notifications.playAudio(getVoicePath('set-acceleration-profile-chill')) +} + +export const modeCruiseSequence = async () => { + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + await tick() + + Notifications.playAudio(getVoicePath('cruise-control-engaged')) +} + +export const modeLudicrousSequence = async () => { + if ( + get(settingsStore).disableAnnoyances || + !get(sequenceStore).initializationComplete + ) + return + await tick() + + Notifications.playAudio(getVoicePath('set-acceleration-profile-ludicrous')) +} diff --git a/client/src/lib/stores/settingsStore.ts b/client/src/lib/stores/settingsStore.ts index 1fd43fd..cbb96cd 100644 --- a/client/src/lib/stores/settingsStore.ts +++ b/client/src/lib/stores/settingsStore.ts @@ -2,11 +2,15 @@ import { writable } from 'svelte/store' +type SupportedLanguage = 'en-US' | 'en-RU' + export interface SettingsStoreData { disableAnnoyances: boolean goWoke: boolean fastStartup: boolean randomWeight: number + voiceLang: SupportedLanguage + sentry: boolean } export const defaults: SettingsStoreData = { @@ -14,6 +18,8 @@ export const defaults: SettingsStoreData = { goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences fastStartup: false, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.) randomWeight: 1, // the weight of random events (multiplied by the original probability) + voiceLang: 'en-US', + sentry: true, } const createSequenceStore = () => { diff --git a/client/src/lib/stores/telemetryStore.ts b/client/src/lib/stores/telemetryStore.ts index 2abc510..7521597 100644 --- a/client/src/lib/stores/telemetryStore.ts +++ b/client/src/lib/stores/telemetryStore.ts @@ -1,20 +1,21 @@ -import { writable, readonly } from 'svelte/store' +import { writable, readonly, get } from 'svelte/store' let defaults: TelemetryData = { - 'orientation': -999, - 'chassis-x-speed': -999, - 'chassis-y-speed': -999, - 'accx': -999, - 'accy': -999, - 'accz': -999, - 'jerk-x': -999, - 'jerk-y': -999, - 'voltage': -999, - 'acc-profile': '-999', - 'gear': '-999', + 'orientation': 0, + 'chassis-x-speed': 0, + 'chassis-y-speed': 0, + 'accx': 0, + 'accy': 0, + 'accz': 0, + 'jerk-x': 0, + 'jerk-y': 0, + 'voltage': 0, + 'acc-profile': 'chill', + 'gear': 'park', 'ebrake': false, 'reorient': false, 'gpws': false, + 'connected': false, } const createTelemetryStore = () => { @@ -27,6 +28,16 @@ const createTelemetryStore = () => { return store }) }, + set: (key: keyof TelemetryData, value: any) => { + let newObj = { + ...get(telemetryStore), + } + newObj = { + ...newObj, + [key]: value, + } + set(newObj) + }, reset: () => set(defaults), } } diff --git a/client/src/lib/utils/getVoicePath.ts b/client/src/lib/utils/getVoicePath.ts index 6a1178f..399536f 100644 --- a/client/src/lib/utils/getVoicePath.ts +++ b/client/src/lib/utils/getVoicePath.ts @@ -1,3 +1,6 @@ +import { get } from 'svelte/store' +import { settingsStore } from '../stores/settingsStore' + /** * Retrieves the voice audio path for the given audio file. * @@ -5,7 +8,13 @@ * @param lang - the language of the audio * @return the path of the audio file */ -type SupportedLanguage = 'en' | 'rus' -export default function getVoicePath(audio: string, lang: SupportedLanguage) { +type SupportedLanguage = 'en-US' | 'en-RU' + +export default function getVoicePath(audio: string, lang?: SupportedLanguage) { + console.log(get(settingsStore).voiceLang) + if (!lang) { + return `/static/voices/${get(settingsStore).voiceLang}/${audio}.wav` + } + return `/static/voices/${lang}/${audio}.wav` } diff --git a/client/src/lib/utils/initializeTelemetry.ts b/client/src/lib/utils/initializeTelemetry.ts index 1782ade..0d3839b 100644 --- a/client/src/lib/utils/initializeTelemetry.ts +++ b/client/src/lib/utils/initializeTelemetry.ts @@ -1,5 +1,6 @@ -import { io } from 'socket.io-client' +import { get } from 'svelte/store' import { telemetryStore } from '../stores/telemetryStore' +import { emit, listen } from '@tauri-apps/api/event' /** * Connects to sockets and subscribes to specified topics to receive telemetry data. @@ -14,7 +15,7 @@ const onUpdate = (data: TelemetryData) => { // console.log(data) } -export const initializeTelemetry = ( +export const initializeTelemetry = async ( topics: TelemetryTopics, refreshRate: number ) => { @@ -25,20 +26,24 @@ export const initializeTelemetry = ( ) } - const socket = io('localhost:1280') - socket.on('connect', () => { - console.log('Socket-IO connected!') - socket.emit('subscribe', topics) - console.log(`Subscribing to topics: ${JSON.stringify(topics)}`) + const unlistenStatus = await listen('telemetry_status', event => { + if (event.payload === 'connected') { + telemetryStore.set('connected', false) + } else if (event.payload === 'disconnected') { + telemetryStore.set('connected', false) + } }) - socket.on('subscribed', () => { - console.log('Successfully subscribed to requested topics!') - socket.emit('request_data', { refresh_rate: refreshRate }) - console.log(`Refreshing at ${refreshRate} Hz`) + const unlistenTelemetry = await listen('telemetry_data', event => { + const data = JSON.parse(event.payload as string) + // console.log(JSON.parse) + telemetryStore.set(data['topic_name'], data['data']) }) - socket.on('telemetry_data', (data: string) => { - onUpdate(JSON.parse(data)) - }) + const unlistenAll = () => { + unlistenStatus() + unlistenTelemetry() + } + + return unlistenAll }