diff --git a/client/src/App.svelte b/client/src/App.svelte index 654fe33..16c1e90 100644 --- a/client/src/App.svelte +++ b/client/src/App.svelte @@ -5,8 +5,29 @@ import 'material-symbols' import AppBar from './lib/Apps/AppBar.svelte' import { appList } from './lib/Apps/appList' + import { initializeTelemetry } from './lib/utils/initializeTelemetry' + import { onMount } from 'svelte' let activeApp: App = 'media-player' + 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'], + } + + onMount(() => { + initializeTelemetry(topics, 5) + })
diff --git a/client/src/globals.d.ts b/client/src/globals.d.ts index cac0a05..a02201b 100644 --- a/client/src/globals.d.ts +++ b/client/src/globals.d.ts @@ -1,4 +1,4 @@ -type Gear = 'p' | 'r' | 'n' | 'l' | 'a' | 'd' +type Gear = 'park' | 'reverse' | 'neutral' | 'low' | 'auto' | 'drive' type Mode = 'chill' | 'ludicrous' | 'cruise' @@ -18,3 +18,56 @@ interface AppData { icon: string } } + +type Polar = { + R: number + theta: number +} + +/* + * Represents a network table with various vehicle parameters. + * + * @property orientation - The orientation of the vehicle. + * @property chassis-x-speed - The speed of the vehicle along the x-axis. + * @property chassis-y-speed - The speed of the vehicle along the y-axis. + * @property accx - The acceleration of the vehicle along the x-axis. + * @property accy - The acceleration of the vehicle along the y-axis. + * @property accz - The acceleration of the vehicle along the z-axis. + * @property jerk-x - The jerk of the vehicle along the x-axis. + * @property jerk-y - The jerk of the vehicle along the y-axis. + * @property voltage - The voltage of the vehicle's battery. + * @property acc-profile - The acceleration profile of the vehicle. + * @property gear - The current gear of the vehicle. + */ +interface TelemetryData { + 'orientation': number + 'chassis-x-speed': number + 'chassis-y-speed': number + 'accx': number + 'accy': number + 'accz': number + 'jerk-x': number + 'jerk-y': number + 'voltage': number + 'acc-profile': Mode | '-999' + 'gear': Gear | '-999' + 'ebrake': boolean + 'reorient': boolean + 'gpws': boolean +} + +type CardinalDirection = + | 'North' + | 'Northeast' + | 'East' + | 'Southeast' + | 'South' + | 'Southwest' + | 'West' + | 'Northwest' + +interface TelemetryTopics { + doubles: string[] + strings: string[] + booleans: string[] +} diff --git a/client/src/lib/Apps/AppBar.svelte b/client/src/lib/Apps/AppBar.svelte index daf3ebf..1a6970e 100644 --- a/client/src/lib/Apps/AppBar.svelte +++ b/client/src/lib/Apps/AppBar.svelte @@ -1,3 +1,11 @@ + + +
+

+ Heading {getDirection(orientation)} ({orientation}°) +

+

+ {getAcceleration(accResolved)} ({mpss2knps(accResolved).toFixed(2)} + kn/s) +

+
diff --git a/client/src/lib/Dashboard/Dashboard.svelte b/client/src/lib/Dashboard/Dashboard.svelte index 0fa97d8..3319a41 100644 --- a/client/src/lib/Dashboard/Dashboard.svelte +++ b/client/src/lib/Dashboard/Dashboard.svelte @@ -1,24 +1,49 @@ + + import Compass from './Compass.svelte' + import { telemetryReadonlyStore } from '../stores/telemetryStore' + import Bottom from './Bottom.svelte' - + $: speedResolved = Math.hypot( + $telemetryReadonlyStore['chassis-x-speed'], + $telemetryReadonlyStore['chassis-y-speed'] + ) +
- +
- +
-
+ +
+ +
-
+
diff --git a/client/src/lib/Dashboard/MediaPlayer/Controls.svelte b/client/src/lib/Dashboard/MediaPlayer/Controls.svelte index 8593140..59df560 100644 --- a/client/src/lib/Dashboard/MediaPlayer/Controls.svelte +++ b/client/src/lib/Dashboard/MediaPlayer/Controls.svelte @@ -1,16 +1,14 @@ +
diff --git a/client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte b/client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte index c72cd66..18b7368 100644 --- a/client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte +++ b/client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte @@ -1,10 +1,16 @@ +
diff --git a/client/src/lib/Dashboard/TopBar/GearSelector.svelte b/client/src/lib/Dashboard/TopBar/GearSelector.svelte index 8626b4f..03770ff 100644 --- a/client/src/lib/Dashboard/TopBar/GearSelector.svelte +++ b/client/src/lib/Dashboard/TopBar/GearSelector.svelte @@ -1,15 +1,15 @@
-
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 727507e..58e78d7 100644 --- a/client/src/lib/Dashboard/TopBar/ModeSelector.svelte +++ b/client/src/lib/Dashboard/TopBar/ModeSelector.svelte @@ -1,5 +1,12 @@ + diff --git a/client/src/lib/Dashboard/TopBar/TopBar.svelte b/client/src/lib/Dashboard/TopBar/TopBar.svelte index 87ba9cf..ec644e3 100644 --- a/client/src/lib/Dashboard/TopBar/TopBar.svelte +++ b/client/src/lib/Dashboard/TopBar/TopBar.svelte @@ -1,17 +1,31 @@ -
- +
- +
- +
diff --git a/client/src/lib/stores/telemetryStore.ts b/client/src/lib/stores/telemetryStore.ts new file mode 100644 index 0000000..2abc510 --- /dev/null +++ b/client/src/lib/stores/telemetryStore.ts @@ -0,0 +1,36 @@ +import { writable, readonly } 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', + 'ebrake': false, + 'reorient': false, + 'gpws': false, +} + +const createTelemetryStore = () => { + const { subscribe, set, update } = writable(defaults) + return { + subscribe, + update: (data: TelemetryData) => { + update(store => { + if (data !== store) store = data + return store + }) + }, + reset: () => set(defaults), + } +} + +export const telemetryStore = createTelemetryStore() + +export const telemetryReadonlyStore = readonly(telemetryStore) diff --git a/client/src/lib/utils/helpers.ts b/client/src/lib/utils/helpers.ts new file mode 100644 index 0000000..67dc278 --- /dev/null +++ b/client/src/lib/utils/helpers.ts @@ -0,0 +1,111 @@ +// various utilities to help with displaying data + +/** + * Function to filter the network table, replacing any -999 values with default values. + * + * @param table - the network table to be filtered + * @return the filtered network table + */ +export const getAcceleration = (acc: number) => { + if (acc > 0.75) { + return 'Rapidly accelerating' + } + if (acc > 0.25) { + return 'Accelerating' + } + return 'Not accelerating' +} + +let defaults: TelemetryData = { + 'orientation': 0, + 'chassis-x-speed': 0, + 'chassis-y-speed': 0, + 'accx': 0, + 'accy': 0, + 'accz': 0, + 'jerk-x': 0, + 'jerk-y': 0, + 'voltage': 12, + 'acc-profile': 'chill', + 'gear': 'park', + 'ebrake': false, + 'reorient': false, + 'gpws': false, +} + +/** + * Function to filter the network table, replacing any -999 values with default values. + * + * @param table - the network table to be filtered + * @return the filtered network table + */ +export const filter = (table: TelemetryData) => { + // if any entry of the table object has value -999, replace with default value + for (let key in table) { + if ( + table[key as keyof TelemetryData] === -999 || + table[key as keyof TelemetryData] === '-999' + ) { + ;(table[key as keyof TelemetryData] as number | string | boolean) = + defaults[key as keyof TelemetryData] + } + } + return table +} + +/** + * Calculate the resultant acceleration and its direction based on the given acceleration components and velocity components. + * + * @param acc_x - The x-component of acceleration + * @param acc_y - The y-component of acceleration + * @param v_x - The x-component of velocity + * @param v_y - The y-component of velocity + * @return An object containing the resultant acceleration (R) and its direction (theta) + */ +export const resolveAcceleration = ( + acc_x: number, + acc_y: number, + v_x: number, + v_y: number +): Polar => { + let R = (acc_x * v_x + acc_y * v_y) / Math.hypot(v_x, v_y) + let theta = Math.atan2(v_y, v_x) + return { R, theta } +} + +/* SHUT UP SONARLINT */ +/** + * Returns the cardinal direction based on the input angle. + * + * @param angle - The input angle in degrees + * @return The cardinal direction based on the input angle + */ +export const getDirection = (angle: number): CardinalDirection => { + if (angle < 0 || angle > 360) + if (angle < 22.5 || angle > 337.5) { + return 'North' + } + if (angle > 22.5 && angle < 67.5) { + return 'Northeast' + } + if (angle > 67.5 && angle < 112.5) { + return 'East' + } + if (angle > 112.5 && angle < 157.5) { + return 'Southeast' + } + if (angle > 157.5 && angle < 202.5) { + return 'South' + } + if (angle > 202.5 && angle < 247.5) { + return 'Southwest' + } + if (angle > 247.5 && angle < 292.5) { + return 'West' + } + if (angle > 292.5 && angle < 337.5) { + return 'Northwest' + } + + throw new Error(`Angle ${angle} out of range!`) +} diff --git a/client/src/lib/utils/initializeTelemetry.ts b/client/src/lib/utils/initializeTelemetry.ts new file mode 100644 index 0000000..a20377e --- /dev/null +++ b/client/src/lib/utils/initializeTelemetry.ts @@ -0,0 +1,44 @@ +import { io } from 'socket.io-client' +import { telemetryStore } from '../stores/telemetryStore' + +/** + * Connects to sockets and subscribes to specified topics to receive telemetry data. + * + * @param topics - the topics to subscribe to + * @param refreshRate - the refresh rate in Hz to be sent to the backend + * 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 = ( + 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 socket = io() + socket.on('connect', () => { + console.log('Socket-IO connected!') + socket.emit('subscribe', topics) + console.log(`Subscribing to topics: ${JSON.stringify(topics)}`) + }) + + socket.on('subscribed', () => { + console.log('Successfully subscribed to requested topics!') + socket.emit('request_data', { refresh_rate: refreshRate }) + console.log(`Refreshing at ${refreshRate} Hz`) + }) + + socket.on('telemetry_data', (data: string) => { + onUpdate(JSON.parse(data)) + }) +} diff --git a/client/src/lib/utils/unitConversions.ts b/client/src/lib/utils/unitConversions.ts new file mode 100644 index 0000000..a01d3e5 --- /dev/null +++ b/client/src/lib/utils/unitConversions.ts @@ -0,0 +1,19 @@ +/** + * Convert meters per second to miles per hour. + * + * @param mps - the speed in meters per second + * @return the speed in miles per hour, rounded to one decimal place + */ +export const mps2mph = (mps: number) => { + return mps * 2.23694 +} + +/** + * Converts meters per second to knots per second. + * + * @param mpss - the value in meters per second + * @return the value in knots per second with 2 decimal places + */ +export const mpss2knps = (mpss: number) => { + return mpss / 0.5144 +}