🎨 Format and simplify controls
This commit is contained in:
Vendored
+6
-6
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
omega: settings.omega,
|
KinematicData.create({
|
||||||
phi: settings.phi,
|
omega: settings.omega,
|
||||||
psi: settings.psi,
|
phi: settings.phi,
|
||||||
xm: settings.xm,
|
psi: settings.psi,
|
||||||
ym: settings.ym,
|
xm: settings.xm,
|
||||||
zm: settings.zm
|
ym: settings.ym,
|
||||||
}))
|
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 }))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -5,163 +5,167 @@
|
|||||||
// 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) {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
case 2: {
|
if (message.y !== 0) {
|
||||||
if (tag !== 21) {
|
writer.uint32(21).float(message.y)
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.y = reader.float();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
case 3: {
|
if (message.z !== 0) {
|
||||||
if (tag !== 29) {
|
writer.uint32(29).float(message.z)
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.z = reader.float();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
case 4: {
|
if (message.temp !== 0) {
|
||||||
if (tag !== 37) {
|
writer.uint32(37).float(message.temp)
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.temp = reader.float();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
case 5: {
|
if (message.success !== false) {
|
||||||
if (tag !== 40) {
|
writer.uint32(40).bool(message.success)
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success = reader.bool();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
return writer
|
||||||
if ((tag & 7) === 4 || tag === 0) {
|
},
|
||||||
break;
|
|
||||||
}
|
|
||||||
reader.skip(tag & 7);
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
|
|
||||||
fromJSON(object: any): IMUReport {
|
decode(input: BinaryReader | Uint8Array, length?: number): IMUReport {
|
||||||
return {
|
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
|
||||||
x: isSet(object.x) ? globalThis.Number(object.x) : 0,
|
const end = length === undefined ? reader.len : reader.pos + length
|
||||||
y: isSet(object.y) ? globalThis.Number(object.y) : 0,
|
const message = createBaseIMUReport()
|
||||||
z: isSet(object.z) ? globalThis.Number(object.z) : 0,
|
while (reader.pos < end) {
|
||||||
temp: isSet(object.temp) ? globalThis.Number(object.temp) : 0,
|
const tag = reader.uint32()
|
||||||
success: isSet(object.success) ? globalThis.Boolean(object.success) : false,
|
switch (tag >>> 3) {
|
||||||
};
|
case 1: {
|
||||||
},
|
if (tag !== 13) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
toJSON(message: IMUReport): unknown {
|
message.x = reader.float()
|
||||||
const obj: any = {};
|
continue
|
||||||
if (message.x !== 0) {
|
}
|
||||||
obj.x = message.x;
|
case 2: {
|
||||||
}
|
if (tag !== 21) {
|
||||||
if (message.y !== 0) {
|
break
|
||||||
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 {
|
message.y = reader.float()
|
||||||
return IMUReport.fromPartial(base ?? ({} as any));
|
continue
|
||||||
},
|
}
|
||||||
fromPartial<I extends Exact<DeepPartial<IMUReport>, I>>(object: I): IMUReport {
|
case 3: {
|
||||||
const message = createBaseIMUReport();
|
if (tag !== 29) {
|
||||||
message.x = object.x ?? 0;
|
break
|
||||||
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;
|
message.z = reader.float()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
if (tag !== 37) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
export type DeepPartial<T> = T extends Builtin ? T
|
message.temp = reader.float()
|
||||||
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
|
continue
|
||||||
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
|
}
|
||||||
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
|
case 5: {
|
||||||
: Partial<T>;
|
if (tag !== 40) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
message.success = reader.bool()
|
||||||
export type Exact<P, I extends P> = P extends Builtin ? P
|
continue
|
||||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
|
}
|
||||||
|
}
|
||||||
|
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 {
|
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,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,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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>)
|
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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>()
|
||||||
@@ -13,7 +16,7 @@ export const MESSAGE_KEY_TO_TAG = new Map<string, number>()
|
|||||||
|
|
||||||
// Build the mapping using references from metadata
|
// Build the mapping using references from metadata
|
||||||
const websocketMessageType = websocket_md.fileDescriptor.messageType?.find(
|
const websocketMessageType = websocket_md.fileDescriptor.messageType?.find(
|
||||||
( msg: { name: string } ) => msg.name === 'WebsocketMessage'
|
(msg: { name: string }) => msg.name === 'WebsocketMessage'
|
||||||
)
|
)
|
||||||
|
|
||||||
if (websocketMessageType?.field) {
|
if (websocketMessageType?.field) {
|
||||||
@@ -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() {
|
||||||
@@ -92,22 +96,21 @@ function createWebSocket() {
|
|||||||
connect()
|
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_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) {
|
||||||
@@ -135,7 +138,7 @@ function createWebSocket() {
|
|||||||
}
|
}
|
||||||
ws.onmessage = frame => {
|
ws.onmessage = frame => {
|
||||||
resetUnresponsiveCheck()
|
resetUnresponsiveCheck()
|
||||||
const {tag, msg} = decodeMessage(frame.data)
|
const { tag, msg } = decodeMessage(frame.data)
|
||||||
if (tag) message_listeners.get(tag)?.forEach(listener => listener(msg))
|
if (tag) message_listeners.get(tag)?.forEach(listener => listener(msg))
|
||||||
}
|
}
|
||||||
ws.onerror = ev => disconnect('error', ev)
|
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
|
// 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,38 +173,38 @@ 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() {
|
||||||
send(WebsocketMessage.create({pingmsg: {}}))
|
send(WebsocketMessage.create({ pingmsg: {} }))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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('close', handleClose),
|
socket.onEvent('open', handleOpen),
|
||||||
socket.onEvent('error', handleError),
|
socket.onEvent('close', handleClose),
|
||||||
socket.on(RSSIData, (data) => telemetry.setRSSI(data)),
|
socket.onEvent('error', handleError),
|
||||||
socket.on(ModeData, (data) => mode.set(data)),
|
socket.on(RSSIData, data => telemetry.setRSSI(data)),
|
||||||
socket.on(AnalyticsData, (data) => {analytics.addData(data)}),
|
socket.on(ModeData, data => mode.set(data)),
|
||||||
socket.on(AnglesData, (data) => {servoAngles.set(data)})
|
socket.on(AnalyticsData, 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +102,7 @@
|
|||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
notifications.error('Connection to device lost', 5000)
|
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)
|
const handleError = (data: unknown) => console.error(data)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
@@ -189,9 +191,9 @@
|
|||||||
const updateData = () => {
|
const updateData = () => {
|
||||||
if ($features.imu) {
|
if ($features.imu) {
|
||||||
const x = $imu.map(datapoint => datapoint.x)
|
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)
|
const z = $imu.map(datapoint => datapoint.z)
|
||||||
|
|
||||||
angleChart.data.labels = Array.from({ length: $imu.length }, (_, i) => i + 1)
|
angleChart.data.labels = Array.from({ length: $imu.length }, (_, i) => i + 1)
|
||||||
angleChart.data.datasets[0].data = x
|
angleChart.data.datasets[0].data = x
|
||||||
angleChart.data.datasets[1].data = y
|
angleChart.data.datasets[1].data = y
|
||||||
@@ -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) => {
|
...[
|
||||||
console.log(data)
|
socket.on(IMUData, data => {
|
||||||
imu.addData(data)
|
console.log(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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,16 +12,16 @@
|
|||||||
const throttler = new Throttler()
|
const throttler = new Throttler()
|
||||||
|
|
||||||
const activateServo = () => {
|
const activateServo = () => {
|
||||||
socket.sendEvent(ServoStateData, ServoStateData.create({active: true}))
|
socket.sendEvent(ServoStateData, ServoStateData.create({ active: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const deactivateServo = () => {
|
const deactivateServo = () => {
|
||||||
socket.sendEvent(ServoStateData, ServoStateData.create({active: false}))
|
socket.sendEvent(ServoStateData, ServoStateData.create({ active: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePWM = () => {
|
const updatePWM = () => {
|
||||||
throttler.throttle(() => {
|
throttler.throttle(() => {
|
||||||
socket.sendEvent(ServoPWMData, ServoPWMData.create({ servoId: servoId, servoPwm: pwm }))
|
socket.sendEvent(ServoPWMData, ServoPWMData.create({ servoId: servoId, servoPwm: pwm }))
|
||||||
}, 10)
|
}, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
|
|
||||||
<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'
|
||||||
|
|
||||||
// const imu_report: IMUReport = {type: IMUType.IMU_ACCEL, xVal: 4}
|
// const imu_report: IMUReport = {type: IMUType.IMU_ACCEL, xVal: 4}
|
||||||
// const writer = IMUReport.encode(imu_report);
|
// const writer = IMUReport.encode(imu_report);
|
||||||
// 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 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: 1}, angles: {angles: [2]}}
|
||||||
// const wmd: WebsocketMessage = { imu: {temp: 0, x: 0, y: 0, z: 0} }
|
// 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 = { 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)`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
import { KnownNetworkItem } from '$lib/platform_shared/websocket_message'
|
import { KnownNetworkItem } from '$lib/platform_shared/websocket_message'
|
||||||
import { WifiSettings, type WifiStatus } from '$lib/platform_shared/rest_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)
|
let static_ip_config = $state(false)
|
||||||
|
|
||||||
@@ -84,15 +84,17 @@
|
|||||||
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
|
||||||
dndNetworkList = wifiSettings.wifiNetworks
|
dndNetworkList = wifiSettings.wifiNetworks
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
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()) {
|
||||||
|
|||||||
+185
-181
@@ -1,212 +1,216 @@
|
|||||||
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 {
|
||||||
const message: any = {}
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
message[messageType] = data
|
const message: any = {}
|
||||||
const wsMessage = WebsocketMessage.create(message)
|
message[messageType] = data
|
||||||
return WebsocketMessage.encode(wsMessage).finish()
|
const wsMessage = WebsocketMessage.create(message)
|
||||||
|
return WebsocketMessage.encode(wsMessage).finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
describe.sequential('WebSocket Integration Tests', () => {
|
describe.sequential('WebSocket Integration Tests', () => {
|
||||||
let wss: WebSocketServer
|
let wss: WebSocketServer
|
||||||
let TEST_PORT = 8765
|
let TEST_PORT = 8765
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Use a different port for each test to avoid conflicts
|
// Use a different port for each test to avoid conflicts
|
||||||
TEST_PORT++
|
TEST_PORT++
|
||||||
|
|
||||||
// Create real WebSocket server
|
// Create real WebSocket server
|
||||||
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
|
||||||
await new Promise(resolve => setTimeout(resolve, 100))
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should connect to WebSocket server', async () => {
|
it('should connect to WebSocket server', async () => {
|
||||||
socket.init(`ws://localhost:${TEST_PORT}`)
|
socket.init(`ws://localhost:${TEST_PORT}`)
|
||||||
|
|
||||||
// Wait for connection
|
// Wait for connection
|
||||||
await new Promise(resolve => setTimeout(resolve, 100))
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
|
||||||
let isConnected = false
|
let isConnected = false
|
||||||
socket.subscribe(value => {
|
socket.subscribe(value => {
|
||||||
isConnected = value
|
isConnected = value
|
||||||
})()
|
})()
|
||||||
|
|
||||||
expect(isConnected).toBe(true)
|
expect(isConnected).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should receive and decode IMU data from server', async () => {
|
it('should receive and decode IMU data from server', async () => {
|
||||||
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
|
||||||
})
|
})
|
||||||
|
|
||||||
// Connect socket
|
// Connect socket
|
||||||
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,
|
||||||
y: 2.5,
|
y: 2.5,
|
||||||
z: 1.75,
|
z: 1.75,
|
||||||
heading: 10,
|
heading: 10,
|
||||||
altitude: 11,
|
altitude: 11,
|
||||||
bmpTemp: 22,
|
bmpTemp: 22,
|
||||||
pressure: 23
|
pressure: 23
|
||||||
})
|
})
|
||||||
|
|
||||||
const encodedMessage = createEncodedMessage('imu', imuPayload)
|
const encodedMessage = createEncodedMessage('imu', imuPayload)
|
||||||
ws.send(encodedMessage)
|
ws.send(encodedMessage)
|
||||||
|
|
||||||
setTimeout(resolve, 50)
|
setTimeout(resolve, 50)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(receivedIMUData).toBeDefined()
|
expect(receivedIMUData).toBeDefined()
|
||||||
expect(receivedIMUData?.imu).toBeDefined()
|
expect(receivedIMUData?.imu).toBeDefined()
|
||||||
|
|
||||||
expect(receivedIMUData?.imu.x).toBe(3.25)
|
expect(receivedIMUData?.imu.x).toBe(3.25)
|
||||||
expect(receivedIMUData?.imu.y).toBe(2.5)
|
expect(receivedIMUData?.imu.y).toBe(2.5)
|
||||||
expect(receivedIMUData?.imu.z).toBe(1.75)
|
expect(receivedIMUData?.imu.z).toBe(1.75)
|
||||||
expect(receivedIMUData?.imu.heading).toBe(10)
|
expect(receivedIMUData?.imu.heading).toBe(10)
|
||||||
expect(receivedIMUData?.imu.altitude).toBe(11)
|
expect(receivedIMUData?.imu.altitude).toBe(11)
|
||||||
expect(receivedIMUData?.imu.bmpTemp).toBe(22)
|
expect(receivedIMUData?.imu.bmpTemp).toBe(22)
|
||||||
expect(receivedIMUData?.imu.pressure).toBe(23)
|
expect(receivedIMUData?.imu.pressure).toBe(23)
|
||||||
|
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should send IMU data from client to server using sendEvent', async () => {
|
it('should send IMU data from client to server using sendEvent', async () => {
|
||||||
let serverReceivedData: any = null
|
let serverReceivedData: any = null
|
||||||
|
|
||||||
// Connect socket
|
// Connect socket
|
||||||
socket.init(`ws://localhost:${TEST_PORT}`)
|
socket.init(`ws://localhost:${TEST_PORT}`)
|
||||||
|
|
||||||
// Wait for client to connect and send data
|
// Wait for client to connect and send data
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
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
|
||||||
ws.on('message', (data: Buffer) => {
|
ws.on('message', (data: Buffer) => {
|
||||||
// console.log('Server: Received message, length:', data.length)
|
// console.log('Server: Received message, length:', data.length)
|
||||||
|
|
||||||
// Skip empty messages (from ping, etc.)
|
// Skip empty messages (from ping, etc.)
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
console.log('Server: Skipping empty message (Probably a ping')
|
console.log('Server: Skipping empty message (Probably a ping')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Decode the protobuf message
|
// Decode the protobuf message
|
||||||
const decoded = WebsocketMessage.decode(new Uint8Array(data))
|
const decoded = WebsocketMessage.decode(new Uint8Array(data))
|
||||||
// console.log('Server: Decoded message:', JSON.stringify(decoded, null, 2))
|
// console.log('Server: Decoded message:', JSON.stringify(decoded, null, 2))
|
||||||
|
|
||||||
// Only resolve if we got actual IMU data
|
// Only resolve if we got actual IMU data
|
||||||
if (decoded.imu) {
|
if (decoded.imu) {
|
||||||
serverReceivedData = decoded
|
serverReceivedData = decoded
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
resolve()
|
resolve()
|
||||||
} else {
|
} else {
|
||||||
// console.log('Server: Message decoded but no IMU data, waiting...')
|
// console.log('Server: Message decoded but no IMU data, waiting...')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Server: Failed to decode:', error)
|
console.error('Server: Failed to decode:', error)
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for WebSocket to be fully connected
|
// Wait for WebSocket to be fully connected
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Client: Sending IMU data...')
|
console.log('Client: Sending IMU data...')
|
||||||
// Client sends IMU data to server
|
// Client sends IMU data to server
|
||||||
const imuData = IMUData.create({
|
const imuData = IMUData.create({
|
||||||
x: 3.25,
|
x: 3.25,
|
||||||
y: 2.5,
|
y: 2.5,
|
||||||
z: 1.75,
|
z: 1.75,
|
||||||
heading: 10,
|
heading: 10,
|
||||||
altitude: 11,
|
altitude: 11,
|
||||||
bmpTemp: 22,
|
bmpTemp: 22,
|
||||||
pressure: 23
|
pressure: 23
|
||||||
})
|
})
|
||||||
socket.sendEvent(IMUData, imuData)
|
socket.sendEvent(IMUData, imuData)
|
||||||
console.log('Client: sendEvent called')
|
console.log('Client: sendEvent called')
|
||||||
}, 150)
|
}, 150)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Verify server received the data
|
// Verify server received the data
|
||||||
expect(serverReceivedData).toBeDefined()
|
expect(serverReceivedData).toBeDefined()
|
||||||
expect(serverReceivedData?.imu).toBeDefined()
|
expect(serverReceivedData?.imu).toBeDefined()
|
||||||
|
|
||||||
expect(serverReceivedData?.imu.x).toBe(3.25)
|
expect(serverReceivedData?.imu.x).toBe(3.25)
|
||||||
expect(serverReceivedData?.imu.y).toBe(2.5)
|
expect(serverReceivedData?.imu.y).toBe(2.5)
|
||||||
expect(serverReceivedData?.imu.z).toBe(1.75)
|
expect(serverReceivedData?.imu.z).toBe(1.75)
|
||||||
expect(serverReceivedData?.imu.heading).toBe(10)
|
expect(serverReceivedData?.imu.heading).toBe(10)
|
||||||
expect(serverReceivedData?.imu.altitude).toBe(11)
|
expect(serverReceivedData?.imu.altitude).toBe(11)
|
||||||
expect(serverReceivedData?.imu.bmpTemp).toBe(22)
|
expect(serverReceivedData?.imu.bmpTemp).toBe(22)
|
||||||
expect(serverReceivedData?.imu.pressure).toBe(23)
|
expect(serverReceivedData?.imu.pressure).toBe(23)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should fail to serialize data on sendEvent', async () => {
|
||||||
|
// Connect socket
|
||||||
|
socket.init(`ws://localhost:${TEST_PORT}`)
|
||||||
|
|
||||||
it('should fail to serialize data on sendEvent', async () => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
// Connect socket
|
const timeout = setTimeout(() => {
|
||||||
socket.init(`ws://localhost:${TEST_PORT}`)
|
reject(new Error('Test timeout'))
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
// Wait for WebSocket to be fully connected
|
||||||
const timeout = setTimeout(() => {
|
setTimeout(() => {
|
||||||
reject(new Error('Test timeout'))
|
console.log('Client: Sending invalid message type...')
|
||||||
}, 1000)
|
// Send any invalid message type
|
||||||
|
const wsm = WebsocketMessage.create()
|
||||||
// Wait for WebSocket to be fully connected
|
try {
|
||||||
setTimeout(() => {
|
socket.sendEvent(WebsocketMessage as any, wsm)
|
||||||
console.log('Client: Sending invalid message type...')
|
clearTimeout(timeout)
|
||||||
// Send any invalid message type
|
reject(new Error('Expected sendEvent to throw, but it did not'))
|
||||||
const wsm = WebsocketMessage.create()
|
} catch (e) {
|
||||||
try {
|
console.log('Client: sendEvent correctly threw error:', e)
|
||||||
socket.sendEvent(WebsocketMessage as any, wsm)
|
clearTimeout(timeout)
|
||||||
clearTimeout(timeout)
|
resolve()
|
||||||
reject(new Error('Expected sendEvent to throw, but it did not'))
|
}
|
||||||
} catch (e) {
|
}, 150)
|
||||||
console.log('Client: sendEvent correctly threw error:', e)
|
})
|
||||||
clearTimeout(timeout)
|
})
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}, 150)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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,
|
||||||
@@ -217,34 +221,35 @@ describe('WebsocketMessage Protobuf Encoding/Decoding', () => {
|
|||||||
pressure: 23
|
pressure: 23
|
||||||
})
|
})
|
||||||
|
|
||||||
const encoded = IMUData.encode(imuData).finish()
|
const encoded = IMUData.encode(imuData).finish()
|
||||||
const decoded = IMUData.decode(encoded)
|
const decoded = IMUData.decode(encoded)
|
||||||
|
|
||||||
expect(decoded.x).toBe(3.25)
|
expect(decoded.x).toBe(3.25)
|
||||||
expect(decoded.y).toBe(2.5)
|
expect(decoded.y).toBe(2.5)
|
||||||
expect(decoded.z).toBe(1.75)
|
expect(decoded.z).toBe(1.75)
|
||||||
expect(decoded.heading).toBe(10)
|
expect(decoded.heading).toBe(10)
|
||||||
expect(decoded.altitude).toBe(11)
|
expect(decoded.altitude).toBe(11)
|
||||||
expect(decoded.bmpTemp).toBe(22)
|
expect(decoded.bmpTemp).toBe(22)
|
||||||
expect(decoded.pressure).toBe(23)
|
expect(decoded.pressure).toBe(23)
|
||||||
})
|
})
|
||||||
|
|
||||||
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', () => {
|
||||||
|
const original = WebsocketMessage.create({
|
||||||
it('should encode and decode complete WebsocketMessage', () => {
|
imu: IMUData.create({
|
||||||
const original = WebsocketMessage.create({
|
|
||||||
imu: IMUData.create({
|
|
||||||
x: 3.25,
|
x: 3.25,
|
||||||
y: 2.5,
|
y: 2.5,
|
||||||
z: 1.75,
|
z: 1.75,
|
||||||
@@ -253,19 +258,18 @@ describe('WebsocketMessage Protobuf Encoding/Decoding', () => {
|
|||||||
bmpTemp: 22,
|
bmpTemp: 22,
|
||||||
pressure: 23
|
pressure: 23
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const encoded = WebsocketMessage.encode(original).finish()
|
const encoded = WebsocketMessage.encode(original).finish()
|
||||||
const decoded = WebsocketMessage.decode(encoded)
|
const decoded = WebsocketMessage.decode(encoded)
|
||||||
|
|
||||||
expect(decoded.imu).toBeDefined()
|
expect(decoded.imu).toBeDefined()
|
||||||
expect(decoded.imu?.x).toBe(3.25)
|
expect(decoded.imu?.x).toBe(3.25)
|
||||||
expect(decoded.imu?.y).toBe(2.5)
|
expect(decoded.imu?.y).toBe(2.5)
|
||||||
expect(decoded.imu?.z).toBe(1.75)
|
expect(decoded.imu?.z).toBe(1.75)
|
||||||
expect(decoded.imu?.heading).toBe(10)
|
expect(decoded.imu?.heading).toBe(10)
|
||||||
expect(decoded.imu?.altitude).toBe(11)
|
expect(decoded.imu?.altitude).toBe(11)
|
||||||
expect(decoded.imu?.bmpTemp).toBe(22)
|
expect(decoded.imu?.bmpTemp).toBe(22)
|
||||||
expect(decoded.imu?.pressure).toBe(23)
|
expect(decoded.imu?.pressure).toBe(23)
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
+10
-10
@@ -3,15 +3,15 @@ import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
const config: UserConfigExport = {
|
const config: UserConfigExport = {
|
||||||
plugins: [svelte()],
|
plugins: [svelte()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
$lib: path.resolve(__dirname, './src/lib')
|
$lib: path.resolve(__dirname, './src/lib')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: 'jsdom'
|
environment: 'jsdom'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default defineConfig(config)
|
export default defineConfig(config)
|
||||||
|
|||||||
Reference in New Issue
Block a user