♻️ Moves analytics subscription handling to store
This commit is contained in:
+1
-2
@@ -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": {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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(AnalyticsData, data => {
|
|
||||||
// console.log(data);
|
|
||||||
analytics.addData(data)
|
|
||||||
}),
|
|
||||||
socket.on(AnglesData, data => {
|
socket.on(AnglesData, data => {
|
||||||
servoAngles.set(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')
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user