Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PanelTestMode suppport to smx class #42

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
97 changes: 95 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 @@ -82,6 +82,8 @@ export class SMXStage {
private test_mode: SensorTestMode = SensorTestMode.CalibratedValues;
private debug = true;
private _config: SMXConfig | null = null;
private panelTestMode = PanelTestMode.Off;
private testModeIntervalHandle: number | null = null;

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

/*
void SMX::SMXManager::UpdatePanelTestMode()
{
// If the test mode has changed, send the new test mode.
//
// When the test mode is enabled, send the test mode again periodically, or it'll time
// out on the master and be turned off. Don't repeat the PanelTestMode_Off command.
g_Lock.AssertLockedByCurrentThread();
uint32_t now = GetTickCount();
if(m_PanelTestMode == m_LastSentPanelTestMode &&
(m_PanelTestMode == PanelTestMode_Off || now - m_SentPanelTestModeAtTicks < 1000))
return;

// When we first send the test mode command (not for repeats), turn off lights.
if(m_LastSentPanelTestMode == PanelTestMode_Off)
{
// The 'l' command used to set lights, but it's now only used to turn lights off
// for cases like this.
string sData = "l";
sData.append(108, 0);
sData += "\n";
for(int iPad = 0; iPad < 2; ++iPad)
m_pDevices[iPad]->SendCommandLocked(sData);
}

m_SentPanelTestModeAtTicks = now;
m_LastSentPanelTestMode = m_PanelTestMode;
for(int iPad = 0; iPad < 2; ++iPad)
m_pDevices[iPad]->SendCommandLocked(ssprintf("t %c\n", m_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.testModeIntervalHandle !== null) {
clearInterval(this.testModeIntervalHandle);
this.testModeIntervalHandle = 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, mode));

// TODO: Do we need to do anything with this? Does this even need to be called to flush
// the queue?
this.events.ackReports$.firstToPromise();
}

// 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
* Lights are always updated together (for some reason??)
* 2 pads * 9 panels * 25 lights each * 3 (RGB) = 1350
* The source code uses `108` and I'm really unsure why
*
* TODO: Does this even do anything?
*/
this.events.output$.push(Uint8Array.of(API_COMMAND.SET_LIGHTS_OLD, ...padData([], 1350, 0), char2byte("\n")));

// TODO: Do we need to do anything with this? Does this even need to be called to flush
// the queue?
this.events.ackReports$.firstToPromise();

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

// TODO: Do we need to do anything with this? Does this even need to be called to flush
// the queue?
this.events.ackReports$.firstToPromise();

// The Panel Test Mode command needs to be resent every second
this.testModeIntervalHandle = setInterval(() => {
this.events.output$.push(Uint8Array.of(API_COMMAND.SET_PANEL_TEST_MODE, mode));
Comment on lines +322 to +323
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think there's probably a better way to do this with the BaconJS interval to generate a stream of regular events that we can use for this. I'll add the UI for this first and worry about thinking through the better version of this interval later

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah interesting. Would just need a way to tell it to stop since it says indefinitely.

Copy link
Owner

@noahm noahm May 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I think you would chain the interval stream with a takeUntil that watches another event stream (maybe a bus we can manually emit from) for a stop signal.


// TODO: Do I need to call this to consume the event?
this.events.ackReports$.firstToPromise();
Comment on lines +325 to +326
Copy link
Owner

@noahm noahm May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to worry about consuming these. I think because they are acks to unattended commands we can just let them silently discarded in the background.

}, 1000);
}
}

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
Loading