From 8e34efdf154ff4cfeada9894e7aa8c9976a02415 Mon Sep 17 00:00:00 2001 From: Fernando Chorney Date: Sun, 7 Apr 2024 18:33:15 -0500 Subject: [PATCH] do stuff --- sdk/commands/config.ts | 4 +-- sdk/smx.ts | 42 ++++++++++++++++++--------- sdk/state-machines/collate-packets.ts | 21 +++++++++++--- ui/pad-coms.ts | 1 + ui/stage/stage-test.tsx | 1 + 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/sdk/commands/config.ts b/sdk/commands/config.ts index 1a2c566..ee6dffe 100644 --- a/sdk/commands/config.ts +++ b/sdk/commands/config.ts @@ -205,7 +205,7 @@ export class SMXConfig { * TODO: Make this private again later, and maybe make a function called * "write_to_stage" or something? Depends on how we want to handle writing/reading */ - encode(): DataView { - return smx_config_t.encode(this.config, true); + encode(): Array { + return Array.from(new Uint8Array(smx_config_t.encode(this.config, true).buffer)); } } diff --git a/sdk/smx.ts b/sdk/smx.ts index 94b8565..f04295d 100644 --- a/sdk/smx.ts +++ b/sdk/smx.ts @@ -1,5 +1,5 @@ import * as Bacon from "baconjs"; -import { collatePackets, type DataPacket } from "./state-machines/collate-packets"; +import { collatePackets, type AckPacket, type DataPacket } from "./state-machines/collate-packets"; import { API_COMMAND, char2byte } from "./api"; import { SMXConfig, type Decoded } from "./commands/config"; import { SMXDeviceInfo } from "./commands/data_info"; @@ -18,6 +18,8 @@ class SMXEvents { public readonly inputState$; /** read from this to see all reports sent by the stage in response to a command */ public readonly otherReports$: Bacon.EventStream; + /** read from this to see if we've received an ACK */ + public readonly ackReports$: Bacon.EventStream; /** push to this to write commands to the stage */ public readonly output$: Bacon.Bus; /** this is everything pushed to `output$` but properly throttled/timed to the device's ack responses */ @@ -39,9 +41,8 @@ class SMXEvents { .filter((d) => d.byteLength !== 0) .withStateMachine({ currentPacket: new Uint8Array() }, collatePackets); - this.otherReports$ = (report$.filter((e) => e.type === "data") as Bacon.EventStream).map( - (e) => e.payload, - ); + this.otherReports$ = (report$.filter((e) => e.type === "data") as Bacon.EventStream).map((e) => e.payload); + this.ackReports$ = (report$.filter((e) => e.type === "ack") as Bacon.EventStream).map((e) => e.type === "ack"); const finishedCommand$ = report$ .filter((e) => e.type === "host_cmd_finished") @@ -127,21 +128,38 @@ export class SMXStage { /** * TODO: To Implement: - * + * * WRITE_CONFIG_V5 * FACTORY_RESET * SET_LIGHT_STRIP * FORCE_RECALIBRATION - * + * * Stretch Goal: * SET_PANEL_TEST_MODE - * + * * Double Stretch Goal: * GET_CONFIG (old firmware) * WRITE_CONFIG (old_firmware) * SET_SERIAL_NUMBERS */ + writeConfig(): Promise | undefined { + if (!this.info || !this.config) { + return; + } + + const encoded_config = this.config.encode(); + + const command = this.info.firmware_version < 5 ? API_COMMAND.WRITE_CONFIG : API_COMMAND.WRITE_CONFIG_V5; + const cmd = [command, encoded_config.length].concat(encoded_config); + + this.events.output$.push([command, encoded_config.length].concat(encoded_config)); + + return this.events.ackReports$ + .map((e) => void e) + .firstToPromise(); + } + updateDeviceInfo(): Promise { this.events.output$.push([API_COMMAND.GET_DEVICE_INFO]); return this.deviceInfo$.firstToPromise(); @@ -180,8 +198,8 @@ export class SMXStage { // Right now I just want to confirm that decoding and encoding gives us back the same data const encoded_config = this.config.encode(); if (encoded_config) { - const buf = new Uint8Array(encoded_config.buffer); - this.debug && console.log("Config Encodes Correctly: ", data.slice(2, -1).toString() === buf.toString()); + this.debug && + console.log("Config Encodes Correctly: ", data.slice(2, -1).toString() === encoded_config.toString()); } this.debug && console.log("Got Config: ", this.config); @@ -191,11 +209,7 @@ export class SMXStage { private handleTestData(data: Uint8Array): SMXSensorTestData | undefined { if (!this.config) return; - this.test = new SMXSensorTestData( - Array.from(data), - this.test_mode, - this.config.config.flags.PlatformFlags_FSR, - ); + this.test = new SMXSensorTestData(Array.from(data), this.test_mode, this.config.config.flags.PlatformFlags_FSR); this.debug && console.log("Got Test: ", this.test); diff --git a/sdk/state-machines/collate-packets.ts b/sdk/state-machines/collate-packets.ts index a7660ca..04349e7 100644 --- a/sdk/state-machines/collate-packets.ts +++ b/sdk/state-machines/collate-packets.ts @@ -8,12 +8,15 @@ import { PACKET_PREAMBLE_SIZE, } from "../packet"; +const ACK_PACKET = 0x7; + interface PacketHandlingState { currentPacket: Uint8Array; } export type DataPacket = { type: "data"; payload: Uint8Array }; -export type Packet = { type: "host_cmd_finished" } | DataPacket; +export type AckPacket = { type: "ack" }; +export type Packet = { type: "host_cmd_finished" } | DataPacket | AckPacket; /** * Gets called when a packet is received, returns a tuple of new state and an array of @@ -53,9 +56,15 @@ export const collatePackets: StateF = (st } // The data exists after the first 2 bytes - const dataBody = data.slice(2, 2 + byte_len); + const dataBody = data.slice(PACKET_PREAMBLE_SIZE, PACKET_PREAMBLE_SIZE + byte_len); + + /** + * If packet starts with `ACK_PACKET`, the `byte_len` is 0, and the whole array is just 0, + * then this is an ack packet. + */ + const isAck = (cmd & ACK_PACKET) === ACK_PACKET && byte_len === 0 && dataBody.byteLength === 0; - if ((cmd & PACKET_FLAG_START_OF_COMMAND) === PACKET_FLAG_START_OF_COMMAND && state.currentPacket.length > 0) { + if (cmd & PACKET_FLAG_START_OF_COMMAND && state.currentPacket.length > 0) { /** * When we get a start packet, the read buffer should already be empty. If it isn't, * we got a command that didn't end with an END_OF_COMMAND packet and something is wrong. @@ -85,7 +94,11 @@ export const collatePackets: StateF = (st if ((cmd & PACKET_FLAG_END_OF_COMMAND) === PACKET_FLAG_END_OF_COMMAND) { newState = { currentPacket: new Uint8Array(0) }; - eventsToPass.push({ type: "data", payload: nextPacket }); + if (isAck) { + eventsToPass.push({ type: "ack" }); + } else { + eventsToPass.push({ type: "data", payload: nextPacket }); + } } return [newState, eventsToPass.map((e) => new Bacon.Next(e))]; diff --git a/ui/pad-coms.ts b/ui/pad-coms.ts index df940ef..078d0e3 100644 --- a/ui/pad-coms.ts +++ b/ui/pad-coms.ts @@ -45,5 +45,6 @@ type FunctionKeys = keyof { /** anything here will appear in the debug UI to dispatch at will */ export const DEBUG_COMMANDS: Record> = { requestConfig: "updateConfig", + writeConfig: "writeConfig", requestTestData: "updateTestData", }; diff --git a/ui/stage/stage-test.tsx b/ui/stage/stage-test.tsx index 209473a..0dd733f 100644 --- a/ui/stage/stage-test.tsx +++ b/ui/stage/stage-test.tsx @@ -9,6 +9,7 @@ import { timez } from "./util"; const UI_UPDATE_RATE = 50; function useInputState(stage: SMXStage | undefined) { + const readTestData = useAtomValue(displayTestData$); const [panelStates, setPanelStates] = useState | null>(); useEffect(() => { return stage?.inputState$.throttle(UI_UPDATE_RATE).onValue(setPanelStates);