From e0d4e019056637cc8de65fb807dd446fb7dbcd67 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Wed, 6 Mar 2024 22:02:33 -0800 Subject: [PATCH 1/8] feat: activate woke mode by default to comply with DEI --- client/src/lib/stores/settingsStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/lib/stores/settingsStore.ts b/client/src/lib/stores/settingsStore.ts index cbb96cd..817c741 100644 --- a/client/src/lib/stores/settingsStore.ts +++ b/client/src/lib/stores/settingsStore.ts @@ -15,7 +15,7 @@ export interface SettingsStoreData { export const defaults: SettingsStoreData = { disableAnnoyances: false, // disable non-critical notifications - goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences + goWoke: true, // 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', @@ -30,7 +30,7 @@ const createSequenceStore = () => { data: keyof SettingsStoreData, newValue: SettingsStoreData[typeof data] ) => { - update(store => { + update((store) => { // @ts-expect-error store[data] = newValue return store From 48267f9af90b8740efa4db38ef760d7ebd0f67ae Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Wed, 6 Mar 2024 23:01:36 -0800 Subject: [PATCH 2/8] refactor: make telemetry code more maintainable --- client/src-tauri/src/main.rs | 6 +- client/src-tauri/src/telemetry.rs | 75 ------------------- .../src-tauri/src/telemetry/create_client.rs | 52 +++++++++++++ .../src/telemetry/create_subscription.rs | 42 +++++++++++ client/src-tauri/src/telemetry/mod.rs | 61 +++++++++++++++ 5 files changed, 160 insertions(+), 76 deletions(-) delete mode 100644 client/src-tauri/src/telemetry.rs create mode 100644 client/src-tauri/src/telemetry/create_client.rs create mode 100644 client/src-tauri/src/telemetry/create_subscription.rs create mode 100644 client/src-tauri/src/telemetry/mod.rs diff --git a/client/src-tauri/src/main.rs b/client/src-tauri/src/main.rs index a9aca44..f328956 100644 --- a/client/src-tauri/src/main.rs +++ b/client/src-tauri/src/main.rs @@ -9,6 +9,9 @@ struct Payload { message: String, } +const NTABLE_IP: (u8, u8, u8, u8) = (10, 12, 80, 2); +const NTABLE_PORT: u16 = 5810; + fn main() { let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); @@ -19,7 +22,8 @@ fn main() { let app_handle = app.app_handle(); tokio::spawn(async move { - crate::telemetry::subscribe_topics(app_handle.clone()).await; + crate::telemetry::subscribe_topics(app_handle.clone(), NTABLE_IP, NTABLE_PORT) + .await; }); Ok(()) diff --git a/client/src-tauri/src/telemetry.rs b/client/src-tauri/src/telemetry.rs deleted file mode 100644 index 3be2175..0000000 --- a/client/src-tauri/src/telemetry.rs +++ /dev/null @@ -1,75 +0,0 @@ -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-tauri/src/telemetry/create_client.rs b/client/src-tauri/src/telemetry/create_client.rs new file mode 100644 index 0000000..f1987fc --- /dev/null +++ b/client/src-tauri/src/telemetry/create_client.rs @@ -0,0 +1,52 @@ +use network_tables::v4::client_config::Config; +use network_tables::v4::Client; +use std::net::{Ipv4Addr, SocketAddrV4}; +use tauri::{AppHandle, Manager}; +use tokio::time::{sleep, Duration}; + +/// Creates a NetworkTables client +/// +/// This function will keep trying to create a NetworkTables client with the given +/// IP address and port until it is successful. It will sleep for 3 seconds between +/// attempts. If successful, it will emit the `telemetry_connected` event on the +/// `app_handle` with the payload `"connected"`. If unsuccessful, it will emit the +/// `telemetry_status` event with the payload `"disconnected"` instead. +pub async fn create_client( + app_handle: &AppHandle, + ntable_ip: &(u8, u8, u8, u8), + ntable_port: &u16, +) -> Client { + loop { + let client_attempt = 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; + + match client_attempt { + 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) => { + if cfg!(debug_assertions) { + 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 + } + }; + } +} diff --git a/client/src-tauri/src/telemetry/create_subscription.rs b/client/src-tauri/src/telemetry/create_subscription.rs new file mode 100644 index 0000000..6442707 --- /dev/null +++ b/client/src-tauri/src/telemetry/create_subscription.rs @@ -0,0 +1,42 @@ +use network_tables::v4::{Client, Subscription, SubscriptionOptions}; +use tokio::time::{sleep, Duration}; + +/// Create a subscription to all SmartDashboard values +/// +/// The subscription will receive updates to all values in the +/// SmartDashboard, and any future values added to it. +/// +/// The subscription will be created with the following options: +/// +/// * `all`: `true` - receive updates to all values +/// * `prefix`: `true` - receive updates to all keys with the +/// prefix `/SmartDashboard` +/// +/// This function will retry creating a subscription every 3 seconds +/// if it fails. +pub async fn create_subscription(client: &Client) -> Subscription { + loop { + let subscription_attempt = client + .subscribe_w_options( + &["/SmartDashboard"], + Some(SubscriptionOptions { + all: Some(true), + prefix: Some(true), + ..Default::default() + }), + ) + .await; + + match subscription_attempt { + Ok(subscription) => break subscription, + Err(e) => { + if cfg!(debug_assertions) { + println!("Failed to create subscription: {}", e); + } + + sleep(Duration::from_secs(3)).await; // Wait for 3 seconds before retrying + continue; + } + } + } +} diff --git a/client/src-tauri/src/telemetry/mod.rs b/client/src-tauri/src/telemetry/mod.rs new file mode 100644 index 0000000..0c6b588 --- /dev/null +++ b/client/src-tauri/src/telemetry/mod.rs @@ -0,0 +1,61 @@ +use network_tables::v4::MessageData; +use serde_json::to_string; +use tauri::{AppHandle, Manager}; +mod create_client; +mod create_subscription; + +use crate::telemetry::create_client::create_client; +use create_subscription::create_subscription; + +/// Attempts to subscribe to NetworkTables topics and send the data to the frontend. +/// +/// This function creates a NetworkTables client and subscribes to all +/// topics. When new data is received, it is serialized as JSON and emitted +/// to all connected frontends using the "telemetry_data" event. +/// +/// The function loops forever, retrying connection every 3 seconds, reconnecting if the client disconnects. +pub async fn subscribe_topics( + app_handle: AppHandle, + ntable_ip: (u8, u8, u8, u8), + ntable_port: u16, +) { + loop { + // I hope this doesn't lead to a catastrophic infinite loop failure + let client = create_client(&app_handle, &ntable_ip, &ntable_port).await; + + let mut subscription = create_subscription(&client).await; + + while let Some(mut message) = subscription.next().await { + process_message(&mut message); + + let json_message = to_string(&message).expect("Failed to serialize message"); + app_handle + .emit_all("telemetry_data", json_message.clone()) + .expect("Failed to send telemetry message"); + + if cfg!(debug_assertions) { + println!("{}", json_message); + } + } + if cfg!(debug_assertions) { + println!("disconnected"); + } + app_handle + .emit_all("telemetry_status", "disconnected") + .expect("Failed to emit telemetry_disconnected event"); + } +} + +/// Strips the '/SmartDashboard/' prefix from NetworkTables topic names if present. +/// +/// NetworkTables uses the '/SmartDashboard/' prefix to indicate that the topic +/// was published to the SmartDashboard. The SmartDashboard is a way for the robot +/// to send data to any coprocessors, and it's published to the /SmartDashboard topic. +/// +/// This function strips the '/SmartDashboard/' prefix from the topic name if +/// it is present. This allows easier data processing from the frontend. +fn process_message(message: &mut MessageData) { + if let Some(stripped) = message.topic_name.strip_prefix("/SmartDashboard/") { + message.topic_name = stripped.to_string(); + } +} From 832083ac48ac42e79a360de4bd73c9325e95504b Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Wed, 6 Mar 2024 23:14:54 -0800 Subject: [PATCH 3/8] refactor: give ownership of app_handle to create_subscription, which runs forever --- client/src-tauri/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src-tauri/src/main.rs b/client/src-tauri/src/main.rs index f328956..82fab0f 100644 --- a/client/src-tauri/src/main.rs +++ b/client/src-tauri/src/main.rs @@ -22,8 +22,7 @@ fn main() { let app_handle = app.app_handle(); tokio::spawn(async move { - crate::telemetry::subscribe_topics(app_handle.clone(), NTABLE_IP, NTABLE_PORT) - .await; + crate::telemetry::subscribe_topics(app_handle, NTABLE_IP, NTABLE_PORT).await; }); Ok(()) From 3aaf3bd8426dcc374e60468558b34d1333c41a77 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Wed, 6 Mar 2024 23:47:53 -0800 Subject: [PATCH 4/8] refactor: use allegedly more performant codium fix --- client/src-tauri/src/telemetry/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src-tauri/src/telemetry/mod.rs b/client/src-tauri/src/telemetry/mod.rs index 0c6b588..98a597c 100644 --- a/client/src-tauri/src/telemetry/mod.rs +++ b/client/src-tauri/src/telemetry/mod.rs @@ -55,7 +55,8 @@ pub async fn subscribe_topics( /// This function strips the '/SmartDashboard/' prefix from the topic name if /// it is present. This allows easier data processing from the frontend. fn process_message(message: &mut MessageData) { - if let Some(stripped) = message.topic_name.strip_prefix("/SmartDashboard/") { - message.topic_name = stripped.to_string(); - } + message.topic_name = message + .topic_name + .trim_start_matches("/SmartDashboard/") + .to_string(); } From db2819d89d81cd41641e98b623d1a2aebcf3fa55 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Wed, 6 Mar 2024 23:55:35 -0800 Subject: [PATCH 5/8] chore: remove unused functions and imports --- client/src/App.svelte | 21 ++------------------- client/src/lib/utils/initializeTelemetry.ts | 21 +++------------------ 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/client/src/App.svelte b/client/src/App.svelte index 8c9aa48..2bd309b 100644 --- a/client/src/App.svelte +++ b/client/src/App.svelte @@ -12,25 +12,8 @@ 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 = { - doubles: [ - 'orientation', - 'chassis-x-speed', - 'chassis-y-speed', - 'accx', - 'accy', - 'accz', - 'jerk-x', - 'jerk-y', - 'voltage', - ], - strings: ['acc-profile', 'gear'], - booleans: ['ebrake', 'reorient', 'gpws'], - } let loading = $settingsStore.fastStartup ? false : true let unlistenAll: () => void @@ -42,7 +25,7 @@ } window.ResizeObserver = ResizeObserver // disabled while migrating away from python - initializeTelemetry(topics, 200).then((unsubFunction: () => void) => { + initializeTelemetry().then((unsubFunction: () => void) => { unlistenAll = unsubFunction }) setTimeout(() => { @@ -50,7 +33,7 @@ initializationSequence() }, 3000) - settingsStore.subscribe(value => { + settingsStore.subscribe((value) => { localStorage.setItem('settings', JSON.stringify(value)) }) }) diff --git a/client/src/lib/utils/initializeTelemetry.ts b/client/src/lib/utils/initializeTelemetry.ts index 06ba0cc..d98ed08 100644 --- a/client/src/lib/utils/initializeTelemetry.ts +++ b/client/src/lib/utils/initializeTelemetry.ts @@ -9,23 +9,8 @@ import { listen } from '@tauri-apps/api/event' * which will be called with the NetworkTable object every time an update is received from the backend. */ -const onUpdate = (data: TelemetryData) => { - telemetryStore.update(data) - // console.log(data) -} - -export const initializeTelemetry = async ( - topics: TelemetryTopics, - refreshRate: number -) => { - // Make sure refreshRate is valid - if (!Number.isInteger(refreshRate) || refreshRate < 1) { - throw new Error( - 'refreshRate must be an integer greater than or equal to 1.' - ) - } - - const unlistenStatus = await listen('telemetry_status', event => { +export const initializeTelemetry = async () => { + const unlistenStatus = await listen('telemetry_status', (event) => { if (event.payload === 'connected') { telemetryStore.set('connected', true) } else if (event.payload === 'disconnected') { @@ -33,7 +18,7 @@ export const initializeTelemetry = async ( } }) - const unlistenTelemetry = await listen('telemetry_data', event => { + const unlistenTelemetry = await listen('telemetry_data', (event) => { const data = JSON.parse(event.payload as string) telemetryStore.set(data['topic_name'], data['data']) }) From fc39e9e740f63843ef302f33868196f1e34f69f9 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Thu, 7 Mar 2024 09:51:56 -0800 Subject: [PATCH 6/8] feat: break out of subscription creation loop after 50 attempts --- client/src-tauri/src/telemetry/create_subscription.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client/src-tauri/src/telemetry/create_subscription.rs b/client/src-tauri/src/telemetry/create_subscription.rs index 6442707..c9ab0b5 100644 --- a/client/src-tauri/src/telemetry/create_subscription.rs +++ b/client/src-tauri/src/telemetry/create_subscription.rs @@ -14,7 +14,9 @@ use tokio::time::{sleep, Duration}; /// /// This function will retry creating a subscription every 3 seconds /// if it fails. -pub async fn create_subscription(client: &Client) -> Subscription { +pub async fn create_subscription(client: &Client) -> Result { + let mut attempts: u8 = 0; + loop { let subscription_attempt = client .subscribe_w_options( @@ -28,12 +30,17 @@ pub async fn create_subscription(client: &Client) -> Subscription { .await; match subscription_attempt { - Ok(subscription) => break subscription, + Ok(subscription) => break Ok(subscription), Err(e) => { if cfg!(debug_assertions) { println!("Failed to create subscription: {}", e); } + if attempts >= 50 { + break Err(e); + } + + attempts += 1; sleep(Duration::from_secs(3)).await; // Wait for 3 seconds before retrying continue; } From b0d075bbfd7c992a3a028775ab2f3a42a2c0caef Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Thu, 7 Mar 2024 12:31:18 -0800 Subject: [PATCH 7/8] fix: refactor telemetry to use new method --- client/src-tauri/src/telemetry/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src-tauri/src/telemetry/mod.rs b/client/src-tauri/src/telemetry/mod.rs index 98a597c..c65a87e 100644 --- a/client/src-tauri/src/telemetry/mod.rs +++ b/client/src-tauri/src/telemetry/mod.rs @@ -1,4 +1,4 @@ -use network_tables::v4::MessageData; +use network_tables::v4::{MessageData, Subscription}; use serde_json::to_string; use tauri::{AppHandle, Manager}; mod create_client; @@ -23,7 +23,15 @@ pub async fn subscribe_topics( // I hope this doesn't lead to a catastrophic infinite loop failure let client = create_client(&app_handle, &ntable_ip, &ntable_port).await; - let mut subscription = create_subscription(&client).await; + let mut subscription: Subscription = match create_subscription(&client).await { + Ok(subscription) => subscription, + Err(_) => { + app_handle + .emit_all("telemetry_status", "disconnected") + .expect("Failed to emit telemetry_disconnected event"); + continue; + } + }; while let Some(mut message) = subscription.next().await { process_message(&mut message); From 13399fcd9300e2909d88b80e69ee18c929347702 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Fri, 8 Mar 2024 08:37:32 -0800 Subject: [PATCH 8/8] fix: detect connectivity properly --- .../src-tauri/src/telemetry/create_client.rs | 2 +- client/src-tauri/src/telemetry/mod.rs | 32 +++++++++++++++---- client/src/lib/utils/initializeTelemetry.ts | 15 +++++++-- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/client/src-tauri/src/telemetry/create_client.rs b/client/src-tauri/src/telemetry/create_client.rs index f1987fc..b1b1c90 100644 --- a/client/src-tauri/src/telemetry/create_client.rs +++ b/client/src-tauri/src/telemetry/create_client.rs @@ -32,7 +32,7 @@ pub async fn create_client( Ok(client) => { println!("Client created"); app_handle - .emit_all("telemetry_connected", "connected") + .emit_all("telemetry_status", "connected") .expect("Failed to emit telemetry_status connected event"); break client; // Exit the loop if the client is successfully created } diff --git a/client/src-tauri/src/telemetry/mod.rs b/client/src-tauri/src/telemetry/mod.rs index c65a87e..a10ad42 100644 --- a/client/src-tauri/src/telemetry/mod.rs +++ b/client/src-tauri/src/telemetry/mod.rs @@ -24,7 +24,12 @@ pub async fn subscribe_topics( let client = create_client(&app_handle, &ntable_ip, &ntable_port).await; let mut subscription: Subscription = match create_subscription(&client).await { - Ok(subscription) => subscription, + Ok(subscription) => { + app_handle + .emit_all("telemetry_status", "connected") + .expect("Failed to emit telemetry_connected event"); + subscription + } Err(_) => { app_handle .emit_all("telemetry_status", "disconnected") @@ -41,13 +46,28 @@ pub async fn subscribe_topics( .emit_all("telemetry_data", json_message.clone()) .expect("Failed to send telemetry message"); - if cfg!(debug_assertions) { - println!("{}", json_message); + app_handle + .emit_all("telemetry_status", "connected") + .expect("Failed to emit telemetry_connected event"); + + check_triggers( + &app_handle, + &message.topic_name, + &message.data, + &previous_gpws, + ); + + if message.topic_name == "gpws" { + previous_gpws = match message.data { + network_tables::Value::Boolean(b) => b, + _ => previous_gpws, + }; } + + tracing::debug!("{}", json_message); } - if cfg!(debug_assertions) { - println!("disconnected"); - } + + tracing::debug!("disconnected"); app_handle .emit_all("telemetry_status", "disconnected") .expect("Failed to emit telemetry_disconnected event"); diff --git a/client/src/lib/utils/initializeTelemetry.ts b/client/src/lib/utils/initializeTelemetry.ts index d98ed08..35fc723 100644 --- a/client/src/lib/utils/initializeTelemetry.ts +++ b/client/src/lib/utils/initializeTelemetry.ts @@ -10,7 +10,8 @@ import { listen } from '@tauri-apps/api/event' */ export const initializeTelemetry = async () => { - const unlistenStatus = await listen('telemetry_status', (event) => { + const unlistenStatus = await listen('telemetry_status', event => { + console.log(event) if (event.payload === 'connected') { telemetryStore.set('connected', true) } else if (event.payload === 'disconnected') { @@ -18,11 +19,21 @@ export const initializeTelemetry = async () => { } }) - const unlistenTelemetry = await listen('telemetry_data', (event) => { + const unlistenTelemetry = await listen('telemetry_data', event => { const data = JSON.parse(event.payload as string) telemetryStore.set(data['topic_name'], data['data']) }) +<<<<<<< HEAD +======= + const unlistenGPWS = await listen('telemetry_gpws', event => { + const data = JSON.parse(event.payload as string) as boolean + if (data) { + gpwsTriggeredSequence() + } + }) + +>>>>>>> cffa594 (fix: detect connectivity properly) const unlistenAll = () => { unlistenStatus() unlistenTelemetry()