♻️ Moves analytics subscription handling to store

This commit is contained in:
Rune Harlyk
2026-01-03 17:41:42 +01:00
committed by nikguin04
parent 39f9e47e59
commit a31e001eb5
6 changed files with 128 additions and 81 deletions
+1 -2
View File
@@ -47,8 +47,7 @@
"typescript-eslint": "^8.51.0", "typescript-eslint": "^8.51.0",
"unplugin-icons": "^22.4.2", "unplugin-icons": "^22.4.2",
"vite": "^7.1.9", "vite": "^7.1.9",
"vitest": "^3.2.4", "vitest": "^3.2.4"
"ws": "^8.18.3"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
+22 -7
View File
@@ -1,19 +1,34 @@
import { AnalyticsData } from '$lib/platform_shared/message' import { AnalyticsData } from '$lib/platform_shared/message'
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
import { socket } from './socket'
const analytics_data: AnalyticsData[] = []
const maxAnalyticsData = 100 const maxAnalyticsData = 100
function createAnalytics() { function createAnalytics() {
const { subscribe, update } = writable(analytics_data) const { subscribe, update } = writable<AnalyticsData[]>([])
let unsubscribe: (() => void) | null = null
let listenerCount = 0
const addData = (content: AnalyticsData) => {
update(data => [...data, content].slice(-maxAnalyticsData))
}
return { return {
subscribe, subscribe,
addData: (content: AnalyticsData) => { addData,
update(analytics_data => { listen: () => {
return [...analytics_data, content].slice(-maxAnalyticsData) listenerCount++
}) if (!unsubscribe) {
unsubscribe = socket.on(AnalyticsData, addData)
}
},
stop: () => {
listenerCount = Math.max(0, listenerCount - 1)
if (listenerCount === 0 && unsubscribe) {
unsubscribe()
unsubscribe = null
}
} }
} }
} }
+50
View File
@@ -1,3 +1,53 @@
export enum MessageTopic {
imu = 'imu',
imuCalibrate = 'imuCalibrate',
mode = 'mode',
input = 'input',
analytics = 'analytics',
position = 'position',
angles = 'angles',
i2cScan = 'i2cScan',
peripheralSettings = 'peripheralSettings',
otastatus = 'otastatus',
gait = 'walk_gait',
servoState = 'servoState',
servoPWM = 'servoPWM',
WiFiSettings = 'WiFiSettings',
sonar = 'sonar',
rssi = 'rssi'
}
export type WifiStatus = {
status: number
local_ip: string
mac_address: string
rssi: number
ssid: string
bssid: string
channel: number
subnet_mask: string
gateway_ip: string
dns_ip_1: string
dns_ip_2?: string
}
export type KnownNetworkItem = {
ssid: string
password: string
static_ip_config: boolean
local_ip?: string
subnet_mask?: string
gateway_ip?: string
dns_ip_1?: string
dns_ip_2?: string
}
export type WifiSettings = {
hostname: string
priority_RSSI: boolean
wifi_networks: KnownNetworkItem[]
}
export type vector = { x: number; y: number } export type vector = { x: number; y: number }
export type GithubRelease = { export type GithubRelease = {
+10 -26
View File
@@ -10,7 +10,6 @@
import Statusbar from '../lib/components/statusbar/statusbar.svelte' import Statusbar from '../lib/components/statusbar/statusbar.svelte'
import { import {
telemetry, telemetry,
analytics,
kinematicData, kinematicData,
mode, mode,
input, input,
@@ -22,13 +21,11 @@
walkGait walkGait
} from '$lib/stores' } from '$lib/stores'
import { import {
AnalyticsData,
AnglesData, AnglesData,
DownloadOTAData, DownloadOTAData,
HumanInputData, HumanInputData,
KinematicData, KinematicData,
ModeData, ModeData,
PingMsg,
RSSIData, RSSIData,
SonarData, SonarData,
WalkGaitData WalkGaitData
@@ -68,23 +65,14 @@
const eventListeners: (() => void)[] = [] const eventListeners: (() => void)[] = []
const addEventListeners = () => { const addEventListeners = () => {
eventListeners.push( eventListeners.push(
...[ socket.onEvent('open', handleOpen),
socket.onEvent('open', handleOpen), socket.onEvent('close', handleClose),
socket.onEvent('close', handleClose), socket.onEvent('error', handleError),
socket.onEvent('error', handleError), socket.on(RSSIData, data => telemetry.setRSSI(data)),
socket.on(RSSIData, data => telemetry.setRSSI(data)), socket.on(ModeData, data => mode.set(data)),
socket.on(ModeData, data => mode.set(data)), socket.on(AnglesData, data => {
socket.on(AnalyticsData, data => { servoAngles.set(data)
// console.log(data); })
analytics.addData(data)
}),
socket.on(AnglesData, data => {
servoAngles.set(data)
}),
socket.on(PingMsg, data => {
console.log('Ping received!')
})
]
) )
features.subscribe(data => { features.subscribe(data => {
if (data?.download_firmware) if (data?.download_firmware)
@@ -96,14 +84,10 @@
} }
const removeEventListeners = () => { const removeEventListeners = () => {
for (let offFunction of eventListeners) { eventListeners.forEach(offFunction => offFunction())
offFunction()
}
} }
const handleOpen = () => { const handleOpen = () => notifications.success('Connection to device established', 5000)
notifications.success('Connection to device established', 5000)
}
const handleClose = () => { const handleClose = () => {
notifications.error('Connection to device lost', 5000) notifications.error('Connection to device lost', 5000)
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte' import { onMount, onDestroy } from 'svelte'
import SettingsCard from '$lib/components/SettingsCard.svelte' import SettingsCard from '$lib/components/SettingsCard.svelte'
import { slide } from 'svelte/transition' import { slide } from 'svelte/transition'
import { cubicOut } from 'svelte/easing' import { cubicOut } from 'svelte/easing'
@@ -21,6 +21,7 @@
let temperatureChart: Chart let temperatureChart: Chart
onMount(() => { onMount(() => {
analytics.listen()
heapChart = new Chart(heapChartElement, { heapChart = new Chart(heapChartElement, {
type: 'line', type: 'line',
data: { data: {
@@ -221,9 +222,13 @@
setInterval(updateData, 500) setInterval(updateData, 500)
}) })
onDestroy(() => analytics.stop())
function updateData() { function updateData() {
heapChart.data.labels = $analytics.map(datapoint => datapoint.uptime) heapChart.data.labels = $analytics.map(datapoint => datapoint.uptime)
heapChart.data.datasets[0].data = $analytics.map(datapoint => datapoint.totalHeap - datapoint.freeHeap) heapChart.data.datasets[0].data = $analytics.map(
datapoint => datapoint.totalHeap - datapoint.freeHeap
)
heapChart.options.scales!.y!.max = Math.ceil($analytics[0]?.totalHeap ?? 0) heapChart.options.scales!.y!.max = Math.ceil($analytics[0]?.totalHeap ?? 0)
heapChart.update('none') heapChart.update('none')
+38 -44
View File
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy } from 'svelte'
import { modals } from 'svelte-modals' import { modals } from 'svelte-modals'
import { slide } from 'svelte/transition' import { slide } from 'svelte/transition'
import { cubicOut } from 'svelte/easing' import { cubicOut } from 'svelte/easing'
@@ -11,7 +10,7 @@
import ScanNetworks from './Scan.svelte' import ScanNetworks from './Scan.svelte'
import Spinner from '$lib/components/Spinner.svelte' import Spinner from '$lib/components/Spinner.svelte'
import InfoDialog from '$lib/components/InfoDialog.svelte' import InfoDialog from '$lib/components/InfoDialog.svelte'
import { socket } from '$lib/stores' import { type KnownNetworkItem, type WifiSettings, type WifiStatus } from '$lib/types/models'
import { api } from '$lib/api' import { api } from '$lib/api'
import { import {
Cancel, Cancel,
@@ -33,10 +32,17 @@
Edit Edit
} from '$lib/components/icons' } from '$lib/components/icons'
import StatusItem from '$lib/components/StatusItem.svelte' import StatusItem from '$lib/components/StatusItem.svelte'
import { KnownNetworkItem } from '$lib/platform_shared/message'
import { WifiSettings, type WifiStatus } from '$lib/platform_shared/rest_message'
let networkEditable: KnownNetworkItem = $state(KnownNetworkItem.create()) let networkEditable: KnownNetworkItem = $state({
ssid: '',
password: '',
static_ip_config: false,
local_ip: undefined,
subnet_mask: undefined,
gateway_ip: undefined,
dns_ip_1: undefined,
dns_ip_2: undefined
})
let static_ip_config = $state(false) let static_ip_config = $state(false)
@@ -50,7 +56,7 @@
let showWifiDetails = $state(false) let showWifiDetails = $state(false)
let formField: HTMLFormElement = $state()! let formField: Record<string, unknown> = $state({})
let formErrors = $state({ let formErrors = $state({
ssid: false, ssid: false,
@@ -80,21 +86,10 @@
return return
} }
wifiSettings = result.inner wifiSettings = result.inner
dndNetworkList = wifiSettings.wifiNetworks dndNetworkList = wifiSettings.wifi_networks
return wifiSettings return wifiSettings
} }
let unsub_obj: (() => void) | undefined = undefined
onMount(() => {
unsub_obj = socket.on<WifiSettings>(WifiSettings, data => {
wifiSettings = data
dndNetworkList = wifiSettings.wifiNetworks
})
})
onDestroy(() => {
if (unsub_obj) unsub_obj()
})
async function postWiFiSettings(data: WifiSettings) { async function postWiFiSettings(data: WifiSettings) {
const result = await api.post<WifiSettings>('/api/wifi/sta/settings', data) const result = await api.post<WifiSettings>('/api/wifi/sta/settings', data)
if (result.isErr()) { if (result.isErr()) {
@@ -113,7 +108,7 @@
} else { } else {
formErrorhostname = false formErrorhostname = false
// Update global wifiSettings object // Update global wifiSettings object
wifiSettings.wifiNetworks = dndNetworkList wifiSettings.wifi_networks = dndNetworkList
// Post to REST API // Post to REST API
postWiFiSettings(wifiSettings) postWiFiSettings(wifiSettings)
console.log(wifiSettings) console.log(wifiSettings)
@@ -132,15 +127,15 @@
formErrors.ssid = false formErrors.ssid = false
} }
networkEditable.staticIp = static_ip_config networkEditable.static_ip_config = static_ip_config
if (networkEditable.staticIp) { if (networkEditable.static_ip_config) {
// RegEx for IPv4 // RegEx for IPv4
const regexExp = const regexExp =
/\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/ /\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/
// Validate gateway IP // Validate gateway IP
if (!regexExp.test(networkEditable.gatewayIp!)) { if (!regexExp.test(networkEditable.gateway_ip!)) {
valid = false valid = false
formErrors.gateway_ip = true formErrors.gateway_ip = true
} else { } else {
@@ -148,7 +143,7 @@
} }
// Validate Subnet Mask // Validate Subnet Mask
if (!regexExp.test(networkEditable.subnetMask!)) { if (!regexExp.test(networkEditable.subnet_mask!)) {
valid = false valid = false
formErrors.subnet_mask = true formErrors.subnet_mask = true
} else { } else {
@@ -156,7 +151,7 @@
} }
// Validate local IP // Validate local IP
if (!regexExp.test(networkEditable.localIp!)) { if (!regexExp.test(networkEditable.local_ip!)) {
valid = false valid = false
formErrors.local_ip = true formErrors.local_ip = true
} else { } else {
@@ -164,7 +159,7 @@
} }
// Validate DNS 1 // Validate DNS 1
if (!regexExp.test(networkEditable.dnsIp1!)) { if (!regexExp.test(networkEditable.dns_ip_1!)) {
valid = false valid = false
formErrors.dns_1 = true formErrors.dns_1 = true
} else { } else {
@@ -172,8 +167,7 @@
} }
// Validate DNS 2 // Validate DNS 2
// TODO: This is optional, make sure to handle correctly? if (!regexExp.test(networkEditable.dns_ip_2!)) {
if (!regexExp.test(networkEditable.dnsIp2!)) {
valid = false valid = false
formErrors.dns_2 = true formErrors.dns_2 = true
} else { } else {
@@ -215,12 +209,12 @@
networkEditable = { networkEditable = {
ssid: '', ssid: '',
password: '', password: '',
staticIp: false, static_ip_config: false,
localIp: undefined, local_ip: undefined,
subnetMask: undefined, subnet_mask: undefined,
gatewayIp: undefined, gateway_ip: undefined,
dnsIp1: undefined, dns_ip_1: undefined,
dnsIp2: undefined dns_ip_2: undefined
} }
} }
@@ -306,7 +300,7 @@
<StatusItem <StatusItem
icon={Home} icon={Home}
title="IP Address" title="IP Address"
description={wifiStatus.localIp} description={wifiStatus.local_ip}
/> />
<StatusItem icon={WiFi} title="RSSI" description={`${wifiStatus.rssi} dBm`}> <StatusItem icon={WiFi} title="RSSI" description={`${wifiStatus.rssi} dBm`}>
@@ -337,7 +331,7 @@
<StatusItem <StatusItem
icon={MAC} icon={MAC}
title="MAC Address" title="MAC Address"
description={wifiStatus.macAddress} description={wifiStatus.mac_address}
/> />
<StatusItem <StatusItem
@@ -349,16 +343,16 @@
<StatusItem <StatusItem
icon={Gateway} icon={Gateway}
title="Gateway IP" title="Gateway IP"
description={wifiStatus.gatewayIp} description={wifiStatus.gateway_ip}
/> />
<StatusItem <StatusItem
icon={Subnet} icon={Subnet}
title="Subnet Mask" title="Subnet Mask"
description={wifiStatus.subnetMask} description={wifiStatus.subnet_mask}
/> />
<StatusItem icon={DNS} title="DNS" description={wifiStatus.dnsIp1} /> <StatusItem icon={DNS} title="DNS" description={wifiStatus.dns_ip_1} />
</div> </div>
{/if} {/if}
{/if} {/if}
@@ -475,7 +469,7 @@
> >
<input <input
type="checkbox" type="checkbox"
bind:checked={wifiSettings.priorityRssi} bind:checked={wifiSettings.priority_RSSI}
class="checkbox checkbox-primary sm:-mb-5" class="checkbox checkbox-primary sm:-mb-5"
/> />
<span class="sm:-mb-5">Connect to strongest WiFi</span> <span class="sm:-mb-5">Connect to strongest WiFi</span>
@@ -549,7 +543,7 @@
minlength="7" minlength="7"
maxlength="15" maxlength="15"
size="15" size="15"
bind:value={networkEditable.localIp} bind:value={networkEditable.local_ip}
id="localIP" id="localIP"
required required
/> />
@@ -578,7 +572,7 @@
minlength="7" minlength="7"
maxlength="15" maxlength="15"
size="15" size="15"
bind:value={networkEditable.gatewayIp} bind:value={networkEditable.gateway_ip}
required required
/> />
<label class="label" for="gateway"> <label class="label" for="gateway">
@@ -605,7 +599,7 @@
minlength="7" minlength="7"
maxlength="15" maxlength="15"
size="15" size="15"
bind:value={networkEditable.subnetMask} bind:value={networkEditable.subnet_mask}
required required
/> />
<label class="label" for="subnet"> <label class="label" for="subnet">
@@ -632,7 +626,7 @@
minlength="7" minlength="7"
maxlength="15" maxlength="15"
size="15" size="15"
bind:value={networkEditable.dnsIp1} bind:value={networkEditable.dns_ip_1}
required required
/> />
<label class="label" for="gateway"> <label class="label" for="gateway">
@@ -657,7 +651,7 @@
minlength="7" minlength="7"
maxlength="15" maxlength="15"
size="15" size="15"
bind:value={networkEditable.dnsIp2} bind:value={networkEditable.dns_ip_2}
required required
/> />
<label class="label" for="subnet"> <label class="label" for="subnet">