🎨 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
+32 -18
View File
@@ -19,14 +19,21 @@
jointNames,
currentKinematic,
walkGait,
kinematicData,
kinematicData
} from '$lib/stores'
import { populateModelCache, getToeWorldPositions } from '$lib/utilities'
import SceneBuilder from '$lib/sceneBuilder'
import { lerp, degToRad } from 'three/src/math/MathUtils'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
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 type { URDFRobot } from 'urdf-loader'
import { get } from 'svelte/store'
@@ -50,10 +57,12 @@
let sceneManager = $state(new SceneBuilder())
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({ angles: new Array(12).fill(0) })
let modelTargetAngles: AnglesData = AnglesData.create({ angles: new Array(12).fill(0) })
let currentModelAngles: AnglesData = AnglesData.create({
angles: new Array(NUM_ANGLES).fill(0)
})
let modelTargetAngles: AnglesData = AnglesData.create({ angles: new Array(NUM_ANGLES).fill(0) })
let gui_panel: GUI
const SMOOTH_AMOUNT = 0.2
@@ -63,8 +72,7 @@
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, IdleState | CalibrationState | RestState | StandState | BezierState> = {
const planners: Record<ModesEnum, GaitState> = {
[ModesEnum.DEACTIVATED]: new IdleState(),
[ModesEnum.IDLE]: new IdleState(),
[ModesEnum.CALIBRATION]: new CalibrationState(),
@@ -119,7 +127,9 @@
walkGait.subscribe(gait => {
const walkPlanner = planners[ModesEnum.WALK]
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)
})
@@ -163,14 +173,16 @@
}
const updateKinematicPosition = () => {
kinematicData.set(KinematicData.create({
omega: settings.omega,
phi: settings.phi,
psi: settings.psi,
xm: settings.xm,
ym: settings.ym,
zm: settings.zm
}))
kinematicData.set(
KinematicData.create({
omega: settings.omega,
phi: settings.phi,
psi: settings.psi,
xm: settings.xm,
ym: settings.ym,
zm: settings.zm
})
)
}
const setSceneBackground = (c: string | null) => (sceneManager.scene.background = new Color(c!))
@@ -178,7 +190,9 @@
const updateAngles = (name: string, angle: number) => {
modelTargetAngles.angles[$jointNames.indexOf(name)] = angle * (180 / Math.PI)
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 = () => {
if (sceneManager.isDragging || !settings['Internal kinematic']) return
const controlData = get(outControllerData)
const controlData = get(input)
let planner = planners[get(mode).mode]
const delta = performance.now() - lastTick
@@ -1,6 +1,6 @@
<script lang="ts">
import { ModeData, ModesEnum } from '$lib/platform_shared/websocket_message'
import { mode, modes } from '$lib/stores'
import { mode } from '$lib/stores'
const deactivate = async () => {
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]) {
console.log('BezierState set_mode', mode)
this.mode = mode
if (mode === WalkGaits.CRAWL) {
this.speed_factor = 0.5
+138 -134
View File
@@ -5,163 +5,167 @@
// source: platform_shared/imu_report.proto
/* 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 {
x: number;
y: number;
z: number;
temp: number;
success: boolean;
x: number
y: number
z: number
temp: number
success: boolean
}
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> = {
encode(message: IMUReport, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.x !== 0) {
writer.uint32(13).float(message.x);
}
if (message.y !== 0) {
writer.uint32(21).float(message.y);
}
if (message.z !== 0) {
writer.uint32(29).float(message.z);
}
if (message.temp !== 0) {
writer.uint32(37).float(message.temp);
}
if (message.success !== false) {
writer.uint32(40).bool(message.success);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): IMUReport {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseIMUReport();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 13) {
break;
}
message.x = reader.float();
continue;
encode(message: IMUReport, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.x !== 0) {
writer.uint32(13).float(message.x)
}
case 2: {
if (tag !== 21) {
break;
}
message.y = reader.float();
continue;
if (message.y !== 0) {
writer.uint32(21).float(message.y)
}
case 3: {
if (tag !== 29) {
break;
}
message.z = reader.float();
continue;
if (message.z !== 0) {
writer.uint32(29).float(message.z)
}
case 4: {
if (tag !== 37) {
break;
}
message.temp = reader.float();
continue;
if (message.temp !== 0) {
writer.uint32(37).float(message.temp)
}
case 5: {
if (tag !== 40) {
break;
}
message.success = reader.bool();
continue;
if (message.success !== false) {
writer.uint32(40).bool(message.success)
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
return writer
},
fromJSON(object: any): IMUReport {
return {
x: isSet(object.x) ? globalThis.Number(object.x) : 0,
y: isSet(object.y) ? globalThis.Number(object.y) : 0,
z: isSet(object.z) ? globalThis.Number(object.z) : 0,
temp: isSet(object.temp) ? globalThis.Number(object.temp) : 0,
success: isSet(object.success) ? globalThis.Boolean(object.success) : false,
};
},
decode(input: BinaryReader | Uint8Array, length?: number): IMUReport {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
const end = length === undefined ? reader.len : reader.pos + length
const message = createBaseIMUReport()
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {
case 1: {
if (tag !== 13) {
break
}
toJSON(message: IMUReport): unknown {
const obj: any = {};
if (message.x !== 0) {
obj.x = message.x;
}
if (message.y !== 0) {
obj.y = message.y;
}
if (message.z !== 0) {
obj.z = message.z;
}
if (message.temp !== 0) {
obj.temp = message.temp;
}
if (message.success !== false) {
obj.success = message.success;
}
return obj;
},
message.x = reader.float()
continue
}
case 2: {
if (tag !== 21) {
break
}
create<I extends Exact<DeepPartial<IMUReport>, I>>(base?: I): IMUReport {
return IMUReport.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<IMUReport>, I>>(object: I): IMUReport {
const message = createBaseIMUReport();
message.x = object.x ?? 0;
message.y = object.y ?? 0;
message.z = object.z ?? 0;
message.temp = object.temp ?? 0;
message.success = object.success ?? false;
return message;
},
};
message.y = reader.float()
continue
}
case 3: {
if (tag !== 29) {
break
}
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
message.z = reader.float()
continue
}
case 4: {
if (tag !== 37) {
break
}
export type DeepPartial<T> = T extends Builtin ? T
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
message.temp = reader.float()
continue
}
case 5: {
if (tag !== 40) {
break
}
type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
message.success = reader.bool()
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
}
reader.skip(tag & 7)
}
return message
},
fromJSON(object: any): IMUReport {
return {
x: isSet(object.x) ? globalThis.Number(object.x) : 0,
y: isSet(object.y) ? globalThis.Number(object.y) : 0,
z: isSet(object.z) ? globalThis.Number(object.z) : 0,
temp: isSet(object.temp) ? globalThis.Number(object.temp) : 0,
success: isSet(object.success) ? globalThis.Boolean(object.success) : false
}
},
toJSON(message: IMUReport): unknown {
const obj: any = {}
if (message.x !== 0) {
obj.x = message.x
}
if (message.y !== 0) {
obj.y = message.y
}
if (message.z !== 0) {
obj.z = message.z
}
if (message.temp !== 0) {
obj.temp = message.temp
}
if (message.success !== false) {
obj.success = message.success
}
return obj
},
create<I extends Exact<DeepPartial<IMUReport>, I>>(base?: I): IMUReport {
return IMUReport.fromPartial(base ?? ({} as any))
},
fromPartial<I extends Exact<DeepPartial<IMUReport>, I>>(object: I): IMUReport {
const message = createBaseIMUReport()
message.x = object.x ?? 0
message.y = object.y ?? 0
message.z = object.z ?? 0
message.temp = object.temp ?? 0
message.success = object.success ?? false
return message
}
}
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined
export type DeepPartial<T> =
T extends Builtin ? T
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>
type KeysOfUnion<T> = T extends T ? keyof T : never
export type Exact<P, I extends P> =
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 {
return value !== null && value !== undefined;
return value !== null && value !== undefined
}
export interface MessageFns<T> {
encode(message: T, writer?: BinaryWriter): BinaryWriter;
decode(input: BinaryReader | Uint8Array, length?: number): T;
fromJSON(object: any): T;
toJSON(message: T): unknown;
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T;
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T;
encode(message: T, writer?: BinaryWriter): BinaryWriter
decode(input: BinaryReader | Uint8Array, length?: number): T
fromJSON(object: any): T
toJSON(message: T): unknown
create<I extends Exact<DeepPartial<T>, I>>(base?: 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 { writable } from 'svelte/store'
const analytics_data: AnalyticsData[] = [];
const analytics_data: AnalyticsData[] = []
const maxAnalyticsData = 100
+1 -2
View File
@@ -1,10 +1,9 @@
import { writable } from 'svelte/store'
import { IMUData } from '$lib/platform_shared/websocket_message'
const imu_data: IMUData[] = [];
const imu_data: IMUData[] = []
const maxIMUData = 100
export const imu = (() => {
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 { 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 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 walkGait: Writable<WalkGaitData> = writable(
WalkGaitData.create({ gait: WalkGaits.TROT })
)
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
// Auto-generate modes array from ModesEnum (excluding UNRECOGNIZED)
export const modes = Object.values(ModesEnum)
.filter((v): v is ModesEnum => typeof v === 'number' && v !== ModesEnum.UNRECOGNIZED)
return {
values: entries.map(([, v]) => v),
labels: Object.fromEntries(
entries.map(([k, v]) => [v, k.charAt(0) + k.slice(1).toLowerCase()])
) as Record<T, string>
}
}
// Auto-generate mode labels from enum keys
export const modeLabels: Record<ModesEnum, string> = Object.entries(ModesEnum)
.filter(([_, v]) => typeof v === 'number' && v !== ModesEnum.UNRECOGNIZED)
.reduce((acc, [key, value]) => {
acc[value as ModesEnum] = key.charAt(0) + key.slice(1).toLowerCase()
return acc
}, {} as Record<ModesEnum, string>)
const modesData = enumToValuesAndLabels<ModesEnum>(ModesEnum)
export const modes = modesData.values
export const modeLabels = modesData.labels
// Auto-generate walk gaits array from WalkGaits enum (excluding UNRECOGNIZED)
export const walkGaits = Object.values(WalkGaits)
.filter((v): v is WalkGaits => typeof v === 'number' && v !== WalkGaits.UNRECOGNIZED)
// 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>)
const walkGaitsData = enumToValuesAndLabels<WalkGaits>(WalkGaits)
export const walkGaits = walkGaitsData.values
export const walkGaitLabels = walkGaitsData.labels
+6 -2
View File
@@ -1,8 +1,12 @@
import { AnglesData } from '$lib/platform_shared/websocket_message'
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 servoAngles: Writable<AnglesData> = writable(AnglesData.create({angles: [0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90]}))
export const servoAnglesOut: Writable<AnglesData> = writable(
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 mpu = writable({ heading: 0 })
+44 -43
View File
@@ -1,10 +1,13 @@
import { writable } from 'svelte/store'
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 type { BinaryWriter } from '@bufbuild/protobuf/wire'
// -------- START PARSING PROTO DATA --------
// Auto-build reverse mapping from MessageFns to event key and tag
export const MESSAGE_TYPE_TO_KEY = new Map<MessageFns<any>, string>()
@@ -13,7 +16,7 @@ export const MESSAGE_KEY_TO_TAG = new Map<string, number>()
// Build the mapping using references from metadata
const websocketMessageType = websocket_md.fileDescriptor.messageType?.find(
( msg: { name: string } ) => msg.name === 'WebsocketMessage'
(msg: { name: string }) => msg.name === 'WebsocketMessage'
)
if (websocketMessageType?.field) {
@@ -33,7 +36,9 @@ if (websocketMessageType?.field) {
function get_name_from_messagetype(event_type: MessageFns<any>): string {
const event = MESSAGE_TYPE_TO_KEY.get(event_type)
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
}
@@ -42,7 +47,9 @@ function get_name_from_messagetype(event_type: MessageFns<any>): string {
function get_tag_from_messagetype(event_type: MessageFns<any>): number {
const fieldNumber = MESSAGE_TYPE_TO_TAG.get(event_type)
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
}
@@ -52,29 +59,26 @@ function get_tag_from_messagetype(event_type: MessageFns<any>): number {
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const
type SocketEvent = (typeof socketEvents)[number]
type TaggedSocketMessage = {"tag": number, "msg": WebsocketMessage}
type TaggedSocketMessage = { tag: number; msg: WebsocketMessage }
// Only exported for socket test
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
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 tag = MESSAGE_KEY_TO_TAG.get(fieldName)
if (tag === undefined) {
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> => {
const encoded = WebsocketMessage.encode(data).finish();
return encoded;
const encoded = WebsocketMessage.encode(data).finish()
return encoded
}
function createWebSocket() {
@@ -92,22 +96,21 @@ function createWebSocket() {
connect()
}
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_listeners = message_listeners.get(type_tag);
const type_listeners = message_listeners.get(type_tag)
if (type_listeners == undefined) {
return new Set()
}
return type_listeners;
return type_listeners
}
function getListeners<MT>(event: string): Set<(data?: unknown) => void> {
const event_listeners_forevent = event_listeners.get(event);
function getListeners<MT>(event: string): Set<(data?: unknown) => void> {
const event_listeners_forevent = event_listeners.get(event)
if (event_listeners_forevent == undefined) {
return new Set()
}
return event_listeners_forevent;
return event_listeners_forevent
}
function disconnect(reason: SocketEvent, event?: Event) {
@@ -135,7 +138,7 @@ function createWebSocket() {
}
ws.onmessage = frame => {
resetUnresponsiveCheck()
const {tag, msg} = decodeMessage(frame.data)
const { tag, msg } = decodeMessage(frame.data)
if (tag) message_listeners.get(tag)?.forEach(listener => listener(msg))
}
ws.onerror = ev => disconnect('error', ev)
@@ -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
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)
}
}
function unsubscribe_event(event_type: SocketEvent, listener: (data: unknown) => void) {
const message_listeners_totag = event_listeners.get(event_type)
if (!message_listeners_totag) return
@@ -171,38 +173,38 @@ function createWebSocket() {
// T must extend a type of WebsocketMessages
function sendEvent<T>(event: MessageFns<T>, data: T) {
if (!ws || ws.readyState !== WebSocket.OPEN) return
const type = get_name_from_messagetype(event);
const wsm = WebsocketMessage.create();
(wsm as any)[type] = data
const type = get_name_from_messagetype(event)
const wsm = WebsocketMessage.create()
;(wsm as any)[type] = data
send(wsm)
}
function unsubscribeToMessageFromServer<T>(event_type: MessageFns<T>) {
if (!ws || ws.readyState !== WebSocket.OPEN) return
const event = get_name_from_messagetype(event_type);
const unsub_msg = WebsocketMessages.UnsubscribeNotification.create(
{tag: get_tag_from_messagetype(event_type)}
);
send(WebsocketMessage.create({unsubNotif: unsub_msg}));
const event = get_name_from_messagetype(event_type)
const unsub_msg = WebsocketMessages.UnsubscribeNotification.create({
tag: get_tag_from_messagetype(event_type)
})
send(WebsocketMessage.create({ unsubNotif: unsub_msg }))
}
function subscribeToEvent<T>(event_type: MessageFns<T>) {
if (!ws || ws.readyState !== WebSocket.OPEN) return
const event = get_name_from_messagetype(event_type);
const sub_msg = WebsocketMessages.SubscribeNotification.create(
{tag: get_tag_from_messagetype(event_type)}
);
send(WebsocketMessage.create({subNotif: sub_msg}));
const event = get_name_from_messagetype(event_type)
const sub_msg = WebsocketMessages.SubscribeNotification.create({
tag: get_tag_from_messagetype(event_type)
})
send(WebsocketMessage.create({ subNotif: sub_msg }))
}
function send(data: WebsocketMessage) {
if (!ws || ws.readyState !== WebSocket.OPEN) return
const encoded = encodeMessage(data);
ws.send(encoded);
const encoded = encodeMessage(data)
ws.send(encoded)
}
function ping() {
send(WebsocketMessage.create({pingmsg: {}}))
send(WebsocketMessage.create({ pingmsg: {} }))
}
return {
@@ -210,7 +212,7 @@ function createWebSocket() {
sendEvent,
init,
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)
if (!message_listeners_totag) {
@@ -225,7 +227,6 @@ function createWebSocket() {
}
},
onEvent: (event_type: SocketEvent, listener: (data: unknown) => void): (() => void) => {
return () => {
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'
type telemetry_data_type = {
rssi: RSSIData;
download_ota: DownloadOTAData;
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()
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)
@@ -13,10 +16,16 @@ function createTelemetry() {
return {
subscribe,
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) => {
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 GithubRelease = {
message: string
tag_name: string
@@ -12,13 +9,10 @@ export type GithubRelease = {
}>
}
export type NetworkList = {
networks: NetworkItem[]
}
export type NetworkItem = {
rssi: number
ssid: string
@@ -46,14 +40,11 @@ export type ApSettings = {
subnet_mask: string
}
export type Rssi = {
rssi: number
ssid: string
}
export type CameraSettings = {
framesize: number
quality: number
+40 -17
View File
@@ -21,7 +21,17 @@
useFeatureFlags,
walkGait
} 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'
interface Props {
@@ -39,10 +49,14 @@
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))
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))
})
@@ -50,26 +64,35 @@
removeEventListeners()
})
const eventListeners: (() => void)[] = [];
const eventListeners: (() => void)[] = []
const addEventListeners = () => {
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)),
socket.on(AnalyticsData, (data) => {analytics.addData(data)}),
socket.on(AnglesData, (data) => {servoAngles.set(data)})
])
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)),
socket.on(AnalyticsData, data => {
analytics.addData(data)
}),
socket.on(AnglesData, data => {
servoAngles.set(data)
})
]
)
features.subscribe(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)) )
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 = () => {
for (let offFunction of eventListeners) {
offFunction();
offFunction()
}
}
@@ -79,7 +102,7 @@
const handleClose = () => {
notifications.error('Connection to device lost', 5000)
telemetry.setRSSI( RSSIData.create({rssi: 0}) )
telemetry.setRSSI(RSSIData.create({ rssi: 0 }))
}
const handleError = (data: unknown) => console.error(data)
@@ -15,7 +15,6 @@
import { gamepadAxes, gamepadButtonsEdges, hasGamepad } from '$lib/stores/gamepad'
import { notifications } from '$lib/components/toasts/notifications'
import {
HumanInputData,
ModeData,
ModesEnum,
WalkGaitData,
@@ -25,14 +24,6 @@
let left: 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(() => {
if ($hasGamepad) {
notifications.success('🎮 Gamepad connected', 3000)
+6 -2
View File
@@ -4,7 +4,11 @@
import { socket } from '$lib/stores'
import { Connection } from '$lib/components/icons'
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
// const i2cDevices = [
@@ -66,7 +70,7 @@
<div>No I2C devices found</div>
{:else}
{#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}
{/if}
</div>
@@ -4,7 +4,10 @@
import { onMount } from 'svelte'
import { modals } from 'svelte-modals'
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 isEditing = $state(false)
+29 -19
View File
@@ -9,9 +9,11 @@
import { useFeatureFlags } from '$lib/stores/featureFlags'
import { Rotate3d } from '$lib/components/icons'
import { IMUReport } from '$lib/platform_shared/imu_report';
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { IMUCalibrateData, IMUCalibrateExecute, IMUData } from '$lib/platform_shared/websocket_message'
import {
IMUCalibrateData,
IMUCalibrateExecute,
IMUData
} from '$lib/platform_shared/websocket_message'
Chart.register(...registerables)
@@ -189,9 +191,9 @@
const updateData = () => {
if ($features.imu) {
const x = $imu.map(datapoint => datapoint.x)
const y= $imu.map(datapoint => datapoint.y)
const y = $imu.map(datapoint => datapoint.y)
const z = $imu.map(datapoint => datapoint.z)
angleChart.data.labels = Array.from({ length: $imu.length }, (_, i) => i + 1)
angleChart.data.datasets[0].data = x
angleChart.data.datasets[1].data = y
@@ -204,23 +206,31 @@
}
if ($features.bmp) {
updateChartData(tempChart, $imu.map(datapoint => datapoint.bmpTemp))
updateChartData(altitudeChart, $imu.map(datapoint => datapoint.altitude))
updateChartData(
tempChart,
$imu.map(datapoint => datapoint.bmpTemp)
)
updateChartData(
altitudeChart,
$imu.map(datapoint => datapoint.altitude)
)
}
}
const eventListeners: (() => void)[] = [];
const eventListeners: (() => void)[] = []
onMount(() => {
eventListeners.push(...[
socket.on(IMUData, (data) => {
console.log(data)
imu.addData(data)
}),
eventListeners.push(
...[
socket.on(IMUData, data => {
console.log(data)
imu.addData(data)
}),
socket.on(IMUCalibrateData, (data) => {
isCalibrating = false
calibrationResult = data
})
])
socket.on(IMUCalibrateData, data => {
isCalibrating = false
calibrationResult = data
})
]
)
initializeCharts()
intervalId = setInterval(updateData, 200)
@@ -228,7 +238,7 @@
onDestroy(() => {
for (let offFunction of eventListeners) {
offFunction();
offFunction()
}
clearInterval(intervalId)
})
@@ -12,16 +12,16 @@
const throttler = new Throttler()
const activateServo = () => {
socket.sendEvent(ServoStateData, ServoStateData.create({active: true}))
socket.sendEvent(ServoStateData, ServoStateData.create({ active: true }))
}
const deactivateServo = () => {
socket.sendEvent(ServoStateData, ServoStateData.create({active: false}))
socket.sendEvent(ServoStateData, ServoStateData.create({ active: false }))
}
const updatePWM = () => {
throttler.throttle(() => {
socket.sendEvent(ServoPWMData, ServoPWMData.create({ servoId: servoId, servoPwm: pwm }))
socket.sendEvent(ServoPWMData, ServoPWMData.create({ servoId: servoId, servoPwm: pwm }))
}, 10)
}
+15 -21
View File
@@ -1,31 +1,29 @@
<script lang="ts">
import { onMount } from 'svelte'
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'
// const imu_report: IMUReport = {type: IMUType.IMU_ACCEL, xVal: 4}
// const writer = IMUReport.encode(imu_report);
// const imu_report: IMUReport = {type: IMUType.IMU_ACCEL, xVal: 4}
// const writer = IMUReport.encode(imu_report);
// const bytes = writer.finish();
// // Convert bytes to hex
// const hex = Array.from(bytes)
// .map((b) => b.toString(16).padStart(2, '0'))
// .join(' ');
// // Convert bytes to hex
// const hex = Array.from(bytes)
// .map((b) => b.toString(16).padStart(2, '0'))
// .join(' ');
// const wmd: WebsocketMessage = { imu: {temp: 0, x: 0, y: 0, z: 1}, angles: {angles: [2]}}
// const wmd: WebsocketMessage = { imu: {temp: 0, x: 0, y: 0, z: 0} }
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 = { angles: {angles: [1,2,3,4]} }
const writer = WebsocketMessage.encode(wmd);
const bytes = writer.finish();
// Convert bytes to hex
const hex = Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join(' ');
const writer = WebsocketMessage.encode(wmd)
const bytes = writer.finish()
// Convert bytes to hex
const hex = Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join(' ')
// const decodedmsg: WebsocketMessage = WebsocketMessage.decode(bytes);
// const objects = Object.entries(decodedmsg)
@@ -46,17 +44,13 @@
// }
const handleData = (data: IMUData) => {
console.log(data);
console.log(data)
}
onMount(() => {
return socket.on(IMUData, handleData)
})
</script>
<h1>Hexadecimal Output</h1>
<p><strong>Hex output:</strong> {hex}</p>
@@ -51,9 +51,11 @@
const postSleep = async () => await api.post('api/sleep')
let unsub: (() => void) | undefined = undefined;
onMount(() => unsub = socket.on(AnalyticsData, handleSystemData))
onDestroy(() => { if (unsub) unsub() })
let unsub: (() => void) | undefined = undefined
onMount(() => (unsub = socket.on(AnalyticsData, handleSystemData)))
onDestroy(() => {
if (unsub) unsub()
})
const handleSystemData = (data: AnalyticsData) => {
if (systemInformation) {
@@ -179,7 +181,9 @@
icon={Speed}
title="CPU Frequency"
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}
title="Sketch (Used / Free)"
description={`${(
(systemInformation.staticSystemInformation!.sketchSize / systemInformation.staticSystemInformation!.freeSketchSpace) *
(systemInformation.staticSystemInformation!.sketchSize /
systemInformation.staticSystemInformation!.freeSketchSpace) *
100
).toFixed(1)} % of
${systemInformation.staticSystemInformation!.freeSketchSpace / 1000000} MB used (${
(systemInformation.staticSystemInformation!.freeSketchSpace - systemInformation.staticSystemInformation!.sketchSize) / 1000000
(systemInformation.staticSystemInformation!.freeSketchSpace -
systemInformation.staticSystemInformation!.sketchSize) /
1000000
} MB free)`}
/>
@@ -219,10 +226,15 @@
icon={Folder}
title="File System (Used / Total)"
description={`${(
(systemInformation.analyticsData!.fsUsed / systemInformation.analyticsData!.fsTotal) *
(systemInformation.analyticsData!.fsUsed /
systemInformation.analyticsData!.fsTotal) *
100
).toFixed(1)} % of ${systemInformation.analyticsData!.fsTotal / 1000000} MB used (${
(systemInformation.analyticsData!.fsTotal - systemInformation.analyticsData!.fsUsed) / 1000000
).toFixed(
1
)} % of ${systemInformation.analyticsData!.fsTotal / 1000000} MB used (${
(systemInformation.analyticsData!.fsTotal -
systemInformation.analyticsData!.fsUsed) /
1000000
}
MB free)`}
/>
+6 -4
View File
@@ -36,7 +36,7 @@
import { KnownNetworkItem } from '$lib/platform_shared/websocket_message'
import { WifiSettings, type WifiStatus } from '$lib/platform_shared/rest_message'
let networkEditable: KnownNetworkItem = $state( KnownNetworkItem.create() )
let networkEditable: KnownNetworkItem = $state(KnownNetworkItem.create())
let static_ip_config = $state(false)
@@ -84,15 +84,17 @@
return wifiSettings
}
let unsub_obj: (() => void) | undefined = undefined;
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() } )
onDestroy(() => {
if (unsub_obj) unsub_obj()
})
async function postWiFiSettings(data: WifiSettings) {
const result = await api.post<WifiSettings>('/api/wifi/sta/settings', data)
if (result.isErr()) {