🎨 Format and simplify controls

This commit is contained in:
Rune Harlyk
2026-01-02 22:35:04 +01:00
committed by nikguin04
parent a6b5b0881a
commit 0ef55bcc7e
25 changed files with 6424 additions and 5981 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
declare module "app-env" { declare module 'app-env' {
interface ENV { interface ENV {
VITE_USE_HOST_NAME: boolean; VITE_USE_HOST_NAME: boolean
} }
const appEnv: ENV; const appEnv: ENV
export default appEnv; export default appEnv
} }
+26 -12
View File
@@ -19,14 +19,21 @@
jointNames, jointNames,
currentKinematic, currentKinematic,
walkGait, walkGait,
kinematicData, kinematicData
} from '$lib/stores' } from '$lib/stores'
import { populateModelCache, getToeWorldPositions } from '$lib/utilities' import { populateModelCache, getToeWorldPositions } from '$lib/utilities'
import SceneBuilder from '$lib/sceneBuilder' import SceneBuilder from '$lib/sceneBuilder'
import { lerp, degToRad } from 'three/src/math/MathUtils' import { lerp, degToRad } from 'three/src/math/MathUtils'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js' import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
import { type body_state_t } from '$lib/kinematic' import { type body_state_t } from '$lib/kinematic'
import { BezierState, CalibrationState, GaitState, IdleState, RestState, StandState } from '$lib/gait' import {
BezierState,
CalibrationState,
GaitState,
IdleState,
RestState,
StandState
} from '$lib/gait'
import { radToDeg } from 'three/src/math/MathUtils.js' import { radToDeg } from 'three/src/math/MathUtils.js'
import type { URDFRobot } from 'urdf-loader' import type { URDFRobot } from 'urdf-loader'
import { get } from 'svelte/store' import { get } from 'svelte/store'
@@ -50,10 +57,12 @@
let sceneManager = $state(new SceneBuilder()) let sceneManager = $state(new SceneBuilder())
let canvas: HTMLCanvasElement let canvas: HTMLCanvasElement
const NUM_ANGLES = 12 // TODO: This number should come from the robot
// TODO: This assumes that we have 12 angles (valid for the spot robot) but this should not be a static number defined in each individual data set let currentModelAngles: AnglesData = AnglesData.create({
let currentModelAngles: AnglesData = AnglesData.create({ angles: new Array(12).fill(0) }) angles: new Array(NUM_ANGLES).fill(0)
let modelTargetAngles: AnglesData = AnglesData.create({ angles: new Array(12).fill(0) }) })
let modelTargetAngles: AnglesData = AnglesData.create({ angles: new Array(NUM_ANGLES).fill(0) })
let gui_panel: GUI let gui_panel: GUI
const SMOOTH_AMOUNT = 0.2 const SMOOTH_AMOUNT = 0.2
@@ -63,8 +72,7 @@
let kinematic = get(currentKinematic) let kinematic = get(currentKinematic)
// Incredibly ugly but cant be bothered to fix this or statement right now, we cant key on GaitState objects, only the class extensions themselves (which we dont use here) const planners: Record<ModesEnum, GaitState> = {
const planners: Record<ModesEnum, IdleState | CalibrationState | RestState | StandState | BezierState> = {
[ModesEnum.DEACTIVATED]: new IdleState(), [ModesEnum.DEACTIVATED]: new IdleState(),
[ModesEnum.IDLE]: new IdleState(), [ModesEnum.IDLE]: new IdleState(),
[ModesEnum.CALIBRATION]: new CalibrationState(), [ModesEnum.CALIBRATION]: new CalibrationState(),
@@ -119,7 +127,9 @@
walkGait.subscribe(gait => { walkGait.subscribe(gait => {
const walkPlanner = planners[ModesEnum.WALK] const walkPlanner = planners[ModesEnum.WALK]
if (!(walkPlanner instanceof BezierState)) { if (!(walkPlanner instanceof BezierState)) {
throw new Error(`Expected BezierState for WALK mode, got ${walkPlanner.constructor.name}`) throw new Error(
`Expected BezierState for WALK mode, got ${walkPlanner.constructor.name}`
)
} }
walkPlanner.set_mode(gait.gait) walkPlanner.set_mode(gait.gait)
}) })
@@ -163,14 +173,16 @@
} }
const updateKinematicPosition = () => { const updateKinematicPosition = () => {
kinematicData.set(KinematicData.create({ kinematicData.set(
KinematicData.create({
omega: settings.omega, omega: settings.omega,
phi: settings.phi, phi: settings.phi,
psi: settings.psi, psi: settings.psi,
xm: settings.xm, xm: settings.xm,
ym: settings.ym, ym: settings.ym,
zm: settings.zm zm: settings.zm
})) })
)
} }
const setSceneBackground = (c: string | null) => (sceneManager.scene.background = new Color(c!)) const setSceneBackground = (c: string | null) => (sceneManager.scene.background = new Color(c!))
@@ -178,7 +190,9 @@
const updateAngles = (name: string, angle: number) => { const updateAngles = (name: string, angle: number) => {
modelTargetAngles.angles[$jointNames.indexOf(name)] = angle * (180 / Math.PI) modelTargetAngles.angles[$jointNames.indexOf(name)] = angle * (180 / Math.PI)
servoAnglesOut.set( servoAnglesOut.set(
AnglesData.create({ angles: modelTargetAngles.angles.map(num => Math.round(num)) }) AnglesData.create({
angles: modelTargetAngles.angles.map(num => Math.round(num))
})
) )
} }
@@ -282,7 +296,7 @@
const update_gait = () => { const update_gait = () => {
if (sceneManager.isDragging || !settings['Internal kinematic']) return if (sceneManager.isDragging || !settings['Internal kinematic']) return
const controlData = get(outControllerData) const controlData = get(input)
let planner = planners[get(mode).mode] let planner = planners[get(mode).mode]
const delta = performance.now() - lastTick const delta = performance.now() - lastTick
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { ModeData, ModesEnum } from '$lib/platform_shared/websocket_message' import { ModeData, ModesEnum } from '$lib/platform_shared/websocket_message'
import { mode, modes } from '$lib/stores' import { mode } from '$lib/stores'
const deactivate = async () => { const deactivate = async () => {
mode.set(ModeData.create({ mode: ModesEnum.DEACTIVATED })) mode.set(ModeData.create({ mode: ModesEnum.DEACTIVATED }))
-2
View File
@@ -169,8 +169,6 @@ export class BezierState extends GaitState {
} }
set_mode(mode: WalkGaits, duty?: number, order?: [number, number, number, number]) { set_mode(mode: WalkGaits, duty?: number, order?: [number, number, number, number]) {
console.log('BezierState set_mode', mode)
this.mode = mode this.mode = mode
if (mode === WalkGaits.CRAWL) { if (mode === WalkGaits.CRAWL) {
this.speed_factor = 0.5 this.speed_factor = 0.5
+72 -68
View File
@@ -5,96 +5,96 @@
// source: platform_shared/imu_report.proto // source: platform_shared/imu_report.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire'
export const protobufPackage = ""; export const protobufPackage = ''
export interface IMUReport { export interface IMUReport {
x: number; x: number
y: number; y: number
z: number; z: number
temp: number; temp: number
success: boolean; success: boolean
} }
function createBaseIMUReport(): IMUReport { function createBaseIMUReport(): IMUReport {
return { x: 0, y: 0, z: 0, temp: 0, success: false }; return { x: 0, y: 0, z: 0, temp: 0, success: false }
} }
export const IMUReport: MessageFns<IMUReport> = { export const IMUReport: MessageFns<IMUReport> = {
encode(message: IMUReport, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { encode(message: IMUReport, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.x !== 0) { if (message.x !== 0) {
writer.uint32(13).float(message.x); writer.uint32(13).float(message.x)
} }
if (message.y !== 0) { if (message.y !== 0) {
writer.uint32(21).float(message.y); writer.uint32(21).float(message.y)
} }
if (message.z !== 0) { if (message.z !== 0) {
writer.uint32(29).float(message.z); writer.uint32(29).float(message.z)
} }
if (message.temp !== 0) { if (message.temp !== 0) {
writer.uint32(37).float(message.temp); writer.uint32(37).float(message.temp)
} }
if (message.success !== false) { if (message.success !== false) {
writer.uint32(40).bool(message.success); writer.uint32(40).bool(message.success)
} }
return writer; return writer
}, },
decode(input: BinaryReader | Uint8Array, length?: number): IMUReport { decode(input: BinaryReader | Uint8Array, length?: number): IMUReport {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input); const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
const end = length === undefined ? reader.len : reader.pos + length; const end = length === undefined ? reader.len : reader.pos + length
const message = createBaseIMUReport(); const message = createBaseIMUReport()
while (reader.pos < end) { while (reader.pos < end) {
const tag = reader.uint32(); const tag = reader.uint32()
switch (tag >>> 3) { switch (tag >>> 3) {
case 1: { case 1: {
if (tag !== 13) { if (tag !== 13) {
break; break
} }
message.x = reader.float(); message.x = reader.float()
continue; continue
} }
case 2: { case 2: {
if (tag !== 21) { if (tag !== 21) {
break; break
} }
message.y = reader.float(); message.y = reader.float()
continue; continue
} }
case 3: { case 3: {
if (tag !== 29) { if (tag !== 29) {
break; break
} }
message.z = reader.float(); message.z = reader.float()
continue; continue
} }
case 4: { case 4: {
if (tag !== 37) { if (tag !== 37) {
break; break
} }
message.temp = reader.float(); message.temp = reader.float()
continue; continue
} }
case 5: { case 5: {
if (tag !== 40) { if (tag !== 40) {
break; break
} }
message.success = reader.bool(); message.success = reader.bool()
continue; continue
} }
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break
} }
reader.skip(tag & 7); reader.skip(tag & 7)
} }
return message; return message
}, },
fromJSON(object: any): IMUReport { fromJSON(object: any): IMUReport {
@@ -103,65 +103,69 @@ export const IMUReport: MessageFns<IMUReport> = {
y: isSet(object.y) ? globalThis.Number(object.y) : 0, y: isSet(object.y) ? globalThis.Number(object.y) : 0,
z: isSet(object.z) ? globalThis.Number(object.z) : 0, z: isSet(object.z) ? globalThis.Number(object.z) : 0,
temp: isSet(object.temp) ? globalThis.Number(object.temp) : 0, temp: isSet(object.temp) ? globalThis.Number(object.temp) : 0,
success: isSet(object.success) ? globalThis.Boolean(object.success) : false, success: isSet(object.success) ? globalThis.Boolean(object.success) : false
}; }
}, },
toJSON(message: IMUReport): unknown { toJSON(message: IMUReport): unknown {
const obj: any = {}; const obj: any = {}
if (message.x !== 0) { if (message.x !== 0) {
obj.x = message.x; obj.x = message.x
} }
if (message.y !== 0) { if (message.y !== 0) {
obj.y = message.y; obj.y = message.y
} }
if (message.z !== 0) { if (message.z !== 0) {
obj.z = message.z; obj.z = message.z
} }
if (message.temp !== 0) { if (message.temp !== 0) {
obj.temp = message.temp; obj.temp = message.temp
} }
if (message.success !== false) { if (message.success !== false) {
obj.success = message.success; obj.success = message.success
} }
return obj; return obj
}, },
create<I extends Exact<DeepPartial<IMUReport>, I>>(base?: I): IMUReport { create<I extends Exact<DeepPartial<IMUReport>, I>>(base?: I): IMUReport {
return IMUReport.fromPartial(base ?? ({} as any)); return IMUReport.fromPartial(base ?? ({} as any))
}, },
fromPartial<I extends Exact<DeepPartial<IMUReport>, I>>(object: I): IMUReport { fromPartial<I extends Exact<DeepPartial<IMUReport>, I>>(object: I): IMUReport {
const message = createBaseIMUReport(); const message = createBaseIMUReport()
message.x = object.x ?? 0; message.x = object.x ?? 0
message.y = object.y ?? 0; message.y = object.y ?? 0
message.z = object.z ?? 0; message.z = object.z ?? 0
message.temp = object.temp ?? 0; message.temp = object.temp ?? 0
message.success = object.success ?? false; message.success = object.success ?? false
return message; return message
}, }
}; }
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined
export type DeepPartial<T> = T extends Builtin ? T export type DeepPartial<T> =
T extends Builtin ? T
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> } : T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>; : Partial<T>
type KeysOfUnion<T> = T extends T ? keyof T : never; type KeysOfUnion<T> = T extends T ? keyof T : never
export type Exact<P, I extends P> = P extends Builtin ? P export type Exact<P, I extends P> =
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never }; P extends Builtin ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
[K in Exclude<keyof I, KeysOfUnion<P>>]: never
}
function isSet(value: any): boolean { function isSet(value: any): boolean {
return value !== null && value !== undefined; return value !== null && value !== undefined
} }
export interface MessageFns<T> { export interface MessageFns<T> {
encode(message: T, writer?: BinaryWriter): BinaryWriter; encode(message: T, writer?: BinaryWriter): BinaryWriter
decode(input: BinaryReader | Uint8Array, length?: number): T; decode(input: BinaryReader | Uint8Array, length?: number): T
fromJSON(object: any): T; fromJSON(object: any): T
toJSON(message: T): unknown; toJSON(message: T): unknown
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T; create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T; fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
} }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -1,8 +1,7 @@
import { AnalyticsData } from '$lib/platform_shared/websocket_message' import { AnalyticsData } from '$lib/platform_shared/websocket_message'
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
const analytics_data: AnalyticsData[] = []; const analytics_data: AnalyticsData[] = []
const maxAnalyticsData = 100 const maxAnalyticsData = 100
+1 -2
View File
@@ -1,10 +1,9 @@
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
import { IMUData } from '$lib/platform_shared/websocket_message' import { IMUData } from '$lib/platform_shared/websocket_message'
const imu_data: IMUData[] = []; const imu_data: IMUData[] = []
const maxIMUData = 100 const maxIMUData = 100
export const imu = (() => { export const imu = (() => {
const { subscribe, update } = writable(imu_data) const { subscribe, update } = writable(imu_data)
+38 -27
View File
@@ -1,4 +1,12 @@
import { HumanInputData, KinematicData, ModeData, ModesEnum, WalkGaitData, WalkGaits } from '$lib/platform_shared/websocket_message' import Kinematic from '$lib/kinematic'
import {
HumanInputData,
KinematicData,
ModeData,
ModesEnum,
WalkGaitData,
WalkGaits
} from '$lib/platform_shared/websocket_message'
import { persistentStore } from '$lib/utilities/svelte-utilities' import { persistentStore } from '$lib/utilities/svelte-utilities'
import { writable, type Writable } from 'svelte/store' import { writable, type Writable } from 'svelte/store'
@@ -10,36 +18,39 @@ export const model = writable()
export const mode: Writable<ModeData> = writable(ModeData.create({ mode: ModesEnum.DEACTIVATED })) export const mode: Writable<ModeData> = writable(ModeData.create({ mode: ModesEnum.DEACTIVATED }))
export const walkGait: Writable<WalkGaitData> = writable( WalkGaitData.create({gait: WalkGaits.TROT }) ) export const walkGait: Writable<WalkGaitData> = writable(
WalkGaitData.create({ gait: WalkGaits.TROT })
export const outControllerData = writable( HumanInputData.create( {left: {x:0,y:0}, right: {x:0,y:0}, height:0, s1:0, speed:0} ) ) )
export const kinematicData = writable(KinematicData.create()) export const kinematicData = writable(KinematicData.create())
export const input: Writable<HumanInputData> = writable( HumanInputData.create( {left: {x:0,y:0}, right: {x:0,y:0}, height:0, s1:0, speed:0} ) ) export const input: Writable<HumanInputData> = writable(
HumanInputData.create({
left: { x: 0, y: 0 },
right: { x: 0, y: 0 },
height: 0.7,
s1: 0.5,
speed: 0.5
})
)
function enumToValuesAndLabels<T extends number>(enumObj: Record<string, T | string>) {
const entries = Object.entries(enumObj).filter(
([key, v]) => typeof v === 'number' && key !== 'UNRECOGNIZED'
) as [string, T][]
// Following code is generated from CLAUDE CODE return {
// Auto-generate modes array from ModesEnum (excluding UNRECOGNIZED) values: entries.map(([, v]) => v),
export const modes = Object.values(ModesEnum) labels: Object.fromEntries(
.filter((v): v is ModesEnum => typeof v === 'number' && v !== ModesEnum.UNRECOGNIZED) entries.map(([k, v]) => [v, k.charAt(0) + k.slice(1).toLowerCase()])
) as Record<T, string>
}
}
// Auto-generate mode labels from enum keys const modesData = enumToValuesAndLabels<ModesEnum>(ModesEnum)
export const modeLabels: Record<ModesEnum, string> = Object.entries(ModesEnum) export const modes = modesData.values
.filter(([_, v]) => typeof v === 'number' && v !== ModesEnum.UNRECOGNIZED) export const modeLabels = modesData.labels
.reduce((acc, [key, value]) => {
acc[value as ModesEnum] = key.charAt(0) + key.slice(1).toLowerCase()
return acc
}, {} as Record<ModesEnum, string>)
// Auto-generate walk gaits array from WalkGaits enum (excluding UNRECOGNIZED) const walkGaitsData = enumToValuesAndLabels<WalkGaits>(WalkGaits)
export const walkGaits = Object.values(WalkGaits) export const walkGaits = walkGaitsData.values
.filter((v): v is WalkGaits => typeof v === 'number' && v !== WalkGaits.UNRECOGNIZED) export const walkGaitLabels = walkGaitsData.labels
// Auto-generate walk gait labels from enum keys
export const walkGaitLabels: Record<WalkGaits, string> = Object.entries(WalkGaits)
.filter(([_, v]) => typeof v === 'number' && v !== WalkGaits.UNRECOGNIZED)
.reduce((acc, [key, value]) => {
acc[value as WalkGaits] = key.charAt(0) + key.slice(1).toLowerCase()
return acc
}, {} as Record<WalkGaits, string>)
+6 -2
View File
@@ -1,8 +1,12 @@
import { AnglesData } from '$lib/platform_shared/websocket_message' import { AnglesData } from '$lib/platform_shared/websocket_message'
import { writable, type Writable } from 'svelte/store' import { writable, type Writable } from 'svelte/store'
export const servoAnglesOut: Writable<AnglesData> = writable(AnglesData.create({angles: [0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90]})) export const servoAnglesOut: Writable<AnglesData> = writable(
export const servoAngles: Writable<AnglesData> = writable(AnglesData.create({angles: [0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90]})) AnglesData.create({ angles: [0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90] })
)
export const servoAngles: Writable<AnglesData> = writable(
AnglesData.create({ angles: [0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90] })
)
export const logs = writable([] as string[]) export const logs = writable([] as string[])
export const mpu = writable({ heading: 0 }) export const mpu = writable({ heading: 0 })
+39 -38
View File
@@ -1,10 +1,13 @@
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
import { encode, decode } from '@msgpack/msgpack' import { encode, decode } from '@msgpack/msgpack'
import { WebsocketMessage, type MessageFns, protoMetadata as websocket_md } from '$lib/platform_shared/websocket_message' import {
WebsocketMessage,
type MessageFns,
protoMetadata as websocket_md
} from '$lib/platform_shared/websocket_message'
import * as WebsocketMessages from '$lib/platform_shared/websocket_message' import * as WebsocketMessages from '$lib/platform_shared/websocket_message'
import type { BinaryWriter } from '@bufbuild/protobuf/wire' import type { BinaryWriter } from '@bufbuild/protobuf/wire'
// -------- START PARSING PROTO DATA -------- // -------- START PARSING PROTO DATA --------
// Auto-build reverse mapping from MessageFns to event key and tag // Auto-build reverse mapping from MessageFns to event key and tag
export const MESSAGE_TYPE_TO_KEY = new Map<MessageFns<any>, string>() export const MESSAGE_TYPE_TO_KEY = new Map<MessageFns<any>, string>()
@@ -33,7 +36,9 @@ if (websocketMessageType?.field) {
function get_name_from_messagetype(event_type: MessageFns<any>): string { function get_name_from_messagetype(event_type: MessageFns<any>): string {
const event = MESSAGE_TYPE_TO_KEY.get(event_type) const event = MESSAGE_TYPE_TO_KEY.get(event_type)
if (!event) { if (!event) {
throw new Error("Event type not found in 'WebsocketMessage'. The MessageFns you passed doesn't correspond to any WebsocketMessage field."); throw new Error(
"Event type not found in 'WebsocketMessage'. The MessageFns you passed doesn't correspond to any WebsocketMessage field."
)
} }
return event return event
} }
@@ -42,7 +47,9 @@ function get_name_from_messagetype(event_type: MessageFns<any>): string {
function get_tag_from_messagetype(event_type: MessageFns<any>): number { function get_tag_from_messagetype(event_type: MessageFns<any>): number {
const fieldNumber = MESSAGE_TYPE_TO_TAG.get(event_type) const fieldNumber = MESSAGE_TYPE_TO_TAG.get(event_type)
if (fieldNumber === undefined) { if (fieldNumber === undefined) {
throw new Error("Tag not found in 'WebsocketMessage'. The MessageFns you passed doesn't correspond to any WebsocketMessage field."); throw new Error(
"Tag not found in 'WebsocketMessage'. The MessageFns you passed doesn't correspond to any WebsocketMessage field."
)
} }
return fieldNumber return fieldNumber
} }
@@ -52,29 +59,26 @@ function get_tag_from_messagetype(event_type: MessageFns<any>): number {
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const
type SocketEvent = (typeof socketEvents)[number] type SocketEvent = (typeof socketEvents)[number]
type TaggedSocketMessage = {"tag": number, "msg": WebsocketMessage} type TaggedSocketMessage = { tag: number; msg: WebsocketMessage }
// Only exported for socket test // Only exported for socket test
export const decodeMessage = (data: ArrayBuffer): TaggedSocketMessage => { export const decodeMessage = (data: ArrayBuffer): TaggedSocketMessage => {
const decoded = WebsocketMessage.decode(new Uint8Array(data))
const decoded = WebsocketMessage.decode(new Uint8Array(data));
const values = Object.entries(decoded).filter(([, value]) => value !== undefined) // Filter all values which are not undefined const values = Object.entries(decoded).filter(([, value]) => value !== undefined) // Filter all values which are not undefined
if (values.length != 1) { if (values.length != 1) {
throw new Error("Message included either 0 or more than 1 data point") throw new Error('Message included either 0 or more than 1 data point')
} }
const fieldName = values[0][0] const fieldName = values[0][0]
const tag = MESSAGE_KEY_TO_TAG.get(fieldName) const tag = MESSAGE_KEY_TO_TAG.get(fieldName)
if (tag === undefined) { if (tag === undefined) {
throw new Error(`Tag not found for field: ${fieldName}`) throw new Error(`Tag not found for field: ${fieldName}`)
} }
return {"tag": tag, "msg": decoded} return { tag: tag, msg: decoded }
} }
export const encodeMessage = (data: WebsocketMessage): Uint8Array<ArrayBuffer> => { export const encodeMessage = (data: WebsocketMessage): Uint8Array<ArrayBuffer> => {
const encoded = WebsocketMessage.encode(data).finish(); const encoded = WebsocketMessage.encode(data).finish()
return encoded; return encoded
} }
function createWebSocket() { function createWebSocket() {
@@ -95,19 +99,18 @@ function createWebSocket() {
function getMsgListeners<MT>(event_type: MessageFns<MT>): Set<(data?: unknown) => void> { function getMsgListeners<MT>(event_type: MessageFns<MT>): Set<(data?: unknown) => void> {
const type_tag = get_tag_from_messagetype(event_type) const type_tag = get_tag_from_messagetype(event_type)
const type_listeners = message_listeners.get(type_tag); const type_listeners = message_listeners.get(type_tag)
if (type_listeners == undefined) { if (type_listeners == undefined) {
return new Set() return new Set()
} }
return type_listeners; return type_listeners
} }
function getListeners<MT>(event: string): Set<(data?: unknown) => void> { function getListeners<MT>(event: string): Set<(data?: unknown) => void> {
const event_listeners_forevent = event_listeners.get(event)
const event_listeners_forevent = event_listeners.get(event);
if (event_listeners_forevent == undefined) { if (event_listeners_forevent == undefined) {
return new Set() return new Set()
} }
return event_listeners_forevent; return event_listeners_forevent
} }
function disconnect(reason: SocketEvent, event?: Event) { function disconnect(reason: SocketEvent, event?: Event) {
@@ -149,14 +152,13 @@ function createWebSocket() {
// TODO: This looks like it deletes an individual listener, but unsubscribe unsubscribes for everyone. Not sure what it is supposed to do right now // 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 as (data?: unknown) => void) message_listeners_totag?.delete(listener as (data?: unknown) => void)
if (message_listeners_totag.size == 0) { // No more listeners, so we can unsubscribe if (message_listeners_totag.size == 0) {
// No more listeners, so we can unsubscribe
unsubscribeToMessageFromServer(event_type) unsubscribeToMessageFromServer(event_type)
} }
} }
function unsubscribe_event(event_type: SocketEvent, listener: (data: unknown) => void) { function unsubscribe_event(event_type: SocketEvent, listener: (data: unknown) => void) {
const message_listeners_totag = event_listeners.get(event_type) const message_listeners_totag = event_listeners.get(event_type)
if (!message_listeners_totag) return if (!message_listeners_totag) return
@@ -171,34 +173,34 @@ function createWebSocket() {
// T must extend a type of WebsocketMessages // T must extend a type of WebsocketMessages
function sendEvent<T>(event: MessageFns<T>, data: T) { function sendEvent<T>(event: MessageFns<T>, data: T) {
if (!ws || ws.readyState !== WebSocket.OPEN) return if (!ws || ws.readyState !== WebSocket.OPEN) return
const type = get_name_from_messagetype(event); const type = get_name_from_messagetype(event)
const wsm = WebsocketMessage.create(); const wsm = WebsocketMessage.create()
(wsm as any)[type] = data ;(wsm as any)[type] = data
send(wsm) send(wsm)
} }
function unsubscribeToMessageFromServer<T>(event_type: MessageFns<T>) { function unsubscribeToMessageFromServer<T>(event_type: MessageFns<T>) {
if (!ws || ws.readyState !== WebSocket.OPEN) return if (!ws || ws.readyState !== WebSocket.OPEN) return
const event = get_name_from_messagetype(event_type); const event = get_name_from_messagetype(event_type)
const unsub_msg = WebsocketMessages.UnsubscribeNotification.create( const unsub_msg = WebsocketMessages.UnsubscribeNotification.create({
{tag: get_tag_from_messagetype(event_type)} tag: get_tag_from_messagetype(event_type)
); })
send(WebsocketMessage.create({unsubNotif: unsub_msg})); send(WebsocketMessage.create({ unsubNotif: unsub_msg }))
} }
function subscribeToEvent<T>(event_type: MessageFns<T>) { function subscribeToEvent<T>(event_type: MessageFns<T>) {
if (!ws || ws.readyState !== WebSocket.OPEN) return if (!ws || ws.readyState !== WebSocket.OPEN) return
const event = get_name_from_messagetype(event_type); const event = get_name_from_messagetype(event_type)
const sub_msg = WebsocketMessages.SubscribeNotification.create( const sub_msg = WebsocketMessages.SubscribeNotification.create({
{tag: get_tag_from_messagetype(event_type)} tag: get_tag_from_messagetype(event_type)
); })
send(WebsocketMessage.create({subNotif: sub_msg})); send(WebsocketMessage.create({ subNotif: sub_msg }))
} }
function send(data: WebsocketMessage) { function send(data: WebsocketMessage) {
if (!ws || ws.readyState !== WebSocket.OPEN) return if (!ws || ws.readyState !== WebSocket.OPEN) return
const encoded = encodeMessage(data); const encoded = encodeMessage(data)
ws.send(encoded); ws.send(encoded)
} }
function ping() { function ping() {
@@ -210,7 +212,7 @@ function createWebSocket() {
sendEvent, sendEvent,
init, init,
on: <MT>(event_type: MessageFns<MT>, listener: (data: MT) => void): (() => void) => { on: <MT>(event_type: MessageFns<MT>, listener: (data: MT) => void): (() => void) => {
const tag = get_tag_from_messagetype(event_type); const tag = get_tag_from_messagetype(event_type)
let message_listeners_totag = message_listeners.get(tag) let message_listeners_totag = message_listeners.get(tag)
if (!message_listeners_totag) { if (!message_listeners_totag) {
@@ -225,7 +227,6 @@ function createWebSocket() {
} }
}, },
onEvent: (event_type: SocketEvent, listener: (data: unknown) => void): (() => void) => { onEvent: (event_type: SocketEvent, listener: (data: unknown) => void): (() => void) => {
return () => { return () => {
unsubscribe_event(event_type, listener) unsubscribe_event(event_type, listener)
} }
+14 -5
View File
@@ -2,10 +2,13 @@ import { DownloadOTAData, RSSIData } from '$lib/platform_shared/websocket_messag
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
type telemetry_data_type = { type telemetry_data_type = {
rssi: RSSIData; rssi: RSSIData
download_ota: DownloadOTAData; 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() 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() { function createTelemetry() {
const { subscribe, update } = writable(telemetry_data) const { subscribe, update } = writable(telemetry_data)
@@ -13,10 +16,16 @@ function createTelemetry() {
return { return {
subscribe, subscribe,
setRSSI: (data: RSSIData) => { setRSSI: (data: RSSIData) => {
update(telemetry_data => { telemetry_data.rssi = data; return telemetry_data }) update(telemetry_data => {
telemetry_data.rssi = data
return telemetry_data
})
}, },
setDownloadOTA: (data: DownloadOTAData) => { setDownloadOTA: (data: DownloadOTAData) => {
update(telemetry_data => { telemetry_data.download_ota = data; return telemetry_data }) update(telemetry_data => {
telemetry_data.download_ota = data
return telemetry_data
})
} }
} }
} }
-9
View File
@@ -1,8 +1,5 @@
import type { AnalyticsData } from "$lib/platform_shared/websocket_message";
export type vector = { x: number; y: number } export type vector = { x: number; y: number }
export type GithubRelease = { export type GithubRelease = {
message: string message: string
tag_name: string tag_name: string
@@ -12,13 +9,10 @@ export type GithubRelease = {
}> }>
} }
export type NetworkList = { export type NetworkList = {
networks: NetworkItem[] networks: NetworkItem[]
} }
export type NetworkItem = { export type NetworkItem = {
rssi: number rssi: number
ssid: string ssid: string
@@ -46,14 +40,11 @@ export type ApSettings = {
subnet_mask: string subnet_mask: string
} }
export type Rssi = { export type Rssi = {
rssi: number rssi: number
ssid: string ssid: string
} }
export type CameraSettings = { export type CameraSettings = {
framesize: number framesize: number
quality: number quality: number
+36 -13
View File
@@ -21,7 +21,17 @@
useFeatureFlags, useFeatureFlags,
walkGait walkGait
} from '$lib/stores' } from '$lib/stores'
import { AnalyticsData, AnglesData, DownloadOTAData, HumanInputData, KinematicData, ModeData, RSSIData, SonarData, WalkGaitData } from '$lib/platform_shared/websocket_message' import {
AnalyticsData,
AnglesData,
DownloadOTAData,
HumanInputData,
KinematicData,
ModeData,
RSSIData,
SonarData,
WalkGaitData
} from '$lib/platform_shared/websocket_message'
import { Throttler } from '$lib/utilities' import { Throttler } from '$lib/utilities'
interface Props { interface Props {
@@ -39,10 +49,14 @@
addEventListeners() addEventListeners()
outControllerData.subscribe(data => socket.sendEvent(HumanInputData, data)) input.subscribe(data =>
throttler.throttle(() => socket.sendEvent(HumanInputData, data), 100)
)
mode.subscribe(data => socket.sendEvent(ModeData, data)) mode.subscribe(data => socket.sendEvent(ModeData, data))
walkGait.subscribe(data => socket.sendEvent(WalkGaitData, data)) walkGait.subscribe(data => socket.sendEvent(WalkGaitData, data))
servoAnglesOut.subscribe(data => socket.sendEvent(AnglesData, data)) servoAnglesOut.subscribe(data =>
throttler.throttle(() => socket.sendEvent(AnglesData, data), 100)
)
kinematicData.subscribe(data => socket.sendEvent(KinematicData, data)) kinematicData.subscribe(data => socket.sendEvent(KinematicData, data))
}) })
@@ -50,26 +64,35 @@
removeEventListeners() removeEventListeners()
}) })
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) => {analytics.addData(data)}), socket.on(AnalyticsData, data => {
socket.on(AnglesData, (data) => {servoAngles.set(data)}) analytics.addData(data)
]) }),
socket.on(AnglesData, data => {
servoAngles.set(data)
})
]
)
features.subscribe(data => { features.subscribe(data => {
if (data?.download_firmware) eventListeners.push( socket.on(DownloadOTAData, (data) => telemetry.setDownloadOTA(data)) ) if (data?.download_firmware)
if (data?.sonar) eventListeners.push( socket.on(SonarData, (data) => console.log(data)) ) eventListeners.push(
socket.on(DownloadOTAData, data => telemetry.setDownloadOTA(data))
)
if (data?.sonar) eventListeners.push(socket.on(SonarData, data => console.log(data)))
}) })
} }
const removeEventListeners = () => { const removeEventListeners = () => {
for (let offFunction of eventListeners) { for (let offFunction of eventListeners) {
offFunction(); offFunction()
} }
} }
@@ -15,7 +15,6 @@
import { gamepadAxes, gamepadButtonsEdges, hasGamepad } from '$lib/stores/gamepad' import { gamepadAxes, gamepadButtonsEdges, hasGamepad } from '$lib/stores/gamepad'
import { notifications } from '$lib/components/toasts/notifications' import { notifications } from '$lib/components/toasts/notifications'
import { import {
HumanInputData,
ModeData, ModeData,
ModesEnum, ModesEnum,
WalkGaitData, WalkGaitData,
@@ -25,14 +24,6 @@
let left: nipplejs.JoystickManager let left: nipplejs.JoystickManager
let right: nipplejs.JoystickManager let right: nipplejs.JoystickManager
let data: HumanInputData = HumanInputData.create({
left: { x: 0, y: 0 },
right: { x: 0, y: 0 },
height: 0,
s1: 0,
speed: 0
})
$effect(() => { $effect(() => {
if ($hasGamepad) { if ($hasGamepad) {
notifications.success('🎮 Gamepad connected', 3000) notifications.success('🎮 Gamepad connected', 3000)
+6 -2
View File
@@ -4,7 +4,11 @@
import { socket } from '$lib/stores' import { socket } from '$lib/stores'
import { Connection } from '$lib/components/icons' import { Connection } from '$lib/components/icons'
import I2CSetting from './i2cSetting.svelte' import I2CSetting from './i2cSetting.svelte'
import { I2CDevice, I2CScanData, I2CScanDataRequest } from '$lib/platform_shared/websocket_message' import {
I2CDevice,
I2CScanData,
I2CScanDataRequest
} from '$lib/platform_shared/websocket_message'
// TODO: Delete this completely, this should be done on esp side, as it decides what addresses are actually valid, as for example ICM20948 and MPU6050 can have same address // TODO: Delete this completely, this should be done on esp side, as it decides what addresses are actually valid, as for example ICM20948 and MPU6050 can have same address
// const i2cDevices = [ // const i2cDevices = [
@@ -66,7 +70,7 @@
<div>No I2C devices found</div> <div>No I2C devices found</div>
{:else} {:else}
{#each active_devices as device (device.address)} {#each active_devices as device (device.address)}
<div>[{device.address.toString(16)}] {device.part_number} - {device.name}</div> <div>[{device.address.toString(16)}] {device.partNumber} - {device.name}</div>
{/each} {/each}
{/if} {/if}
</div> </div>
@@ -4,7 +4,10 @@
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { modals } from 'svelte-modals' import { modals } from 'svelte-modals'
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte' import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'
import { PeripheralSettingsData, PeripheralSettingsDataRequest } from '$lib/platform_shared/websocket_message' import {
PeripheralSettingsData,
PeripheralSettingsDataRequest
} from '$lib/platform_shared/websocket_message'
let settings: PeripheralSettingsData | null = $state(null) let settings: PeripheralSettingsData | null = $state(null)
let isEditing = $state(false) let isEditing = $state(false)
+21 -11
View File
@@ -9,9 +9,11 @@
import { useFeatureFlags } from '$lib/stores/featureFlags' import { useFeatureFlags } from '$lib/stores/featureFlags'
import { Rotate3d } from '$lib/components/icons' import { Rotate3d } from '$lib/components/icons'
import { IMUReport } from '$lib/platform_shared/imu_report'; import {
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; IMUCalibrateData,
import { IMUCalibrateData, IMUCalibrateExecute, IMUData } from '$lib/platform_shared/websocket_message' IMUCalibrateExecute,
IMUData
} from '$lib/platform_shared/websocket_message'
Chart.register(...registerables) Chart.register(...registerables)
@@ -204,23 +206,31 @@
} }
if ($features.bmp) { if ($features.bmp) {
updateChartData(tempChart, $imu.map(datapoint => datapoint.bmpTemp)) updateChartData(
updateChartData(altitudeChart, $imu.map(datapoint => datapoint.altitude)) tempChart,
$imu.map(datapoint => datapoint.bmpTemp)
)
updateChartData(
altitudeChart,
$imu.map(datapoint => datapoint.altitude)
)
} }
} }
const eventListeners: (() => void)[] = []; const eventListeners: (() => void)[] = []
onMount(() => { onMount(() => {
eventListeners.push(...[ eventListeners.push(
socket.on(IMUData, (data) => { ...[
socket.on(IMUData, data => {
console.log(data) console.log(data)
imu.addData(data) imu.addData(data)
}), }),
socket.on(IMUCalibrateData, (data) => { socket.on(IMUCalibrateData, data => {
isCalibrating = false isCalibrating = false
calibrationResult = data calibrationResult = data
}) })
]) ]
)
initializeCharts() initializeCharts()
intervalId = setInterval(updateData, 200) intervalId = setInterval(updateData, 200)
@@ -228,7 +238,7 @@
onDestroy(() => { onDestroy(() => {
for (let offFunction of eventListeners) { for (let offFunction of eventListeners) {
offFunction(); offFunction()
} }
clearInterval(intervalId) clearInterval(intervalId)
}) })
+5 -11
View File
@@ -1,9 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { socket } from '$lib/stores' import { socket } from '$lib/stores'
//import { IMUReport, IMUType } from '$lib/platform_shared/example'; //import { IMUReport, IMUType } from '$lib/platform_shared/example';
import { AnglesData, WebsocketMessage, IMUData } from '$lib/platform_shared/websocket_message' import { AnglesData, WebsocketMessage, IMUData } from '$lib/platform_shared/websocket_message'
@@ -20,12 +18,12 @@
const wmd: WebsocketMessage = { rssi: { rssi: 16 } } const wmd: WebsocketMessage = { rssi: { rssi: 16 } }
// const wmd: WebsocketMessage = { imu: {temp: 1, x: 2, y: 4, z: 5} } // const wmd: WebsocketMessage = { imu: {temp: 1, x: 2, y: 4, z: 5} }
// const wmd: WebsocketMessage = { angles: {angles: [1,2,3,4]} } // const wmd: WebsocketMessage = { angles: {angles: [1,2,3,4]} }
const writer = WebsocketMessage.encode(wmd); const writer = WebsocketMessage.encode(wmd)
const bytes = writer.finish(); const bytes = writer.finish()
// Convert bytes to hex // Convert bytes to hex
const hex = Array.from(bytes) const hex = Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0')) .map(b => b.toString(16).padStart(2, '0'))
.join(' '); .join(' ')
// const decodedmsg: WebsocketMessage = WebsocketMessage.decode(bytes); // const decodedmsg: WebsocketMessage = WebsocketMessage.decode(bytes);
// const objects = Object.entries(decodedmsg) // const objects = Object.entries(decodedmsg)
@@ -46,17 +44,13 @@
// } // }
const handleData = (data: IMUData) => { const handleData = (data: IMUData) => {
console.log(data)
console.log(data);
} }
onMount(() => { onMount(() => {
return socket.on(IMUData, handleData) return socket.on(IMUData, handleData)
}) })
</script> </script>
<h1>Hexadecimal Output</h1> <h1>Hexadecimal Output</h1>
<p><strong>Hex output:</strong> {hex}</p> <p><strong>Hex output:</strong> {hex}</p>
@@ -51,9 +51,11 @@
const postSleep = async () => await api.post('api/sleep') const postSleep = async () => await api.post('api/sleep')
let unsub: (() => void) | undefined = undefined; let unsub: (() => void) | undefined = undefined
onMount(() => unsub = socket.on(AnalyticsData, handleSystemData)) onMount(() => (unsub = socket.on(AnalyticsData, handleSystemData)))
onDestroy(() => { if (unsub) unsub() }) onDestroy(() => {
if (unsub) unsub()
})
const handleSystemData = (data: AnalyticsData) => { const handleSystemData = (data: AnalyticsData) => {
if (systemInformation) { if (systemInformation) {
@@ -179,7 +181,9 @@
icon={Speed} icon={Speed}
title="CPU Frequency" title="CPU Frequency"
description={`${systemInformation.staticSystemInformation?.cpuFreqMhz} MHz ${ description={`${systemInformation.staticSystemInformation?.cpuFreqMhz} MHz ${
systemInformation.staticSystemInformation?.cpuCores == 2 ? 'Dual Core' : 'Single Core' systemInformation.staticSystemInformation?.cpuCores == 2 ?
'Dual Core'
: 'Single Core'
}`} }`}
/> />
@@ -199,11 +203,14 @@
icon={Sketch} icon={Sketch}
title="Sketch (Used / Free)" title="Sketch (Used / Free)"
description={`${( description={`${(
(systemInformation.staticSystemInformation!.sketchSize / systemInformation.staticSystemInformation!.freeSketchSpace) * (systemInformation.staticSystemInformation!.sketchSize /
systemInformation.staticSystemInformation!.freeSketchSpace) *
100 100
).toFixed(1)} % of ).toFixed(1)} % of
${systemInformation.staticSystemInformation!.freeSketchSpace / 1000000} MB used (${ ${systemInformation.staticSystemInformation!.freeSketchSpace / 1000000} MB used (${
(systemInformation.staticSystemInformation!.freeSketchSpace - systemInformation.staticSystemInformation!.sketchSize) / 1000000 (systemInformation.staticSystemInformation!.freeSketchSpace -
systemInformation.staticSystemInformation!.sketchSize) /
1000000
} MB free)`} } MB free)`}
/> />
@@ -219,10 +226,15 @@
icon={Folder} icon={Folder}
title="File System (Used / Total)" title="File System (Used / Total)"
description={`${( description={`${(
(systemInformation.analyticsData!.fsUsed / systemInformation.analyticsData!.fsTotal) * (systemInformation.analyticsData!.fsUsed /
systemInformation.analyticsData!.fsTotal) *
100 100
).toFixed(1)} % of ${systemInformation.analyticsData!.fsTotal / 1000000} MB used (${ ).toFixed(
(systemInformation.analyticsData!.fsTotal - systemInformation.analyticsData!.fsUsed) / 1000000 1
)} % of ${systemInformation.analyticsData!.fsTotal / 1000000} MB used (${
(systemInformation.analyticsData!.fsTotal -
systemInformation.analyticsData!.fsUsed) /
1000000
} }
MB free)`} MB free)`}
/> />
+4 -2
View File
@@ -84,7 +84,7 @@
return wifiSettings return wifiSettings
} }
let unsub_obj: (() => void) | undefined = undefined; let unsub_obj: (() => void) | undefined = undefined
onMount(() => { onMount(() => {
unsub_obj = socket.on<WifiSettings>(WifiSettings, data => { unsub_obj = socket.on<WifiSettings>(WifiSettings, data => {
wifiSettings = data wifiSettings = data
@@ -92,7 +92,9 @@
}) })
}) })
onDestroy(() => { if (unsub_obj) unsub_obj() } ) 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()) {
+23 -19
View File
@@ -1,10 +1,16 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { WebSocketServer } from 'ws' import { WebSocketServer } from 'ws'
import { decodeMessage, MESSAGE_KEY_TO_TAG, socket } from '../../src/lib/stores/socket' import { decodeMessage, MESSAGE_KEY_TO_TAG, socket } from '../../src/lib/stores/socket'
import { IMUData, PingMsg, PongMsg, RSSIData, WebsocketMessage, protoMetadata as websocket_md } from '../../src/lib/platform_shared/websocket_message' import {
IMUData,
PingMsg,
PongMsg,
WebsocketMessage
} from '../../src/lib/platform_shared/websocket_message'
// Helper function to create encoded WebSocket messages // Helper function to create encoded WebSocket messages
function createEncodedMessage(messageType: 'imu' | 'rssi' | 'mode', data: any): Uint8Array { function createEncodedMessage(messageType: 'imu' | 'rssi' | 'mode', data: unknown): Uint8Array {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const message: any = {} const message: any = {}
message[messageType] = data message[messageType] = data
const wsMessage = WebsocketMessage.create(message) const wsMessage = WebsocketMessage.create(message)
@@ -23,15 +29,15 @@ describe.sequential('WebSocket Integration Tests', () => {
wss = new WebSocketServer({ port: TEST_PORT }) wss = new WebSocketServer({ port: TEST_PORT })
// Wait for server to start // Wait for server to start
await new Promise<void>((resolve) => { await new Promise<void>(resolve => {
wss.on('listening', () => resolve()) wss.on('listening', () => resolve())
}) })
}) })
afterEach(async () => { afterEach(async () => {
// Close all connections and server // Close all connections and server
wss.clients.forEach((client) => client.close()) wss.clients.forEach(client => client.close())
await new Promise<void>((resolve) => { await new Promise<void>(resolve => {
wss.close(() => resolve()) wss.close(() => resolve())
}) })
// Wait a bit for cleanup // Wait a bit for cleanup
@@ -56,7 +62,7 @@ describe.sequential('WebSocket Integration Tests', () => {
let receivedIMUData: any = null let receivedIMUData: any = null
// Subscribe to IMU messages before connecting // Subscribe to IMU messages before connecting
const unsubscribe = socket.on(IMUData, (data) => { const unsubscribe = socket.on(IMUData, data => {
receivedIMUData = data receivedIMUData = data
}) })
@@ -64,8 +70,8 @@ describe.sequential('WebSocket Integration Tests', () => {
socket.init(`ws://localhost:${TEST_PORT}`) socket.init(`ws://localhost:${TEST_PORT}`)
// Wait for client to connect // Wait for client to connect
await new Promise<void>((resolve) => { await new Promise<void>(resolve => {
wss.on('connection', (ws) => { wss.on('connection', ws => {
// Server sends IMU data to client // Server sends IMU data to client
const imuPayload = IMUData.create({ const imuPayload = IMUData.create({
x: 3.25, x: 3.25,
@@ -110,7 +116,7 @@ describe.sequential('WebSocket Integration Tests', () => {
reject(new Error('Test timeout - server did not receive message')) reject(new Error('Test timeout - server did not receive message'))
}, 3000) }, 3000)
wss.on('connection', (ws) => { wss.on('connection', ws => {
// console.log('Server: Client connected') // console.log('Server: Client connected')
// Server listens for messages from client // Server listens for messages from client
@@ -175,7 +181,6 @@ describe.sequential('WebSocket Integration Tests', () => {
expect(serverReceivedData?.imu.pressure).toBe(23) expect(serverReceivedData?.imu.pressure).toBe(23)
}) })
it('should fail to serialize data on sendEvent', async () => { it('should fail to serialize data on sendEvent', async () => {
// Connect socket // Connect socket
socket.init(`ws://localhost:${TEST_PORT}`) socket.init(`ws://localhost:${TEST_PORT}`)
@@ -206,7 +211,6 @@ describe.sequential('WebSocket Integration Tests', () => {
describe('WebsocketMessage Protobuf Encoding/Decoding', () => { describe('WebsocketMessage Protobuf Encoding/Decoding', () => {
it('should encode and decode IMU data correctly', () => { it('should encode and decode IMU data correctly', () => {
const imuData = IMUData.create({ const imuData = IMUData.create({
x: 3.25, x: 3.25,
y: 2.5, y: 2.5,
@@ -230,18 +234,19 @@ describe('WebsocketMessage Protobuf Encoding/Decoding', () => {
}) })
it('should encode and decode two empty types correctly', () => { it('should encode and decode two empty types correctly', () => {
const encoded_ping = WebsocketMessage.encode(
const encoded_ping = WebsocketMessage.encode(WebsocketMessage.create({ pingmsg: PingMsg.create() })).finish() WebsocketMessage.create({ pingmsg: PingMsg.create() })
).finish()
const decoded_ping = decodeMessage(encoded_ping.buffer) const decoded_ping = decodeMessage(encoded_ping.buffer)
expect(decoded_ping.tag).toBe(MESSAGE_KEY_TO_TAG.get("pingmsg")) expect(decoded_ping.tag).toBe(MESSAGE_KEY_TO_TAG.get('pingmsg'))
const encoded_pong = WebsocketMessage.encode(WebsocketMessage.create({ pongmsg: PongMsg.create() })).finish() const encoded_pong = WebsocketMessage.encode(
WebsocketMessage.create({ pongmsg: PongMsg.create() })
).finish()
const decoded_pong = decodeMessage(encoded_pong.buffer) const decoded_pong = decodeMessage(encoded_pong.buffer)
expect(decoded_pong.tag).toBe(MESSAGE_KEY_TO_TAG.get("pongmsg")) expect(decoded_pong.tag).toBe(MESSAGE_KEY_TO_TAG.get('pongmsg'))
}) })
it('should encode and decode complete WebsocketMessage', () => { it('should encode and decode complete WebsocketMessage', () => {
const original = WebsocketMessage.create({ const original = WebsocketMessage.create({
imu: IMUData.create({ imu: IMUData.create({
@@ -267,5 +272,4 @@ describe('WebsocketMessage Protobuf Encoding/Decoding', () => {
expect(decoded.imu?.bmpTemp).toBe(22) expect(decoded.imu?.bmpTemp).toBe(22)
expect(decoded.imu?.pressure).toBe(23) expect(decoded.imu?.pressure).toBe(23)
}) })
}) })