Skip to content

Commit

Permalink
Merge pull request #42 from noahm/fc/add-panel-test-mode
Browse files Browse the repository at this point in the history
  • Loading branch information
noahm authored Feb 21, 2025
2 parents 7391a0a + 945216f commit 2c76989
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 16 deletions.
8 changes: 8 additions & 0 deletions sdk/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ export const API_COMMAND = {
GET_SENSOR_TEST_DATA: char2byte("y"),
SET_SERIAL_NUMBERS: char2byte("s"),
SET_PANEL_TEST_MODE: char2byte("t"),
SET_LIGHTS_OLD: char2byte("l"),
};

export enum PanelTestMode {
/** 48 represents the char "0" */
Off = 48,
/** 49 represents the char "1" */
PressureTest = 49,
}

export const SMX_USB_VENDOR_ID = 0x2341;
export const SMX_USB_PRODUCT_ID = 0x8037;
export const SMX_USB_PRODUCT_NAME = "StepManiaX";
Expand Down
54 changes: 52 additions & 2 deletions sdk/smx.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as Bacon from "baconjs";
import { collatePackets, type AckPacket, type DataPacket } from "./state-machines/collate-packets";
import { API_COMMAND, char2byte } from "./api";
import { API_COMMAND, PanelTestMode, char2byte } from "./api";
import { SMXConfig, type Decoded } from "./commands/config";
import { SMXDeviceInfo } from "./commands/data_info";
import { StageInputs } from "./commands/inputs";
import { HID_REPORT_INPUT, HID_REPORT_INPUT_STATE, send_data } from "./packet";
import { SMXSensorTestData, SensorTestMode } from "./commands/sensor_test";
import { RGB } from "./utils";
import { RGB, padData } from "./utils";

/**
* Class purely to set up in/out event stream "pipes" to properly throttle and sync input/output from a stage
Expand Down Expand Up @@ -80,6 +80,8 @@ export class SMXStage {
private test_mode: SensorTestMode = SensorTestMode.CalibratedValues;
private debug = true;
private _config: SMXConfig | null = null;
private panelTestMode = PanelTestMode.Off;
private cancelTestModeInterval: Bacon.Unsub | null = null;

public get config() {
return this._config?.config || null;
Expand Down Expand Up @@ -231,6 +233,54 @@ export class SMXStage {
return this.testDataResponse$.firstToPromise();
}

getPanelTestMode() {
return this.panelTestMode;
}

setPanelTestMode(mode: PanelTestMode) {
// If we want to turn panel test mode off...
if (mode === PanelTestMode.Off) {
// We don't want to send the "Off" command multiple times, so only send it if
// it's currently activated
if (this.panelTestMode !== PanelTestMode.Off) {
if (this.cancelTestModeInterval) {
this.cancelTestModeInterval();
this.cancelTestModeInterval = null;
}
// Turn off panel test mode, and send "Off" event
this.panelTestMode = mode;
this.events.output$.push(Uint8Array.of(API_COMMAND.SET_PANEL_TEST_MODE, char2byte(" "), mode, char2byte("\n")));
}

// Either we're already off, or we sent the off command, so just return.
return;
}

// We only need to run this when the current mode is not the same as the requested mode
if (this.panelTestMode !== mode) {
this.panelTestMode = mode;

/**
* the 'l' command used to set lights, but it's now only used to turn lights off
* for cases like this
* 1 pad * 9 panels * 25 lights each * 3 (RGB) = 675
* The source code uses `108` instead and I'm really unsure why,
* but we're gonna do the same thing here because it works.
*/
this.events.output$.push(Uint8Array.of(API_COMMAND.SET_LIGHTS_OLD, ...padData([], 108, 0), char2byte("\n")));

// Send the Panel Test Mode command
this.events.output$.push(Uint8Array.of(API_COMMAND.SET_PANEL_TEST_MODE, char2byte(" "), mode, char2byte("\n")));

// The Panel Test Mode command needs to be resent approximately every second, or else the stage will
// auto time out and turn off Panel Test Mode itself
this.cancelTestModeInterval = Bacon.interval(1000, null).subscribe(() => {
console.log("Test Mode Push Interval");
this.events.output$.push(Uint8Array.of(API_COMMAND.SET_PANEL_TEST_MODE, char2byte(" "), mode, char2byte("\n")));
});
}
}

private handleConfig(data: Uint8Array): SMXConfig {
// biome-ignore lint/style/noNonNullAssertion: info should very much be defined here
this._config = new SMXConfig(data, this.info!.firmware_version);
Expand Down
9 changes: 1 addition & 8 deletions ui/DebugCommands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useAtomValue } from "jotai";
import { useState } from "react";
import { DEBUG_COMMANDS } from "./pad-coms";
import { selectedStage$ } from "./state.ts";
import { RGB } from "../sdk/utils.ts";

const cmds = Array.from(Object.entries(DEBUG_COMMANDS));

Expand All @@ -13,13 +12,7 @@ export function DebugCommands() {
selectedCommand && stage
? async () => {
const cmd = DEBUG_COMMANDS[selectedCommand];
if (cmd === "setLightStrip") {
// TODO: We don't even expose this to the dropdown, but this seemed
// like the easiest fix for now.
await stage[cmd](new RGB(0, 0, 255));
} else {
await stage[cmd]();
}
await stage[cmd]();
}
: undefined;
return (
Expand Down
4 changes: 2 additions & 2 deletions ui/pad-coms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ type FunctionKeys<T extends object> = keyof {
};

/** anything here will appear in the debug UI to dispatch at will */
export const DEBUG_COMMANDS: Record<string, FunctionKeys<SMXStage>> = {
export const DEBUG_COMMANDS = {
requestConfig: "updateConfig",
writeConfig: "writeConfig",
requestTestData: "updateTestData",
forceRecalibration: "forceRecalibration",
factoryReset: "factoryReset",
};
} as const satisfies Record<string, FunctionKeys<SMXStage>>;
29 changes: 25 additions & 4 deletions ui/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAtomValue, useAtom, type Atom } from "jotai";
import { useAtomValue, useAtom } from "jotai";
import type React from "react";
import { useEffect, useId } from "react";
import { useEffect } from "react";

import { DebugCommands } from "./DebugCommands.tsx";
import { open_smx_device, promptSelectDevice } from "./pad-coms.ts";
Expand All @@ -14,8 +14,8 @@ import {
} from "./state.ts";
import { StageTest } from "./stage/stage-test.tsx";
import { TypedSelect } from "./common/typed-select.tsx";
import type { SMXStage } from "../sdk/smx.ts";
import { ConfigValues } from "./stage/config.tsx";
import { PanelTestMode } from "../sdk/api.ts";

function usePreviouslyPairedDevices() {
useEffect(() => {
Expand All @@ -41,7 +41,7 @@ export function UI() {
<PickDevice /> <DebugCommands />
</p>
<p>
<TestDataDisplayToggle />
<TestDataDisplayToggle /> <PanelTestModeToggle />
</p>
<ConfigValues stageAtom={selectedStage$} />
<StatusDisplay />
Expand Down Expand Up @@ -96,13 +96,15 @@ function StatusDisplay() {
}

function TestDataDisplayToggle() {
const stage = useAtomValue(selectedStage$);
const [testMode, setTestMode] = useAtom(displayTestData$);

return (
// biome-ignore lint/a11y/noLabelWithoutControl: the control is in the TypedSelect
<label>
Read Test Values:{" "}
<TypedSelect
disabled={!stage}
value={testMode}
options={[
["", "None"],
Expand All @@ -116,3 +118,22 @@ function TestDataDisplayToggle() {
</label>
);
}

function PanelTestModeToggle() {
const stage = useAtomValue(selectedStage$);

return (
<label>
Panel Test Mode:{" "}
<input
type="checkbox"
style={{ height: "2em", width: "2em" }}
disabled={!stage}
defaultChecked={stage?.getPanelTestMode() === PanelTestMode.PressureTest}
onChange={(e) => {
stage?.setPanelTestMode(e.currentTarget.checked ? PanelTestMode.PressureTest : PanelTestMode.Off);
}}
/>
</label>
);
}

0 comments on commit 2c76989

Please sign in to comment.