From 1f014cde870532851d3b2e3debbd5f1c0c76289a Mon Sep 17 00:00:00 2001 From: Fernando Chorney Date: Sat, 6 Apr 2024 20:30:05 -0500 Subject: [PATCH] Various Fixes - Pass in `isFsr` to test data parsing - Throttle stage panel inputs in UI for better reactivity - General cleanup --- sdk/commands/sensor_test.ts | 21 +++----- sdk/packet.ts | 74 +++++---------------------- sdk/smx.ts | 32 +++++------- sdk/state-machines/collate-packets.ts | 7 +-- ui/stage/stage-test.tsx | 22 +++++--- 5 files changed, 56 insertions(+), 100 deletions(-) diff --git a/sdk/commands/sensor_test.ts b/sdk/commands/sensor_test.ts index 2b1cc15..fdaa829 100644 --- a/sdk/commands/sensor_test.ts +++ b/sdk/commands/sensor_test.ts @@ -93,7 +93,7 @@ export class SMXPanelTestData { dip_switch_value = -1; bad_jumper: Array = Array(SENSOR_COUNT).fill(false); - constructor(data: Decoded, mode: SensorTestMode) { + constructor(data: Decoded, mode: SensorTestMode, isFsr: boolean) { /** * Check the header. this is always `false true false` or `0 1 0` to identify it as a response, * and not as random steps from the player. @@ -134,10 +134,10 @@ export class SMXPanelTestData { * These are signed as they can be negative, but I imagine them going * negative is just kind of noise from the hardware. */ - this.sensor_level = data.sensors.map((value) => this.clamp_sensor_value(value, mode)); + this.sensor_level = data.sensors.map((value) => this.clamp_sensor_value(value, mode, isFsr)); } - private clamp_sensor_value(value: number, mode: SensorTestMode) { + private clamp_sensor_value(value: number, mode: SensorTestMode, isFsr: boolean) { if (mode === SensorTestMode.Noise) { /** * In Noise mode, we receive standard deviation values squared. @@ -145,18 +145,13 @@ export class SMXPanelTestData { * This makes the number different than the configured value * (square it to convert back), but without this we display a bunch * of four and five digit numbers that are too hard to read. - * - * TODO: Do we want to round this value or just display decimal values? */ return Math.sqrt(value); } - // TODO: We need a way to pass in if the stage we are getting this data for - // is using FSRs or not. Defined as `true` for now. - const isFSR = true; - // TODO: This may be necessary for defining sensor value vertial bars in the UI - // const max_value = isFSR ? 250 : 500; + // This will probably go in the UI and not here + // const max_value = isFsr ? 250 : 500; let clamped_value = value; /** @@ -169,7 +164,7 @@ export class SMXPanelTestData { } // FSR values are bitshifted right by 2 (effectively a div by 4). - if (isFSR) { + if (isFsr) { clamped_value >>= 2; } @@ -180,7 +175,7 @@ export class SMXPanelTestData { export class SMXSensorTestData { panels: Array = []; - constructor(data: Array, mode: SensorTestMode) { + constructor(data: Array, mode: SensorTestMode, isFsr: boolean) { /** * The first 3 bytes are the preamble. * @@ -236,7 +231,7 @@ export class SMXSensorTestData { } // Generate an SMXPanelTestData object for each panel - this.panels.push(new SMXPanelTestData(detail_data_t.decode(out_bytes, true), data_mode)); + this.panels.push(new SMXPanelTestData(detail_data_t.decode(out_bytes, true), data_mode, isFsr)); } } } diff --git a/sdk/packet.ts b/sdk/packet.ts index b1b9f67..3f984a9 100644 --- a/sdk/packet.ts +++ b/sdk/packet.ts @@ -1,37 +1,5 @@ import { pad_packet } from "./utils.ts"; -/** - * Gets the next report from the device matching a given reportId, - * wrapped in a promise for convenience. Times out after 500ms looking - * for a response. - */ -export function nextReportCommand(dev: HIDDevice): Promise { - return new Promise((resolve, reject) => { - // set a 500ms timeout - // TODO: Should this be longer? - const timeoutHandle = window.setTimeout(() => { - // stop listening - dev.removeEventListener("inputreport", handleReport); - reject("timeout"); - }, 500); - - function handleReport(event: HIDInputReportEvent) { - // TODO other more specific filtering here? - if (event.reportId !== HID_REPORT_INPUT) { - return; // not the report we're looking for - } - // stop listening - dev.removeEventListener("inputreport", handleReport); - // stop timeout - clearTimeout(timeoutHandle); - // return data to caller - resolve(event.data); - } - - dev.addEventListener("inputreport", handleReport); - }); -} - /* StepManiaX Stages expect packets that are exactly 64-bytes in length. @@ -43,7 +11,7 @@ Thus, we're going to set the packet length to 63, since the Report ID will be added to the data going out, making it 64 bits. */ export const MAX_PACKET_SIZE = 63; -const PACKET_PREAMBLE_SIZE = 2; +export const PACKET_PREAMBLE_SIZE = 2; // USB Communication Packet Flags export const PACKET_FLAG_START_OF_COMMAND = 0x04; @@ -56,9 +24,19 @@ export const HID_REPORT_INPUT_STATE = 0x03; export const HID_REPORT_OUTPUT = 0x05; export const HID_REPORT_INPUT = 0x06; -// Acknowledge Code -// TODO: Decide what to do with this -const ACK_COMMAND = 0x7; +export async function send_data(dev: HIDDevice, data: Array, debug = false) { + // Split data into packets + const packets = make_packets(data); + + if (debug) { + console.log("Sending Packets: ", packets); + } + + // Send each packet + for (const packet of packets) { + await dev.sendReport(HID_REPORT_OUTPUT, packet); + } +} export function make_packets(data: Array): Array { const packets = []; @@ -100,27 +78,3 @@ export function make_packets(data: Array): Array { return packets; } - -export async function makeSpecialDevicePacket(dev: HIDDevice, debug = false) { - const packet = pad_packet([PACKET_FLAG_DEVICE_INFO]); - - if (debug) { - console.log("Sending Packets: ", packet); - } - - await dev.sendReport(HID_REPORT_OUTPUT, packet); -} - -export async function send_data(dev: HIDDevice, data: Array, debug = false) { - // Split data into packets - const packets = make_packets(data); - - if (debug) { - console.log("Sending Packets: ", packets); - } - - // Send each packet - for (const packet of packets) { - await dev.sendReport(HID_REPORT_OUTPUT, packet); - } -} diff --git a/sdk/smx.ts b/sdk/smx.ts index 04d8a4f..44568db 100644 --- a/sdk/smx.ts +++ b/sdk/smx.ts @@ -47,9 +47,7 @@ class SMXEvents { .filter((e) => e.type === "host_cmd_finished") .map((e) => e.type === "host_cmd_finished"); - // this.otherReports$.onValue((value) => console.log("Packet: ", value)); - - finishedCommand$.log("Cmd Finished"); + // finishedCommand$.log("Cmd Finished"); const okSend$ = finishedCommand$.startWith(true); @@ -76,7 +74,7 @@ export class SMXStage { test: SMXSensorTestData | null = null; inputs: Array | null = null; private test_mode: SensorTestMode = SensorTestMode.CalibratedValues; // TODO: Maybe we just let this be public - private debug = true; + private debug = false; private configResponse$: Bacon.EventStream; @@ -86,8 +84,8 @@ export class SMXStage { // write outgoing events to the device this.events.eventsToSend$.onValue(async (value) => { - console.log("writing to HID"); - await send_data(this.dev, value); + this.debug && console.log("writing to HID"); + await send_data(this.dev, value, this.debug); }); // Set the device info handler @@ -112,13 +110,7 @@ export class SMXStage { } async init(): Promise { - /** - * This is a special RequestDeviceInfo packet. This is the same as sending an - * 'i' command, but we can send it safely at any time, even if another - * application is talking to the device. Thus we can do this during enumeration. - */ - //await requestSpecialDeviceInfo(this.dev); // Modify `send_data` to accept this somehow? - + // Request the device information this.updateDeviceInfo(); // Request some initial test data @@ -151,22 +143,26 @@ export class SMXStage { const encoded_config = this.config.encode(); if (encoded_config) { const buf = new Uint8Array(encoded_config.buffer); - console.log("Config Encodes Correctly: ", data.slice(2, -1).toString() === buf.toString()); + this.debug && console.log("Config Encodes Correctly: ", data.slice(2, -1).toString() === buf.toString()); } - console.log("Got Config: ", this.config); + this.debug && console.log("Got Config: ", this.config); return this.config; } private handleTestData(data: Uint8Array) { - this.test = new SMXSensorTestData(Array.from(data), this.test_mode); + this.test = new SMXSensorTestData( + Array.from(data), + this.test_mode, + this.config?.config?.flags?.PlatformFlags_FSR || true, + ); - console.log("Got Test: ", this.test); + this.debug && console.log("Got Test: ", this.test); } private handleDeviceInfo(data: Uint8Array) { this.info = new SMXDeviceInfo(Array.from(data)); - console.log("Got Info: ", this.info); + this.debug && console.log("Got Info: ", this.info); } private handleInputs(data: Decoded) { diff --git a/sdk/state-machines/collate-packets.ts b/sdk/state-machines/collate-packets.ts index ab96100..a7660ca 100644 --- a/sdk/state-machines/collate-packets.ts +++ b/sdk/state-machines/collate-packets.ts @@ -5,6 +5,7 @@ import { PACKET_FLAG_END_OF_COMMAND, PACKET_FLAG_HOST_CMD_FINISHED, PACKET_FLAG_START_OF_COMMAND, + PACKET_PREAMBLE_SIZE, } from "../packet"; interface PacketHandlingState { @@ -18,6 +19,7 @@ export type Packet = { type: "host_cmd_finished" } | DataPacket; * Gets called when a packet is received, returns a tuple of new state and an array of */ export const collatePackets: StateF = (state, event) => { + // TODO: This whole function could maybe just use a bit more comments if (!Bacon.hasValue(event)) { console.log("No Event Value"); return [state, []]; @@ -29,7 +31,7 @@ export const collatePackets: StateF = (st // console.log("Raw Packet Data: ", data); // Return if packet is empty - if (data.length <= 3) { + if (data.length <= PACKET_PREAMBLE_SIZE) { console.log("Empty Packet"); return [state, []]; } @@ -44,8 +46,7 @@ export const collatePackets: StateF = (st console.log("Found Packet Flag Device Info"); } - // TODO: Make some consts for these 2's everywhere - if (2 + byte_len > data.length) { + if (PACKET_PREAMBLE_SIZE + byte_len > data.length) { // TODO: Can this even happen??? console.log("Communication Error: Oversized Packet (ignored)"); return [state, []]; diff --git a/ui/stage/stage-test.tsx b/ui/stage/stage-test.tsx index 07184bd..25c08fa 100644 --- a/ui/stage/stage-test.tsx +++ b/ui/stage/stage-test.tsx @@ -4,14 +4,24 @@ import { FsrPanel } from "./fsr-panel"; import { type SMXStage, SensorTestMode, type SMXPanelTestData, type SMXSensorTestData } from "../../sdk/"; import { displayTestData$ } from "../state"; +// UI Update Rate in Milliseconds +const UI_UPDATE_RATE = 50; + function useInputState(stage: SMXStage | undefined) { - const [panelStates, setPanelStates] = useState | undefined>(); - const inputs = stage?.inputs || undefined; + const [panelStates, setPanelStates] = useState | null>(); useEffect(() => { if (!stage) return; - return setPanelStates(inputs); //TODO: Figure out why this feels laggy? - }, [stage, inputs]); + + const d = stage; + async function update() { + setPanelStates(d.inputs); + } + + const handle = setInterval(update, UI_UPDATE_RATE); + return () => clearInterval(handle); + }, [stage]); + return panelStates; } @@ -30,7 +40,7 @@ function useTestData(stage: SMXStage | undefined) { setTestData(d.test); } - const handle = setInterval(update, 50); + const handle = setInterval(update, UI_UPDATE_RATE); return () => clearInterval(handle); }, [stage, readTestData]); @@ -46,7 +56,7 @@ export function StageTest({ const testData = useTestData(stage); const inputState = useInputState(stage); - if (!testData) { + if (!testData || !inputState) { return null; }