Skip to content

Commit

Permalink
Massive clean up of sensor_test.ts
Browse files Browse the repository at this point in the history
- Cleanup of logs, code, documentation
- Use enums for Panel and Sensor array positions
- Clamp/Scale sensor values based on mode
  • Loading branch information
fchorney committed Apr 2, 2024
1 parent defa62a commit 748ecc8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 103 deletions.
22 changes: 22 additions & 0 deletions sdk/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,25 @@ export const API_COMMAND = {
export const SMX_USB_VENDOR_ID = 0x2341;
export const SMX_USB_PRODUCT_ID = 0x8037;
export const SMX_USB_PRODUCT_NAME = "StepManiaX";

export const PANEL_COUNT = 9;
export const SENSOR_COUNT = 4;

export enum Panel {
UpLeft = 0,
Up = 1,
UpRight = 2,
Left = 3,
Center = 4,
Right = 5,
DownLeft = 6,
Down = 7,
DownRight = 8,
}

export enum Sensor {
Left = 0,
Right = 1,
Up = 2,
Down = 3,
}
206 changes: 109 additions & 97 deletions sdk/commands/sensor_test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { StructBuffer, bitFields, bits, bool, int16_t, uint16_t, uint8_t } from "@nmann/struct-buffer";
import { API_COMMAND, char2byte } from "../api";
import { StructBuffer, bitFields, bits, int16_t, uint16_t, uint8_t } from "@nmann/struct-buffer";
import { API_COMMAND, PANEL_COUNT, SENSOR_COUNT } from "../api";
import type { Decoded } from "./config";
import type { EachPanel, EachSensor } from "./inputs";

/**
* Sensor Test Mode values the stages expect
*/
export const SENSOR_TEST_MODE = {
export enum SensorTestMode {
/** 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"),
Off = 0,

/**
* Return the raw uncalibrated value of each sensor.
* 48 represents the char "0"
**/
UncalibratedValues = 48,

/**
* Return the calibrated value of each sensor
* 49 represents the char "1"
**/
CalibratedValues = 49,

/**
* Return the sensor noise value
* 50 represents the char "2"
**/
Noise = 50,

/**
* Return the sensor tare value
* 51 represents the char "3"
**/
Tare = 51,
};

/**
Expand Down Expand Up @@ -77,27 +88,12 @@ const detail_data_t = new StructBuffer("detail_data_t", {
*/
export 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,
};
sensor_level: Array<number> = Array(SENSOR_COUNT).fill(-1);
bad_sensor_input: Array<boolean> = Array(SENSOR_COUNT).fill(false);
dip_switch_value = -1;
bad_jumper: EachSensor<boolean> = {
up: false,
right: false,
down: false,
left: false,
};

constructor(data: Decoded<typeof detail_data_t>) {
bad_jumper: Array<boolean> = Array(SENSOR_COUNT).fill(false);

constructor(data: Decoded<typeof detail_data_t>, mode: SensorTestMode) {
/**
* 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.
Expand All @@ -107,74 +103,113 @@ export class SMXPanelTestData {
return;
}

// Assuming the sig bits are correct, we can confirm here that we have proper data
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: data.sig_bad.bad_sensor_0,
right: data.sig_bad.bad_sensor_1,
down: data.sig_bad.bad_sensor_2,
left: data.sig_bad.bad_sensor_3,
};
this.bad_sensor_input = [
data.sig_bad.bad_sensor_0,
data.sig_bad.bad_sensor_1,
data.sig_bad.bad_sensor_2,
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: !!data.dips.bad_sensor_dip_0,
right: !!data.dips.bad_sensor_dip_1,
down: !!data.dips.bad_sensor_dip_2,
left: !!data.dips.bad_sensor_dip_3,
};
this.bad_jumper = [
!!data.dips.bad_sensor_dip_0,
!!data.dips.bad_sensor_dip_1,
!!data.dips.bad_sensor_dip_2,
!!data.dips.bad_sensor_dip_3,
];

/**
* These are 16-bit signed integers for the sensor values.
* 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 = {
left: data.sensors[0],
right: data.sensors[1],
up: data.sensors[2],
down: data.sensors[3],
};
this.sensor_level = data.sensors.map((value) => this.clamp_sensor_value(value, mode));
}

private clamp_sensor_value(value: number, mode: SensorTestMode) {
if (mode === SensorTestMode.Noise) {
/**
* In Noise mode, we receive standard deviation values squared.
* Display the square root, since the panels don't do this for us.
* 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;

let clamped_value = value;
/**
* Very slightly negative values happen due to noise.
* The don't indicate a problem, but they're confusing
* in the UI, so clamp them away.
*/
if (value < 0 && value >= -10) {
clamped_value = 0;
}

// FSR values are bitshifted right by 2 (effectively a div by 4).
if (isFSR) {
clamped_value >>= 2;
}

return clamped_value;
}
}

export class SMXSensorTestData {
panels: EachPanel<SMXPanelTestData>;
panels: Array<SMXPanelTestData> = [];

constructor(data: Array<number>) {
/**
* The first 3 bytes are the preamble.
*
* "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.
* where A (mode) is our original query mode (currently "0", "1", "2", or "3"), and
* B (size) is the number of 16-Bit Integers that contain all of our panel data.
*
* The explanation of how these bits are interlaced and decoded can be found
* in the README.
*
* TODO: Put in readme link here
*/
const preamble = 3;
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
// TODO: We need to somehow know what mode we requested, so we can potentially check
// here that we got the right response.
const mode = SensorTestMode[data[1] as unknown as keyof typeof SensorTestMode];
console.assert(mode !== undefined);
const size = data[2];

/**
* Convert the data from 8-Bit Little Endian bytes to 16-Bit Integers
* Convert the data from 8-Bit Little Endian Bytes to 16-Bit Integers
*/
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 = [];
const sensor_data_t = new StructBuffer("sensor_data_t", {data: uint16_t[size]});
const decoded_data = sensor_data_t.decode(data.slice(preamble), true);

// Cycle through each panel and grab the data
// TODO: Document exactly how we're dealing with the bits here and how things are layed out
for (let panel = 0; panel < panel_count; panel++) {
for (let panel = 0; panel < PANEL_COUNT; panel++) {
let idx = 0;
const out_bytes: Array<number> = [];

Expand All @@ -185,41 +220,18 @@ export class SMXSensorTestData {
for (const _ of Array.from({ length: size / 8 })) {
let result = 0;

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

// We need to shift the result by the panel to move it back to fit within
// an 8-bit byte
// 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));
// Generate an SMXPanelTestData object for each panel
this.panels.push(new SMXPanelTestData(detail_data_t.decode(out_bytes, true), mode));
}

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);
}
}
4 changes: 2 additions & 2 deletions sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Bacon from "baconjs";
import type { StateF } from "baconjs/types/withstatemachine";
import { API_COMMAND } from "./api";
import { StageInputs } from "./commands/inputs";
import { SENSOR_TEST_MODE } from "./commands/sensor_test";
import { SensorTestMode } from "./commands/sensor_test";
import {
HID_REPORT_INPUT,
HID_REPORT_INPUT_STATE,
Expand All @@ -25,7 +25,7 @@ export async function getStageConfig(dev: HIDDevice) {
}

export async function getSensorTestData(dev: HIDDevice) {
await send_data(dev, [API_COMMAND.GET_SENSOR_TEST_DATA, SENSOR_TEST_MODE.CALIBRATED_VALUES], true);
await send_data(dev, [API_COMMAND.GET_SENSOR_TEST_DATA, SensorTestMode.CalibratedValues], true);
return process_packets(dev, true);
}

Expand Down
9 changes: 5 additions & 4 deletions ui/stage/fsr-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cn from "classnames";
import type { SMXPanelTestData } from "../../sdk/commands/sensor_test";
import { Sensor } from "../../sdk/api";

interface EnabledProps {
data: SMXPanelTestData;
Expand All @@ -15,10 +16,10 @@ export function FsrPanel(props: EnabledProps) {
active: props.active,
})}
>
<Fsr className="top horiz" badInput={bad_sensor_input.up} value={sensor_level.up} />
<Fsr className="right vert" badInput={bad_sensor_input.right} value={sensor_level.right} />
<Fsr className="bottom horiz" badInput={bad_sensor_input.down} value={sensor_level.down} />
<Fsr className="left vert" badInput={bad_sensor_input.left} value={sensor_level.left} />
<Fsr className="top horiz" badInput={bad_sensor_input[Sensor.Up]} value={sensor_level[Sensor.Up]} />
<Fsr className="right vert" badInput={bad_sensor_input[Sensor.Right]} value={sensor_level[Sensor.Right]} />
<Fsr className="bottom horiz" badInput={bad_sensor_input[Sensor.Down]} value={sensor_level[Sensor.Down]} />
<Fsr className="left vert" badInput={bad_sensor_input[Sensor.Left]} value={sensor_level[Sensor.Left]} />
</div>
);
}
Expand Down

0 comments on commit 748ecc8

Please sign in to comment.