From 3fa39f34bed784937631a203657adf1a21fe819d Mon Sep 17 00:00:00 2001 From: Fernando Chorney Date: Wed, 10 Apr 2024 14:07:18 -0500 Subject: [PATCH] Add code to read/write/convert old config --- package.json | 2 +- sdk/commands/config.ts | 314 ++++++++++++++++++++++++++++++++++++++++- sdk/smx.ts | 26 ++-- yarn.lock | 10 +- 4 files changed, 325 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 7aab4e1..cb8959a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "author": "Noah Manneschmidt ", "license": "MIT", "dependencies": { - "@nmann/struct-buffer": "^5.4.0", + "@nmann/struct-buffer": "^5.4.1", "baconjs": "patch:baconjs@npm%3A3.0.17#~/.yarn/patches/baconjs-npm-3.0.17-1a95474784.patch", "classnames": "^2.5.1", "jotai": "^2.8.0", diff --git a/sdk/commands/config.ts b/sdk/commands/config.ts index ee6dffe..750bdae 100644 --- a/sdk/commands/config.ts +++ b/sdk/commands/config.ts @@ -1,5 +1,6 @@ -import { StructBuffer, bits, uint8_t, uint16_t } from "@nmann/struct-buffer"; +import { StructBuffer, bits, uint8_t, uint16_t, sbytes } from "@nmann/struct-buffer"; import { EnabledSensors } from "./enabled-sensors.ts"; +import { Panel } from "../api.ts"; export type Decoded = ReturnType; @@ -188,24 +189,323 @@ export const smx_config_t = new StructBuffer("smx_config_t", { padding: uint8_t[49], }); +const NEW_CONFIG_INIT = sbytes( + [ + // masterVersion : 255 + "FF", + // configVersion : 5 + "05", + // flags : 0 + "00", + // debounceNodelayMilliseconds : 0 + "0000", + // debounceDelayMilliseconds : 0 + "0000", + // panelDebounceMicroseconds : 4000 + "0FA0", + // autoCalibrationMaxDeviation : 100 + "64", + // badSensorMinimumDelaySeconds : 15 + "0F", + // autoCalibrationAveragesPerUpdate : 60 + "003C", + // autoCalibrationSamplesPerAverage : 500 + "01F4", + // autoCalibrationMaxTare : 65535 + "FFFF", + // enabledSensors + "00".repeat(5), + // autoLightsTimeout : 1000 / 128 (1 second) + "07", + // stepColor + "00".repeat(3 * 9), + // platformStripColor + "00".repeat(3), + // autoLightPanelMask : 65535 + "FFFF", + // panelRotation + "00", + // packedSensorSettings + "00".repeat(16 * 9), + // preDetailsDelayMilliseconds : 5 + "05", + // padding + "00".repeat(49), + ].join(), +); + +const smx_old_config_t = new StructBuffer("smx_old_config_t", { + unused1: uint8_t[6], + + masterDebounceMilliseconds: uint16_t, + + // was "cardinal" + panelThreshold7Low: uint8_t, + panelThreshold7High: uint8_t, + + // was "center" + panelThreshold4Low: uint8_t, + panelThreshold4High: uint8_t, + + // was "corner" + panelThreshold2Low: uint8_t, + panelThreshold2High: uint8_t, + + panelDebounceMicroseconds: uint16_t, + autoCalibrationPeriodMilliseconds: uint16_t, + autoCalibrationMaxDeviation: uint8_t, + badSensorMinimumDelaySeconds: uint8_t, + autoCalibrationAveragesPerUpdate: uint16_t, + + unused2: uint8_t[2], + + // was "up" + panelThreshold1Low: uint8_t, + panelThreshold1High: uint8_t, + + enabledSensors: new EnabledSensors(), + + autoLightsTimeout: uint8_t, + + stepColor: rgb_t[9], + + panelRotation: uint8_t, + + autoCalibrationSamplesPerAverage: uint16_t, + + masterVersion: uint8_t, + configVersion: uint8_t, + + unused3: uint8_t[10], + + panelThreshold0Low: uint8_t, + panelThreshold0High: uint8_t, + panelThreshold3Low: uint8_t, + panelThreshold3High: uint8_t, + panelThreshold5Low: uint8_t, + panelThreshold5High: uint8_t, + panelThreshold6Low: uint8_t, + panelThreshold6High: uint8_t, + panelThreshold8Low: uint8_t, + panelThreshold8High: uint8_t, + + debounceDelayMilliseconds: uint16_t, + + padding: uint8_t[164], +}); + +const OLD_CONFIG_INIT = sbytes( + [ + // unused + "FF".repeat(6), + // masterDebounceMilliseconds + "0000", + // panelThreshold{7/4/2}{Low/High} + "FF FF FF FF FF FF", + // panelDebounceMicroseconds : 4000 + "0FA0", + // autoCalibrationPeriodMilliseconds : 1000 + "03E8", + // autoCalibrationMaxDeviation : 100 + "64", + // badSensorMinimumDelaySeconds : 15 + "0F", + // autoCalibrationAveragesPerUpdate : 60 + "003C", + // unused + "FFFF", + // panelThreshold1{Low/High} + "FF FF", + // enabledSensors + "00".repeat(5), + // autoLightsTimeout : 1000 / 128 (1 second) + "07", + // StepColor + "00".repeat(3 * 9), + // panelRotation + "00", + // autoCalibrationSamplesPerAverage : 500 + "01F4", + // masterVersion + "FF", + // configVersion : 3 + "03", + // unused + "FF".repeat(10), + // panelThreshold{0/3/5/6/8}{Low/High} + "FF FF FF FF FF FF FF FF FF FF", + // debounceDelayMilliseconds : 0 + "0000", + // padding + "00".repeat(164), + ].join(), +); + /** * The configuration for a connected SMX Stage. */ export class SMXConfig { public config: Decoded; + private oldConfig: Decoded | null = null; + private firmwareVersion: number; /** * Take in the data array and decode it into this. */ - constructor(data: Array) { - this.config = smx_config_t.decode(data.slice(2, -1), true); + constructor(data: Array, firmware_version: number) { + this.firmwareVersion = firmware_version; + + if (this.firmwareVersion <= 5) { + this.oldConfig = smx_old_config_t.decode(data.slice(2, -1), true); + this.config = this.convertOldToNew(this.oldConfig); + } else { + this.config = smx_config_t.decode(data.slice(2, -1), true); + } } - /** - * 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(): Array { + if (this.firmwareVersion <= 5) { + if (!this.oldConfig) throw new ReferenceError("Can not encode old config as it is null"); + + this.convertNewToOld(); + return Array.from(new Uint8Array(smx_old_config_t.encode(this.oldConfig, true).buffer)); + } return Array.from(new Uint8Array(smx_config_t.encode(this.config, true).buffer)); } + + /** + * Before encoding the config to send back to an old SMX stage, + * we need to copy data from the new config object back into the old config object. + * + * We don't need to check configVersion here since it's safe to set all fields in the + * oldConfig even if the stage doesn't use them. + */ + private convertNewToOld() { + if (!this.oldConfig) throw new ReferenceError("Can not convert new config to old as oldConfig is null"); + + const newConfig = this.config; + const oldConfig = this.oldConfig; + + oldConfig.masterDebounceMilliseconds = newConfig.debounceNodelayMilliseconds; + + // was "cardinal" + oldConfig.panelThreshold7Low = newConfig.panelSettings[Panel.Down].loadCellLowThreshold; + oldConfig.panelThreshold7High = newConfig.panelSettings[Panel.Down].loadCellHighThreshold; + + // was "center" + oldConfig.panelThreshold4Low = newConfig.panelSettings[Panel.Center].loadCellLowThreshold; + oldConfig.panelThreshold4High = newConfig.panelSettings[Panel.Center].loadCellHighThreshold; + + // was "corner" + oldConfig.panelThreshold2Low = newConfig.panelSettings[Panel.UpRight].loadCellLowThreshold; + oldConfig.panelThreshold2High = newConfig.panelSettings[Panel.UpRight].loadCellHighThreshold; + + oldConfig.panelDebounceMicroseconds = newConfig.panelDebounceMicroseconds; + oldConfig.autoCalibrationMaxDeviation = newConfig.autoCalibrationMaxDeviation; + oldConfig.badSensorMinimumDelaySeconds = newConfig.badSensorMinimumDelaySeconds; + oldConfig.autoCalibrationAveragesPerUpdate = newConfig.autoCalibrationAveragesPerUpdate; + + // was "up" + oldConfig.panelThreshold1Low = newConfig.panelSettings[Panel.Up].loadCellLowThreshold; + oldConfig.panelThreshold1High = newConfig.panelSettings[Panel.Up].loadCellHighThreshold; + + oldConfig.enabledSensors = newConfig.enabledSensors; + oldConfig.autoLightsTimeout = newConfig.autoLightsTimeout; + oldConfig.stepColor = newConfig.stepColor; + oldConfig.panelRotation = newConfig.panelRotation; + oldConfig.autoCalibrationSamplesPerAverage = newConfig.autoCalibrationSamplesPerAverage; + + oldConfig.masterVersion = newConfig.masterVersion; + oldConfig.configVersion = newConfig.configVersion; + + oldConfig.panelThreshold0Low = newConfig.panelSettings[Panel.UpLeft].loadCellLowThreshold; + oldConfig.panelThreshold0High = newConfig.panelSettings[Panel.UpLeft].loadCellHighThreshold; + + oldConfig.panelThreshold3Low = newConfig.panelSettings[Panel.Left].loadCellLowThreshold; + oldConfig.panelThreshold3High = newConfig.panelSettings[Panel.Left].loadCellHighThreshold; + + oldConfig.panelThreshold5Low = newConfig.panelSettings[Panel.Right].loadCellLowThreshold; + oldConfig.panelThreshold5High = newConfig.panelSettings[Panel.Right].loadCellHighThreshold; + + oldConfig.panelThreshold6Low = newConfig.panelSettings[Panel.DownLeft].loadCellLowThreshold; + oldConfig.panelThreshold6High = newConfig.panelSettings[Panel.DownLeft].loadCellHighThreshold; + + oldConfig.panelThreshold8Low = newConfig.panelSettings[Panel.DownRight].loadCellLowThreshold; + oldConfig.panelThreshold8High = newConfig.panelSettings[Panel.DownRight].loadCellHighThreshold; + + oldConfig.debounceDelayMilliseconds = newConfig.debounceDelayMilliseconds; + } + + /** + * Given a parsed old config object, initiate a new config object + * and replace the necessary values from the old config. + * + * Depending on the old configs configVersion, we may exit early as earlier versiond of configs + * didn't have certain fields set. + * + * We return the config object so that the constructor has a definitive `this.config` assignment. + * + * @param oldConfig Decoded smx_old_config_t object + * @returns A new config object built from old config data + */ + private convertOldToNew(oldConfig: Decoded): Decoded { + console.log("old config: ", oldConfig); + const newConfig = smx_config_t.decode(NEW_CONFIG_INIT); + + newConfig.debounceNodelayMilliseconds = oldConfig.masterDebounceMilliseconds; + + // was "cardinal" + newConfig.panelSettings[Panel.Down].loadCellLowThreshold = oldConfig.panelThreshold7Low; + newConfig.panelSettings[Panel.Down].loadCellHighThreshold = oldConfig.panelThreshold7High; + + // was "center" + newConfig.panelSettings[Panel.Center].loadCellLowThreshold = oldConfig.panelThreshold4Low; + newConfig.panelSettings[Panel.Center].loadCellHighThreshold = oldConfig.panelThreshold4High; + + // was "corner" + newConfig.panelSettings[Panel.UpRight].loadCellLowThreshold = oldConfig.panelThreshold2Low; + newConfig.panelSettings[Panel.UpRight].loadCellHighThreshold = oldConfig.panelThreshold2High; + + newConfig.panelDebounceMicroseconds = oldConfig.panelDebounceMicroseconds; + newConfig.autoCalibrationMaxDeviation = oldConfig.autoCalibrationMaxDeviation; + newConfig.badSensorMinimumDelaySeconds = oldConfig.badSensorMinimumDelaySeconds; + newConfig.autoCalibrationAveragesPerUpdate = oldConfig.autoCalibrationAveragesPerUpdate; + + // was "up" + newConfig.panelSettings[Panel.Up].loadCellLowThreshold = oldConfig.panelThreshold1Low; + newConfig.panelSettings[Panel.Up].loadCellHighThreshold = oldConfig.panelThreshold1High; + + newConfig.enabledSensors = oldConfig.enabledSensors; + newConfig.autoLightsTimeout = oldConfig.autoLightsTimeout; + newConfig.stepColor = oldConfig.stepColor; + newConfig.panelRotation = oldConfig.panelRotation; + newConfig.autoCalibrationSamplesPerAverage = oldConfig.autoCalibrationSamplesPerAverage; + + if (oldConfig.configVersion === 0xff) return newConfig; + + newConfig.masterVersion = oldConfig.masterVersion; + newConfig.configVersion = oldConfig.configVersion; + + if (oldConfig.configVersion < 2) return newConfig; + + newConfig.panelSettings[Panel.UpLeft].loadCellLowThreshold = oldConfig.panelThreshold0Low; + newConfig.panelSettings[Panel.UpLeft].loadCellHighThreshold = oldConfig.panelThreshold0High; + + newConfig.panelSettings[Panel.Left].loadCellLowThreshold = oldConfig.panelThreshold3Low; + newConfig.panelSettings[Panel.Left].loadCellHighThreshold = oldConfig.panelThreshold3High; + + newConfig.panelSettings[Panel.Right].loadCellLowThreshold = oldConfig.panelThreshold5Low; + newConfig.panelSettings[Panel.Right].loadCellHighThreshold = oldConfig.panelThreshold5High; + + newConfig.panelSettings[Panel.DownLeft].loadCellLowThreshold = oldConfig.panelThreshold6Low; + newConfig.panelSettings[Panel.DownLeft].loadCellHighThreshold = oldConfig.panelThreshold6High; + + newConfig.panelSettings[Panel.DownRight].loadCellLowThreshold = oldConfig.panelThreshold8Low; + newConfig.panelSettings[Panel.DownRight].loadCellHighThreshold = oldConfig.panelThreshold8High; + if (oldConfig.configVersion < 3) return newConfig; + + newConfig.debounceDelayMilliseconds = oldConfig.debounceDelayMilliseconds; + + return newConfig; + } } diff --git a/sdk/smx.ts b/sdk/smx.ts index 41c4d35..28644d5 100644 --- a/sdk/smx.ts +++ b/sdk/smx.ts @@ -59,10 +59,16 @@ class SMXEvents { this.output$ = new Bacon.Bus>(); // Config writes should only happen at most once per second. - const configOutput$ = this.output$.filter((e) => e[0] === API_COMMAND.WRITE_CONFIG_V5).throttle(1000); + const configOutput$ = this.output$ + .filter((e) => { + return e[0] === API_COMMAND.WRITE_CONFIG || e[0] === API_COMMAND.WRITE_CONFIG_V5; + }) + .throttle(1000); // All other writes are passed through unchanged - const otherOutput$ = this.output$.filter((e) => e[0] !== API_COMMAND.WRITE_CONFIG_V5); + const otherOutput$ = this.output$.filter((e) => { + return e[0] !== API_COMMAND.WRITE_CONFIG && e[0] !== API_COMMAND.WRITE_CONFIG_V5; + }); // combine together the throttled and unthrottled writes // and only emit one per each "ok" signal we get back following each previous output @@ -105,7 +111,7 @@ export class SMXStage { // Set the config request handler this.configResponse$ = this.events.otherReports$ - .filter((e) => e[0] === API_COMMAND.GET_CONFIG_V5) + .filter((e) => e[0] === API_COMMAND.GET_CONFIG || e[0] === API_COMMAND.GET_CONFIG_V5) .map((value) => this.handleConfig(value)); // note that the above map function only runs when there are listeners // subscribed to `this.configResponse$`, otherwise nothing happens! @@ -227,16 +233,8 @@ export class SMXStage { } private handleConfig(data: Uint8Array): SMXConfig { - /* - // TODO: Figure out how we want to handle this? I think we can actually convert to/from the new config - // from the old config - if (this.info.firmware_version < 5) { - this.config = new SMXConfigOld(Array.from(data)); - } else { - this.config = new SMXConfig(Array.from(data)); - } - */ - this.config = new SMXConfig(Array.from(data)); + // biome-ignore lint/style/noNonNullAssertion: info should very much be defined here + this.config = new SMXConfig(Array.from(data), this.info!.firmware_version); // Right now I just want to confirm that decoding and encoding gives us back the same data const encoded_config = this.config.encode(); @@ -250,7 +248,7 @@ export class SMXStage { } private handleTestData(data: Uint8Array): SMXSensorTestData { - // biome-ignore lint/style/noNonNullAssertion: Config should very much be defined here + // biome-ignore lint/style/noNonNullAssertion: config should very much be defined here 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/yarn.lock b/yarn.lock index 7227846..04d57af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -587,10 +587,10 @@ __metadata: languageName: node linkType: hard -"@nmann/struct-buffer@npm:^5.4.0": - version: 5.4.0 - resolution: "@nmann/struct-buffer@npm:5.4.0" - checksum: 10c0/f47ee36659e805831aadd3e2f2b75dffb00c0540c9f09a2ee7ea3f99de12b0ab0bd3ea53038893cbb5cb6e8463cee94e548ea4c45a8b98a555cdaa87f4abff9e +"@nmann/struct-buffer@npm:^5.4.1": + version: 5.4.1 + resolution: "@nmann/struct-buffer@npm:5.4.1" + checksum: 10c0/7f5974c34f0b5c27b4f20b423e90c51fa91aef7578e8af285856a4049809c83e569aa44680fefbfd72fd41580b269479511cabdc6112d7a8b7c2b8481b5eed08 languageName: node linkType: hard @@ -2466,7 +2466,7 @@ __metadata: resolution: "smx-config-web@workspace:." dependencies: "@biomejs/biome": "npm:1.6.4" - "@nmann/struct-buffer": "npm:^5.4.0" + "@nmann/struct-buffer": "npm:^5.4.1" "@types/react": "npm:^18.2.75" "@types/react-dom": "npm:^18.2.23" "@types/w3c-web-hid": "npm:1.0.6"