From a8abaaaf618e5d9a541028a9beb956945cecd3ef Mon Sep 17 00:00:00 2001 From: Niklas Jensen Date: Thu, 1 Jan 2026 20:10:13 +0100 Subject: [PATCH] Adjusted parts of routes layout to work with protobufs --- .../lib/platform_shared/websocket_message.ts | 238 +++++++++++++++++- app/src/lib/stores/socket.ts | 27 +- app/src/lib/stores/telemetry.ts | 28 +-- app/src/lib/types/models.ts | 5 - app/src/routes/+layout.svelte | 48 ++-- compile_proto_TEMP | 2 +- platform_shared/websocket_message.proto | 2 + 7 files changed, 284 insertions(+), 66 deletions(-) diff --git a/app/src/lib/platform_shared/websocket_message.ts b/app/src/lib/platform_shared/websocket_message.ts index 9656f2e..ca53c00 100644 --- a/app/src/lib/platform_shared/websocket_message.ts +++ b/app/src/lib/platform_shared/websocket_message.ts @@ -104,6 +104,16 @@ export interface RSSIData { rssi: number; } +export interface DownloadOTAData { + status: string; + progress: number; + error: string; +} + +export interface SonarData { + dummyField: string; +} + export interface SubscribeNotification { tag: number; } @@ -1604,6 +1614,156 @@ export const RSSIData: MessageFns = { }, }; +function createBaseDownloadOTAData(): DownloadOTAData { + return { status: "", progress: 0, error: "" }; +} + +export const DownloadOTAData: MessageFns = { + encode(message: DownloadOTAData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.status !== "") { + writer.uint32(10).string(message.status); + } + if (message.progress !== 0) { + writer.uint32(16).int32(message.progress); + } + if (message.error !== "") { + writer.uint32(26).string(message.error); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): DownloadOTAData { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDownloadOTAData(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.status = reader.string(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.progress = reader.int32(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.error = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): DownloadOTAData { + return { + status: isSet(object.status) ? globalThis.String(object.status) : "", + progress: isSet(object.progress) ? globalThis.Number(object.progress) : 0, + error: isSet(object.error) ? globalThis.String(object.error) : "", + }; + }, + + toJSON(message: DownloadOTAData): unknown { + const obj: any = {}; + if (message.status !== "") { + obj.status = message.status; + } + if (message.progress !== 0) { + obj.progress = Math.round(message.progress); + } + if (message.error !== "") { + obj.error = message.error; + } + return obj; + }, + + create, I>>(base?: I): DownloadOTAData { + return DownloadOTAData.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): DownloadOTAData { + const message = createBaseDownloadOTAData(); + message.status = object.status ?? ""; + message.progress = object.progress ?? 0; + message.error = object.error ?? ""; + return message; + }, +}; + +function createBaseSonarData(): SonarData { + return { dummyField: "" }; +} + +export const SonarData: MessageFns = { + encode(message: SonarData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.dummyField !== "") { + writer.uint32(10).string(message.dummyField); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SonarData { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSonarData(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.dummyField = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SonarData { + return { dummyField: isSet(object.dummyField) ? globalThis.String(object.dummyField) : "" }; + }, + + toJSON(message: SonarData): unknown { + const obj: any = {}; + if (message.dummyField !== "") { + obj.dummyField = message.dummyField; + } + return obj; + }, + + create, I>>(base?: I): SonarData { + return SonarData.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SonarData { + const message = createBaseSonarData(); + message.dummyField = object.dummyField ?? ""; + return message; + }, +}; + function createBaseSubscribeNotification(): SubscribeNotification { return { tag: 0 }; } @@ -2928,6 +3088,78 @@ export const protoMetadata: ProtoMetadata = { "reservedRange": [], "reservedName": [], "visibility": 0, + }, { + "name": "DownloadOTAData", + "field": [{ + "name": "status", + "number": 1, + "label": 1, + "type": 9, + "typeName": "", + "extendee": "", + "defaultValue": "", + "oneofIndex": 0, + "jsonName": "status", + "options": undefined, + "proto3Optional": false, + }, { + "name": "progress", + "number": 2, + "label": 1, + "type": 5, + "typeName": "", + "extendee": "", + "defaultValue": "", + "oneofIndex": 0, + "jsonName": "progress", + "options": undefined, + "proto3Optional": false, + }, { + "name": "error", + "number": 3, + "label": 1, + "type": 9, + "typeName": "", + "extendee": "", + "defaultValue": "", + "oneofIndex": 0, + "jsonName": "error", + "options": undefined, + "proto3Optional": false, + }], + "extension": [], + "nestedType": [], + "enumType": [], + "extensionRange": [], + "oneofDecl": [], + "options": undefined, + "reservedRange": [], + "reservedName": [], + "visibility": 0, + }, { + "name": "SonarData", + "field": [{ + "name": "dummy_field", + "number": 1, + "label": 1, + "type": 9, + "typeName": "", + "extendee": "", + "defaultValue": "", + "oneofIndex": 0, + "jsonName": "dummyField", + "options": undefined, + "proto3Optional": false, + }], + "extension": [], + "nestedType": [], + "enumType": [], + "extensionRange": [], + "oneofDecl": [], + "options": undefined, + "reservedRange": [], + "reservedName": [], + "visibility": 0, }, { "name": "SubscribeNotification", "field": [{ @@ -3193,8 +3425,8 @@ export const protoMetadata: ProtoMetadata = { "trailingComments": "", "leadingDetachedComments": [], }, { - "path": [4, 18], - "span": [54, 0, 71, 1], + "path": [4, 20], + "span": [56, 0, 73, 1], "leadingComments": " WebSocket message wrapper\n Only ONE field will be set at a time (oneof ensures this)\n", "trailingComments": "", "leadingDetachedComments": [], @@ -3218,6 +3450,8 @@ export const protoMetadata: ProtoMetadata = { ".PeripheralSettingsData": PeripheralSettingsData, ".WifiSettingsData": WifiSettingsData, ".RSSIData": RSSIData, + ".DownloadOTAData": DownloadOTAData, + ".SonarData": SonarData, ".SubscribeNotification": SubscribeNotification, ".UnsubscribeNotification": UnsubscribeNotification, ".PingMsg": PingMsg, diff --git a/app/src/lib/stores/socket.ts b/app/src/lib/stores/socket.ts index 5ecbe67..eb6e64e 100644 --- a/app/src/lib/stores/socket.ts +++ b/app/src/lib/stores/socket.ts @@ -142,19 +142,27 @@ function createWebSocket() { ws.onclose = ev => disconnect('close', ev) } - function unsubscribe(event_type: MessageFns, listener: (data: unknown) => void) { + function unsubscribe(event_type: MessageFns, listener: (data: MT) => void) { const tag = get_tag_from_messagetype(event_type) const message_listeners_totag = message_listeners.get(tag) if (!message_listeners_totag) return // TODO: This looks like it deletes an individual listener, but unsubscribe unsubscribes for everyone. Not sure what it is supposed to do right now - message_listeners_totag?.delete(listener) + message_listeners_totag?.delete(listener as (data?: unknown) => void) if (message_listeners_totag.size == 0) { // No more listeners, so we can unsubscribe - unsubscribeToEvent(event_type) + unsubscribeToMessageFromServer(event_type) } } + function unsubscribe_event(event_type: SocketEvent, listener: (data: unknown) => void) { + + const message_listeners_totag = event_listeners.get(event_type) + if (!message_listeners_totag) return + + message_listeners_totag?.delete(listener) + } + function resetUnresponsiveCheck() { clearTimeout(unresponsiveTimeoutId) unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), reconnectTimeoutTime) @@ -169,7 +177,7 @@ function createWebSocket() { send(wsm) } - function unsubscribeToEvent(event_type: MessageFns) { + function unsubscribeToMessageFromServer(event_type: MessageFns) { if (!ws || ws.readyState !== WebSocket.OPEN) return const event = get_name_from_messagetype(event_type); const unsub_msg = WebsocketMessages.UnsubscribeNotification.create( @@ -201,7 +209,7 @@ function createWebSocket() { subscribe, sendEvent, init, - on: (event_type: MessageFns, listener: (data: T) => void): (() => void) => { + on: (event_type: MessageFns, listener: (data: MT) => void): (() => void) => { const tag = get_tag_from_messagetype(event_type); let message_listeners_totag = message_listeners.get(tag) @@ -213,11 +221,14 @@ function createWebSocket() { message_listeners_totag.add(listener as (data: unknown) => void) return () => { - unsubscribe(event_type, listener as (data: unknown) => void) + unsubscribe(event_type, listener) } }, - off: (event_type: MessageFns, listener: (data: T) => void) => { - unsubscribe(event_type, listener as (data: unknown) => void) + onEvent: (event_type: SocketEvent, listener: (data: unknown) => void): (() => void) => { + + return () => { + unsubscribe_event(event_type, listener) + } } } } diff --git a/app/src/lib/stores/telemetry.ts b/app/src/lib/stores/telemetry.ts index 352ad32..0b66157 100644 --- a/app/src/lib/stores/telemetry.ts +++ b/app/src/lib/stores/telemetry.ts @@ -1,33 +1,23 @@ -import type { DownloadOTA } from '$lib/types/models' +import { DownloadOTAData, RSSIData } from '$lib/platform_shared/websocket_message' +import { DownloadOTA } from '$lib/types/models' import { writable } from 'svelte/store' -const telemetry_data = { - rssi: { - rssi: 0 - }, - download_ota: { - status: 'none', - progress: 0, - error: '' - } +type telemetry_data_type = { + rssi: RSSIData; + download_ota: DownloadOTAData; } +const telemetry_data: telemetry_data_type = { rssi: RSSIData.create(), download_ota: DownloadOTAData.create() }; // Note: perhaps init these as null instead of an undefined create() function createTelemetry() { const { subscribe, update } = writable(telemetry_data) return { subscribe, - setRSSI: (data: number) => { - update(telemetry_data => ({ - ...telemetry_data, - rssi: { rssi: data } - })) + setRSSI: (data: RSSIData) => { + update(telemetry_data => { telemetry_data.rssi = data; return telemetry_data }) }, setDownloadOTA: (data: DownloadOTA) => { - update(telemetry_data => ({ - ...telemetry_data, - download_ota: { status: data.status, progress: data.progress, error: data.error } - })) + update(telemetry_data => { telemetry_data.download_ota = data; return telemetry_data }) } } } diff --git a/app/src/lib/types/models.ts b/app/src/lib/types/models.ts index ceaed57..5f26b2e 100644 --- a/app/src/lib/types/models.ts +++ b/app/src/lib/types/models.ts @@ -59,11 +59,6 @@ export type ApSettings = { subnet_mask: string } -export type DownloadOTA = { - status: string - progress: number - error: string -} export type Rssi = { rssi: number diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte index f5b56b3..0484f21 100644 --- a/app/src/routes/+layout.svelte +++ b/app/src/routes/+layout.svelte @@ -22,15 +22,8 @@ useFeatureFlags, walkGait } from '$lib/stores' - import { type Analytics, type DownloadOTA } from '$lib/types/models' + import { AnalyticsData, AnglesData, DownloadOTAData, ModeData, RSSIData, SonarData } from '$lib/platform_shared/websocket_message' import { Throttler } from '$lib/utilities' - import { - AnglesData, - GaitData, - InputData, - ModeData, - PositionData - } from '$lib/platform_shared/websocket_message' interface Props { children?: import('svelte').Snippet @@ -58,28 +51,27 @@ removeEventListeners() }) + const eventListeners: (() => void)[] = []; const addEventListeners = () => { - socket.on('open', handleOpen) - socket.on('close', handleClose) - socket.on('error', handleError) - socket.on(MessageTopic.rssi, handleNetworkStatus) - socket.on(MessageTopic.mode, (data: ModesEnum) => mode.set(data)) - socket.on(MessageTopic.analytics, handleAnalytics) - socket.on(MessageTopic.angles, (angles: number[]) => { - if (angles.length) servoAngles.set(angles) - }) + eventListeners.push(...[ + socket.onEvent('open', handleOpen), + socket.onEvent('close', handleClose), + socket.onEvent('error', handleError), + socket.on(RSSIData, (data) => telemetry.setRSSI(data)), + socket.on(ModeData, (data) => mode.set(data.mode)), + socket.on(AnalyticsData, (data) => {analytics.addData(data)}), + socket.on(AnglesData, (data) => {servoAngles.set(data.angles)}) + ]) features.subscribe(data => { - if (data?.download_firmware) socket.on(MessageTopic.otastatus, handleOAT) - if (data?.sonar) socket.on(MessageTopic.sonar, data => console.log(data)) + if (data?.download_firmware) eventListeners.push( socket.on(DownloadOTAData, (data) => telemetry.setDownloadOTA(data)) ) + if (data?.sonar) eventListeners.push( socket.on(SonarData, (data) => console.log(data)) ) }) } const removeEventListeners = () => { - socket.off(MessageTopic.analytics, handleAnalytics) - socket.off('open', handleOpen) - socket.off('close', handleClose) - socket.off(MessageTopic.rssi, handleNetworkStatus) - socket.off(MessageTopic.otastatus, handleOAT) + for (let offFunction of eventListeners) { + offFunction(); + } } const handleOpen = () => { @@ -88,17 +80,11 @@ const handleClose = () => { notifications.error('Connection to device lost', 5000) - telemetry.setRSSI(0) + telemetry.setRSSI( RSSIData.create({rssi: 0}) ) } const handleError = (data: unknown) => console.error(data) - const handleAnalytics = (data: Analytics) => analytics.addData(data) - - const handleNetworkStatus = (data: number) => telemetry.setRSSI(data) - - const handleOAT = (data: DownloadOTA) => telemetry.setDownloadOTA(data) - let menuOpen = $state(false) diff --git a/compile_proto_TEMP b/compile_proto_TEMP index ec92a15..ef497d6 100644 --- a/compile_proto_TEMP +++ b/compile_proto_TEMP @@ -3,7 +3,7 @@ This file is temporary, just to show how ot compile the proto files Make sure to actually create the output directories before executing the commands TS: -rotoc --plugin="protoc-gen-ts_proto=$(Resolve-Path app\node_modules\.bin\protoc-gen-ts_proto.CMD)" --ts_proto_out="./app/src/lib" --ts_proto_opt=outputSchema=true ".\platform_shared\websocket_message.proto" +protoc --plugin="protoc-gen-ts_proto=$(Resolve-Path app\node_modules\.bin\protoc-gen-ts_proto.CMD)" --ts_proto_out="./app/src/lib" --ts_proto_opt=outputSchema=true ".\platform_shared\websocket_message.proto" NEW TS (USING PROTOBUFJS): cd app diff --git a/platform_shared/websocket_message.proto b/platform_shared/websocket_message.proto index 458612d..0a73504 100644 --- a/platform_shared/websocket_message.proto +++ b/platform_shared/websocket_message.proto @@ -42,6 +42,8 @@ message I2CScanData { repeated I2CDevice devices = 1; } message PeripheralSettingsData { int32 sda = 1; int32 scl = 2; int32 frequency = 3; repeated PinConfig pins = 4; } message WifiSettingsData { string hostname = 1; bool priority_rssi = 2; repeated KnownNetworkItem wifi_networks = 3; } message RSSIData { int32 rssi = 1; } +message DownloadOTAData { string status = 1; int32 progress = 2; string error = 3; } +message SonarData { string dummy_field = 1; } message SubscribeNotification { int32 tag = 1; } message UnsubscribeNotification {int32 tag = 1; }