feat: synced telemetry, basic features done
This commit is contained in:
parent
8d7ef7a898
commit
a82bc5a04a
20 changed files with 441 additions and 35 deletions
|
@ -5,8 +5,29 @@
|
||||||
import 'material-symbols'
|
import 'material-symbols'
|
||||||
import AppBar from './lib/Apps/AppBar.svelte'
|
import AppBar from './lib/Apps/AppBar.svelte'
|
||||||
import { appList } from './lib/Apps/appList'
|
import { appList } from './lib/Apps/appList'
|
||||||
|
import { initializeTelemetry } from './lib/utils/initializeTelemetry'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
let activeApp: App = 'media-player'
|
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)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="select-none">
|
<main class="select-none">
|
||||||
|
|
55
client/src/globals.d.ts
vendored
55
client/src/globals.d.ts
vendored
|
@ -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'
|
type Mode = 'chill' | 'ludicrous' | 'cruise'
|
||||||
|
|
||||||
|
@ -18,3 +18,56 @@ interface AppData {
|
||||||
icon: string
|
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[]
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param activeApp - Currently selected app
|
||||||
|
@param appList - List of apps
|
||||||
|
|
||||||
|
Displays the app bar, automatically populated from appList. Bind to activeApp and apps will be automagically updated
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let activeApp: App
|
export let activeApp: App
|
||||||
export let appList: AppData
|
export let appList: AppData
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
Displays the list of songs
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Song from './Song.svelte'
|
import Song from './Song.svelte'
|
||||||
import { songList } from '../../Dashboard/MediaPlayer/songList'
|
import { songList } from '../../Dashboard/MediaPlayer/songList'
|
||||||
|
@ -6,6 +11,8 @@
|
||||||
<div
|
<div
|
||||||
class="flex gap-4 w-full py-10 px-10 bg-blue-200 bg-opacity-25 backdrop-blur-xl h-full media-background rounded-3xl flex-wrap"
|
class="flex gap-4 w-full py-10 px-10 bg-blue-200 bg-opacity-25 backdrop-blur-xl h-full media-background rounded-3xl flex-wrap"
|
||||||
>
|
>
|
||||||
|
<h2 class="text-8xl font-bold basis-full text-slate-200">Music</h2>
|
||||||
|
<div class="basis-full h-2" />
|
||||||
{#each Object.entries(songList) as [slug, song]}
|
{#each Object.entries(songList) as [slug, song]}
|
||||||
<Song {song} {slug} />
|
<Song {song} {slug} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param song - Song data
|
||||||
|
@param slug - Song slug
|
||||||
|
|
||||||
|
Displays a song and its metadata
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { musicStore } from '../../stores/musicStore'
|
import { musicStore } from '../../stores/musicStore'
|
||||||
|
|
||||||
|
|
3
client/src/lib/Dashboard/Bottom.svelte
Normal file
3
client/src/lib/Dashboard/Bottom.svelte
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="fixed bottom-0 w-[35vw]">
|
||||||
|
<slot />
|
||||||
|
</div>
|
29
client/src/lib/Dashboard/Compass.svelte
Normal file
29
client/src/lib/Dashboard/Compass.svelte
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param accx - Acceleration in x
|
||||||
|
@param accy - Acceleration in y
|
||||||
|
@param orientation - Heading in degrees
|
||||||
|
|
||||||
|
Displays the heading direction and acceleration as human readable text
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { getAcceleration, getDirection } from '../utils/helpers'
|
||||||
|
import { mpss2knps } from '../utils/unitConversions'
|
||||||
|
|
||||||
|
export let accx: number
|
||||||
|
export let accy: number
|
||||||
|
export let orientation: number
|
||||||
|
|
||||||
|
$: accResolved = Math.hypot(accx, accy)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 text-center transition-all">
|
||||||
|
<p class="text-xl font-medium">
|
||||||
|
Heading {getDirection(orientation)} ({orientation}°)
|
||||||
|
</p>
|
||||||
|
<p class="text-lg font-medium">
|
||||||
|
{getAcceleration(accResolved)} ({mpss2knps(accResolved).toFixed(2)}
|
||||||
|
kn/s)
|
||||||
|
</p>
|
||||||
|
</div>
|
|
@ -1,24 +1,49 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param selectedGear - Selected gear
|
||||||
|
@param selectedMode - Selected mode
|
||||||
|
@param voltage - Battery voltage
|
||||||
|
|
||||||
|
Displays the driver dashboard and HUD
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import TopBar from './TopBar/TopBar.svelte'
|
import TopBar from './TopBar/TopBar.svelte'
|
||||||
import Speedometer from './Speedometer.svelte'
|
import Speedometer from './Speedometer.svelte'
|
||||||
import SpeedLimit from './SpeedLimit.svelte'
|
import SpeedLimit from './SpeedLimit.svelte'
|
||||||
import MediaDisplay from './MediaPlayer/MediaDisplay.svelte'
|
import MediaDisplay from './MediaPlayer/MediaDisplay.svelte'
|
||||||
// import Player from './MediaPlayer/Player.svelte'
|
import Compass from './Compass.svelte'
|
||||||
</script>
|
import { telemetryReadonlyStore } from '../stores/telemetryStore'
|
||||||
|
import Bottom from './Bottom.svelte'
|
||||||
|
|
||||||
<!-- <Player /> -->
|
$: speedResolved = Math.hypot(
|
||||||
|
$telemetryReadonlyStore['chassis-x-speed'],
|
||||||
|
$telemetryReadonlyStore['chassis-y-speed']
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="px-5">
|
<div class="px-5">
|
||||||
<TopBar />
|
<TopBar
|
||||||
|
selectedGear={$telemetryReadonlyStore.gear}
|
||||||
|
selectedMode={$telemetryReadonlyStore['acc-profile']}
|
||||||
|
voltage={$telemetryReadonlyStore.voltage}
|
||||||
|
/>
|
||||||
<div class="h-0.5 mt-1 w-full bg-slate-300 border-0"></div>
|
<div class="h-0.5 mt-1 w-full bg-slate-300 border-0"></div>
|
||||||
<div class="mt-8 flex justify-between">
|
<div class="mt-8 flex justify-between">
|
||||||
<Speedometer />
|
<Speedometer speed={speedResolved} />
|
||||||
<SpeedLimit />
|
<SpeedLimit />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fixed bottom-0 w-[35vw]">
|
<Bottom>
|
||||||
<MediaDisplay />
|
<div class="mb-10">
|
||||||
|
<Compass
|
||||||
|
accx={$telemetryReadonlyStore['accx']}
|
||||||
|
accy={$telemetryReadonlyStore['accy']}
|
||||||
|
orientation={$telemetryReadonlyStore['orientation']}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<MediaDisplay />
|
||||||
|
</Bottom>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param playing - Whether the music is playing
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { musicStore } from '../../stores/musicStore'
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let playing = false
|
export let playing = false
|
||||||
|
|
||||||
let startTime = Date.now()
|
|
||||||
|
|
||||||
$: if (playing) {
|
|
||||||
startTime = Date.now()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="my-auto flex gap-4 mr-4">
|
<div class="my-auto flex gap-4 mr-4">
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
The dashboard's media controller. Automatically updates with the music service.
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Controls from './Controls.svelte'
|
import Controls from './Controls.svelte'
|
||||||
import { musicStore } from '../../stores/musicStore'
|
import { musicStore } from '../../stores/musicStore'
|
||||||
import { songList } from './songList'
|
import { songList } from './songList'
|
||||||
import { fly } from 'svelte/transition'
|
import { fly } from 'svelte/transition'
|
||||||
import { quintInOut } from 'svelte/easing'
|
import { cubicInOut } from 'svelte/easing'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
import { flip } from 'svelte/animate'
|
||||||
|
|
||||||
$: currentSong = $musicStore.queue[$musicStore.currentIndex]
|
$: currentSong = $musicStore.queue[$musicStore.currentIndex]
|
||||||
$: songData = songList[currentSong]
|
$: songData = songList[currentSong]
|
||||||
|
@ -31,7 +37,7 @@
|
||||||
{#if songData}
|
{#if songData}
|
||||||
<div
|
<div
|
||||||
class="rounded-t-lg bg-neutral-800 px-4 py-2 h-24 flex justify-between"
|
class="rounded-t-lg bg-neutral-800 px-4 py-2 h-24 flex justify-between"
|
||||||
transition:fly={{ y: 100, duration: 300, easing: quintInOut }}
|
transition:fly={{ y: 100, duration: 300, easing: cubicInOut }}
|
||||||
>
|
>
|
||||||
<div class="flex gap-6">
|
<div class="flex gap-6">
|
||||||
<div class="aspect-square">
|
<div class="aspect-square">
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const songList: { [key: string]: SongData } = {
|
||||||
src: '/static/songs/deja-vu/audio.m4a',
|
src: '/static/songs/deja-vu/audio.m4a',
|
||||||
coverImg: '/static/songs/deja-vu/cover.jpg',
|
coverImg: '/static/songs/deja-vu/cover.jpg',
|
||||||
},
|
},
|
||||||
'Xenogenesis': {
|
'xenogenesis': {
|
||||||
title: 'Xenogenesis',
|
title: 'Xenogenesis',
|
||||||
artist: 'TheFatRat',
|
artist: 'TheFatRat',
|
||||||
src: '/static/songs/xenogenesis/audio.m4a',
|
src: '/static/songs/xenogenesis/audio.m4a',
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param speedLimit - Speed limit in Miles Per Hour (MPH)
|
||||||
|
|
||||||
|
Displays the speed limit
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let speedLimit: number = 5.0
|
export let speedLimit: number = 5.0
|
||||||
|
|
||||||
|
@ -10,9 +17,7 @@
|
||||||
<div
|
<div
|
||||||
class="px-3 py-1 border-black rounded-xl border-2 flex flex-col text-center gap-1"
|
class="px-3 py-1 border-black rounded-xl border-2 flex flex-col text-center gap-1"
|
||||||
>
|
>
|
||||||
<div class="text-lg font-medium">
|
<div class="text-lg font-medium">SPEED<br />LIMIT</div>
|
||||||
SPEED<br />LIMIT
|
|
||||||
</div>
|
|
||||||
<div class="text-2xl font-bold">{formatted}</div>
|
<div class="text-2xl font-bold">{formatted}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param speed - Speed in meters per second
|
||||||
|
|
||||||
|
Displays the speed in miles per hour
|
||||||
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// in mph
|
import { mps2mph } from '../utils/unitConversions'
|
||||||
|
|
||||||
export let speed: number = 0.0
|
export let speed: number = 0.0
|
||||||
|
|
||||||
$: formatted = speed.toFixed(1)
|
$: formatted = mps2mph(speed).toFixed(1)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let selectedGear: Gear
|
export let selectedGear: Gear | '-999'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-center w-full">
|
<div class="flex justify-center w-full">
|
||||||
<div class="flex flex-row gap-2 text-neutral-400 text-xl font-bold">
|
<div class="flex flex-row gap-2 text-neutral-400 text-xl font-bold">
|
||||||
<div class:highlighted={selectedGear === 'p'}>P</div>
|
<div class:highlighted={selectedGear === 'park'}>P</div>
|
||||||
<div class:highlighted={selectedGear === 'r'}>R</div>
|
<div class:highlighted={selectedGear === 'reverse'}>R</div>
|
||||||
<div class:highlighted={selectedGear === 'n'}>N</div>
|
<div class:highlighted={selectedGear === 'neutral'}>N</div>
|
||||||
<div class:highlighted={selectedGear === 'l'}>L</div>
|
<div class:highlighted={selectedGear === 'low'}>L</div>
|
||||||
<div class:highlighted={selectedGear === 'a'}>A</div>
|
<div class:highlighted={selectedGear === 'auto'}>A</div>
|
||||||
<div class:highlighted={selectedGear === 'd'}>D</div>
|
<div class:highlighted={selectedGear === 'drive'}>D</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param selectedMode - Selected mode
|
||||||
|
|
||||||
|
Displays the drive mode
|
||||||
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let selectedMode: Mode
|
export let selectedMode: Mode | '-999'
|
||||||
|
|
||||||
let modeText = ''
|
let modeText = ''
|
||||||
|
|
||||||
|
@ -13,6 +20,9 @@
|
||||||
case 'ludicrous':
|
case 'ludicrous':
|
||||||
modeText = 'LUDICROUS'
|
modeText = 'LUDICROUS'
|
||||||
break
|
break
|
||||||
|
case '-999':
|
||||||
|
modeText = 'DISCONNECTED'
|
||||||
|
break
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
<script>
|
<!--
|
||||||
|
@component
|
||||||
|
|
||||||
|
@param selectedGear - Selected gear
|
||||||
|
@param selectedMode - Selected mode
|
||||||
|
@param voltage - Battery voltage
|
||||||
|
|
||||||
|
Displays the top bar of the dashboard
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import BatteryDisplay from './BatteryDisplay.svelte'
|
import BatteryDisplay from './BatteryDisplay.svelte'
|
||||||
import GearSelector from './GearSelector.svelte'
|
import GearSelector from './GearSelector.svelte'
|
||||||
import ModeSelector from './ModeSelector.svelte'
|
import ModeSelector from './ModeSelector.svelte'
|
||||||
|
|
||||||
|
export let selectedGear: Gear | '-999'
|
||||||
|
export let selectedMode: Mode | '-999'
|
||||||
|
export let voltage: number
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-row w-full justify-between">
|
<div class="flex flex-row w-full justify-between">
|
||||||
<div>
|
<div>
|
||||||
<GearSelector selectedGear="p" />
|
<GearSelector {selectedGear} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ModeSelector selectedMode="chill" />
|
<ModeSelector {selectedMode} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<BatteryDisplay voltage={12.5} />
|
<BatteryDisplay {voltage} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
36
client/src/lib/stores/telemetryStore.ts
Normal file
36
client/src/lib/stores/telemetryStore.ts
Normal file
|
@ -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<TelemetryData>(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)
|
111
client/src/lib/utils/helpers.ts
Normal file
111
client/src/lib/utils/helpers.ts
Normal file
|
@ -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!`)
|
||||||
|
}
|
44
client/src/lib/utils/initializeTelemetry.ts
Normal file
44
client/src/lib/utils/initializeTelemetry.ts
Normal file
|
@ -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))
|
||||||
|
})
|
||||||
|
}
|
19
client/src/lib/utils/unitConversions.ts
Normal file
19
client/src/lib/utils/unitConversions.ts
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue