Skip to content

Commit

Permalink
Committing weekend updates
Browse files Browse the repository at this point in the history
  • Loading branch information
noahm committed Apr 1, 2024
1 parent ea0bd31 commit 52a9d80
Show file tree
Hide file tree
Showing 12 changed files with 478 additions and 90 deletions.
26 changes: 15 additions & 11 deletions sdk/api.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
const encoder = new TextEncoder();

export function char2byte(c: string): number {
return encoder.encode(c)[0];
}

export const API_COMMAND = {
GET_DEVICE_INFO: encoder.encode("i")[0],
GET_CONFIG: encoder.encode("g")[0],
GET_CONFIG_V5: encoder.encode("G")[0],
WRITE_CONFIG: encoder.encode("w")[0],
WRITE_CONFIG_V5: encoder.encode("W")[0],
FACTORY_RESET: encoder.encode("f")[0],
SET_LIGHT_STRIP: encoder.encode("L")[0],
FORCE_RECALIBRATION: encoder.encode("C")[0],
GET_SENSOR_TEST_DATA: encoder.encode("y")[0],
SET_SERIAL_NUMBERS: encoder.encode("s")[0],
SET_PANEL_TEST_MODE: encoder.encode("t")[0],
GET_DEVICE_INFO: char2byte("i"),
GET_CONFIG: char2byte("g"),
GET_CONFIG_V5: char2byte("G"),
WRITE_CONFIG: char2byte("w"),
WRITE_CONFIG_V5: char2byte("W"),
FACTORY_RESET: char2byte("f"),
SET_LIGHT_STRIP: char2byte("L"),
FORCE_RECALIBRATION: char2byte("C"),
GET_SENSOR_TEST_DATA: char2byte("y"),
SET_SERIAL_NUMBERS: char2byte("s"),
SET_PANEL_TEST_MODE: char2byte("t"),
};

export const SMX_USB_VENDOR_ID = 0x2341;
Expand Down
24 changes: 11 additions & 13 deletions sdk/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StructBuffer, bits, uint16_t, uint8_t } from "@nmann/struct-buffer";
import { StructBuffer, bits, uint8_t, uint16_t } from "@nmann/struct-buffer";
import type { EachPanel } from "./inputs.ts";

type DecodedStruct<SB extends StructBuffer> = ReturnType<SB["decode"]>;
export type DecodedStruct<SB extends StructBuffer> = ReturnType<SB["decode"]>;

/**
* Each FSR panel has 4 sensors. Make read/write easier by
Expand Down Expand Up @@ -102,7 +102,14 @@ const step_colors_t = new StructBuffer("step_colors_t", {
down_right: rgb_t,
});

const configShape = {
/**
* The configuration for a connected controller. This can be retrieved with SMX_GetConfig
* and modified with SMX_SetConfig.
*
* The order and packing of this struct corresponds to the configuration packet sent to
* the master controller, so it must not be changed.
*/
export const smx_config_t = new StructBuffer("smx_config_t", {
/**
* The firmware version of the master controller. Where supported (version 2 and up), this
* will always read back the firmware version. This will default to 0xFF on version 1.
Expand Down Expand Up @@ -218,16 +225,7 @@ const configShape = {
* Applications should leave any data in here unchanged when setting the Config.
*/
padding: uint8_t[49],
};

/**
* The configuration for a connected controller. This can be retrieved with SMX_GetConfig
* and modified with SMX_SetConfig.
*
* The order and packing of this struct corresponds to the configuration packet sent to
* the master controller, so it must not be changed.
*/
export const smx_config_t = new StructBuffer<typeof configShape>("smx_config_t", configShape);
});

/**
* Class to represent all 4 sensors on a panel.
Expand Down
10 changes: 5 additions & 5 deletions sdk/commands/data_info.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { StructBuffer, char, uint16_t, uint8_t } from "@nmann/struct-buffer";
import { StructBuffer, char, uint8_t, uint16_t } from "@nmann/struct-buffer";

const data_info_packet_t = new StructBuffer("data_info_packet_t", {
// Always 'I'
/** Always 'I' */
cmd: char,
// Not Used
packet_size: uint8_t,
// '0' for P1, '1' for P2 (Note there are the characters '0' and '1', not the numbers 0 and 1)
/** '0' for P1, '1' for P2 (Note these are the characters '0' and '1', not the numbers 0 and 1) */
player: char,
// Unused and Unknown
unused2: char,
Expand All @@ -18,7 +18,7 @@ const data_info_packet_t = new StructBuffer("data_info_packet_t", {
});

export class SMXDeviceInfo {
serial = new Uint8Array(16);
serial = "";
firmware_version = 0;
player = 0;

Expand All @@ -30,7 +30,7 @@ export class SMXDeviceInfo {
const info_packet = data_info_packet_t.decode(data, true);

this.player = Number.parseInt(String.fromCharCode(info_packet.player)) + 1;
this.serial = info_packet.serial.map((x) => x.toString(16).toUpperCase()).join("");
this.serial = info_packet.serial.map((x) => `00${x.toString(16).toUpperCase()}`.slice(-2)).join("");
this.firmware_version = info_packet.firmware_version;
}
}
8 changes: 8 additions & 0 deletions sdk/commands/inputs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { bitFields, uint16_t } from "@nmann/struct-buffer";

export type EachSensor<T> = {
up: T;
right: T;
down: T;
left: T;
};

export type EachPanel<T> = {
up_left: T;
up: T;
Expand All @@ -12,6 +19,7 @@ export type EachPanel<T> = {
down_right: T;
};

// TODO: This should be a bits object probably
export const StageInputs = bitFields(uint16_t, {
up_left: 1,
up: 1,
Expand Down
229 changes: 229 additions & 0 deletions sdk/commands/sensor_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { StructBuffer, bitFields, bits, bool, int16_t, uint8_t, uint16_t } from "@nmann/struct-buffer";
import { API_COMMAND, char2byte } from "../api";
import type { DecodedStruct } from "./config";
import type { EachPanel, EachSensor } from "./inputs";

/**
* Sensor Test Mode values the stages expect
*/
export const SENSOR_TEST_MODE = {
/** Actual 0 value */
OFF: 0,

/** Return the raw uncalibrated value of each sensor */
UNCALIBRATED_VALUES: char2byte("0"),

/** Return the calibrated value of each sensor */
CALIBRATED_VALUES: char2byte("1"),

/** Return the sensor noise value */
NOISE: char2byte("2"),

/** Return the sensor tare value */
TARE: char2byte("3"),
};

/**
* The first byte of the test mode detail data contains
* 3 signal bits, 4 bits showing if the sensor has a fault,
* and one dummy bit.
*
* Valid test data will always have sig1 = 0, sig2 = 1, and sig3 = 0
*/
const sig_bad_t = bits(uint8_t, {
sig1: 0,
sig2: 1,
sig3: 2,
bad_sensor_0: 3,
bad_sensor_1: 4,
bad_sensor_2: 5,
bad_sensor_3: 6,
dummy: 7,
});

/**
* The last byte of the test mode detail data contains
* the value of the panels dip switch, as well as 4 bits showing
* if any sensor has the wrong jumper set.
*
* The name `bad_sensor_dip_x` is taken from the source, and feels like
* kind of a misnomer.
*/
const dips_t = bitFields(uint8_t, {
dip: 4,
bad_sensor_dip_0: 1,
bad_sensor_dip_1: 1,
bad_sensor_dip_2: 1,
bad_sensor_dip_3: 1,
});

/**
* Intermediate test mode detail data.
* Contains the `sig_bad_t` data as defined above,
* 4 16-bit signed integers for the actual values of each sensor,
* and the `dips_t` data as defined above.
*
* These values are then used to create the `SMXPanelTestData` for each panel.
*/
const detail_data_t = new StructBuffer("detail_data_t", {
sig_bad: sig_bad_t,
sensors: int16_t[4],
dips: dips_t,
});

/**
* This class represents the results of an SensorTestData request for a single
* panel.
*/
class SMXPanelTestData {
have_data_from_panel: boolean;
sensor_level: EachSensor<number> = {
up: 0,
right: 0,
down: 0,
left: 0,
};
bad_sensor_input: EachSensor<boolean> = {
up: false,
right: false,
down: false,
left: false,
};
dip_switch_value = -1;
bad_jumper: EachSensor<boolean> = {
up: false,
right: false,
down: false,
left: false,
};

constructor(data: DecodedStruct<typeof detail_data_t>) {
/**
* Check the header. this is always `false true false` to identify it as a response,
* and not as random steps from the player.
*/
if (data.sig_bad.sig1 || !data.sig_bad.sig2 || data.sig_bad.sig3) {
this.have_data_from_panel = false;
return;
}

this.have_data_from_panel = true;

/**
* These bits are true if that sensor's most recent reading is invalid.
* A sensors reading could be considered invalid if the sensor has been turned
* off in the config tool.
*/
this.bad_sensor_input = {
up: Boolean(data.sig_bad.bad_sensor_0),
right: Boolean(data.sig_bad.bad_sensor_1),
down: Boolean(data.sig_bad.bad_sensor_2),
left: Boolean(data.sig_bad.bad_sensor_3),
};

// This is what the dipswitch is set to for this panel
this.dip_switch_value = data.dips.dip;

// These are true if the sensor has the incorrect jumper set
this.bad_jumper = {
up: Boolean(data.dips.bad_sensor_dip_0),
right: Boolean(data.dips.bad_sensor_dip_1),
down: Boolean(data.dips.bad_sensor_dip_2),
left: Boolean(data.dips.bad_sensor_dip_3),
};

this.sensor_level = {
up: data.sensors[0],
right: data.sensors[1],
down: data.sensors[2],
left: data.sensors[3],
};
}
}

export class SMXSensorTestData {
private data: Array<number>;
panels: EachPanel<SMXPanelTestData>;

constructor(data: Array<number>) {
this.data = data;

/**
* "y" is a response to our "y" query. This is binary data with the format:
* yAB......
* where A is our original query mode (currently "0", "1", "2", or "3"), and
* B is the number of bits from each panel in the response.
* Each bit is encoded as a 16-bit int, with each int having the response
* bits from each panel.
*/
console.assert(data[0] === API_COMMAND.GET_SENSOR_TEST_DATA); // Expected to be 'y'
// const mode = data[1]; // If we know what command we sent we could confirm we get the right response
const size = data[2];

/**
* Copy the data and convert from Little Endian formatted 8-bit bytes
* and place them into 16-bit bytes.
*/
const sensor_data_t = new StructBuffer("sensor_data_t", {
data: uint16_t[size],
});
const decoded_data = sensor_data_t.decode(data.slice(3), true);
const panel_count = 9; // TODO: This could be a const somewhere?

const panel_data = [];

console.log(data.slice(3));
console.log(decoded_data);

// Cycle through each panel and grab the data
// I'm not sure this one can be magic'd away with a struct buffer
for (let panel = 0; panel < panel_count; panel++) {
let idx = 0;
const out_bytes: Array<number> = [];

/**
* Read each byte in our decoded_data.
* The length here is the size from above (in bits) div by 8 to give bytes.
*/
for (const _ of Array.from({ length: size / 8 })) {
let result = 0;

// Read each bit in each byte
for (let bit = 0; bit < 8; bit++) {
const new_bit = decoded_data.data[idx] & (1 << panel);
result |= new_bit << bit;
idx++;
}

// We need to shift the result by the panel to move it back to fit within
// an 8-bit byte
out_bytes.push(result >> panel);
}

console.log(`Panel ${panel}: ${out_bytes}`);
panel_data.push(detail_data_t.decode(out_bytes, true));
}

const panels = [];
for (let panel = 0; panel < 9; panel++) {
panels.push(new SMXPanelTestData(panel_data[panel]));
}

this.panels = {
up_left: panels[0],
up: panels[1],
up_right: panels[2],
left: panels[3],
center: panels[4],
right: panels[5],
down_left: panels[6],
down: panels[7],
down_right: panels[8],
};

console.log(decoded_data);
console.log(panel_data);

console.log(this);
}
}
Loading

0 comments on commit 52a9d80

Please sign in to comment.