🎨 Format and simplify controls
This commit is contained in:
@@ -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 }))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user