Skip to content

3.2 Configuration

Fernando Chorney edited this page Apr 21, 2024 · 14 revisions

StepManiaX Stage Configuration

This is probably one of the main things you would be interested in if you are interacting with the stages with the SDK. This command will let you request the stage config, as well as set it with any changes you have made. This section will explain how to read/write the config as well as go in depth as to what every configuration value means.

Old vs New Config?

Any SMX Stage with a firmware older than version 5 seems to use an old format for configuring the stages. We will look at the differences between these two config versions and how to convert from one to the other later on this page. For now, we will assume we're talking about the new config.

Requesting and sending config data is the same for both old and new configs, with the only difference being the specific command character/value that is sent, and the length of the data.

Read Config

Request Packet

To request the config data you will send the "G" command (or "g" command for the old config).

Command: "G" (71, 0x47), or "g" (103, 0x67)
Arguments: None
Packet Count: 1
Packet: [5, 5, 1, 71, 0, ..., 0]

Response Packet

As with many other commands, the response packet's first data byte will be the same command you sent. As you can see below, for some reason it will return the command by itself in a single packet, and then the size of the data in another packet, and then the actual data in the remaining packets. This example uses my own personal stages, so the data you receive will of course reflect your stages settings.

Command: "G" (71, 0x47), or "g" (103, 0x67)
Data: Config
Packet Count: 8
Packet   1: [6, 4,  1,  71, 0, ..., 0] // Returns the Command ("G" or "g")
Packet   2: [6, 0,  1, 250, 0, ..., 0] // The size of the incoming data (This is always 250 from what I can tell)
Packet 3-7: [6, 0, 61, data]           // All the data we care about
Packet   8: [6, 1,  1,  10, 0, ..., 0] // Looks like this sends "\n" for the last byte. This can safely be ignored

Decoding The Data

Once you remove the preamble (command, length), and ignore the new line at the end, you end up with the following 250 bytes. These are grouped by useful chunks

[
    // masterVersion
    5,
    // configVersion
    5,
    // flags
    3,
    // debounceNodelayMilliseconds
    15, 0,
    // debounceDelayMilliseconds
    0, 0,
    // panelDebounceMicroseconds
    160, 15,
    // autoCalibrationMaxDeviation
    100,
    // badSensorMinimumDelaySeconds
    15,
    // autoCalibrationAveragesPerUpdate
    44, 1,
    // autoCalibrationSamplesPerAverage
    100, 0,
    // autoCalibrationMaxTare
    255, 255,
    // enabledSensors[5]
    15, 15, 15, 15, 0,
    // autoLightsTimeout
    7,
    //stepColor[3 * 9]
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170, 
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    // platformStripColor[3]
    0, 72, 143,
    // autoLightPanelMask
    170, 0,
    // panelRotation
    0,
    // panelSettings[9]
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    // preDetailsDelayMilliseconds
    5,
    // padding[49]
    255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255,
    255, 255, 255, 255, 255, 255, 255,
]

Config C-Struct

Before we dive into how we break this data apart, let's look at the c-structs/enums that are defined to hold this data in the source SDK. This can give you a quick birds eye view of how the structure is ordered. (Comments have been removed from this source copy/paste, so that we can go through them later on this page.

enum SMXConfigFlags {
    PlatformFlags_AutoLightingUsePressedAnimations = 1 << 0,
    PlatformFlags_FSR = 1 << 1,
};

struct packed_sensor_settings_t {
    uint8_t loadCellLowThreshold;
    uint8_t loadCellHighThreshold;
    uint8_t fsrLowThreshold[4];
    uint8_t fsrHighThreshold[4];
    uint16_t combinedLowThreshold;
    uint16_t combinedHighThreshold;
    uint16_t reserved;
};

struct SMXConfig {
    uint8_t masterVersion = 0xFF;
    uint8_t configVersion = 0x05;
    uint8_t flags = 0;
    uint16_t debounceNodelayMilliseconds = 0;
    uint16_t debounceDelayMilliseconds = 0;
    uint16_t panelDebounceMicroseconds = 4000;
    uint8_t autoCalibrationMaxDeviation = 100;
    uint8_t badSensorMinimumDelaySeconds = 15;
    uint16_t autoCalibrationAveragesPerUpdate = 60;
    uint16_t autoCalibrationSamplesPerAverage = 500;
    uint16_t autoCalibrationMaxTare = 0xFFFF;
    uint8_t enabledSensors[5];
    uint8_t autoLightsTimeout = 1000/128;
    uint8_t stepColor[3*9];
    uint8_t platformStripColor[3];
    uint16_t autoLightPanelMask = 0xFFFF;
    uint8_t panelRotation;
    packed_sensor_settings_t panelSettings[9];
    uint8_t preDetailsDelayMilliseconds = 5;
    uint8_t padding[49];
};

Decoding The Data (for real this time) and What it Means

Lets take the c-struct, and the data defined above, and explain it all here:

masterVersion

Source Comments:

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, and
we'll always write 0xFF here so it doesn't change on that firmware version.

We don't need this since we can read the "I" command which also reports the version, but
this allows panels to also know the master version.

Data Interpretation:

Default: 0xFF (255)
Size: 1 Unsigned 8-Bit Byte
Bytes: [5]
Value: 5

configVersion

Source Comments:

The version of this config packet. This can be used by the firmware to know which values
have been filled in. Any values not filled in will always be 0xFF, which can be tested
for, but that doesn't work for values where 0xFF is a valid value. This value is unrelated
to the firmware version, and just indicates which fields in this packet have been set. 

Note that we don't need to increase this any time we add a field, only when it's important
that we be able to tell if a field is set or not.

Versions:
- 0xFF: This is a config packet from before configVersion as added.
- 0x00: configVersion added.
- 0x02: panelThreshold0Low through panelThreshold8High added.
- 0x03: debounceDelayMs added.

Data Interpretation:

Default: 0xFF (255)
Size: 1 Unsigned 8-Bit Byte
Bytes: [5]
Value: 5

flags

Source Comments:

Packed flags (masterVersion >= 4).

Data Interpretation:

Default: 0x00 (0)
Size: 1 Unsigned 8-Bit Byte
Bytes: [3]
Value: 3

This flag value actually maps to the SMXConfigFlags enum defined above.

0x3 = 00000011
Bit 0 (right most bit) = PlatformFlags_AutoLightingUsePressedAnimations
Bit 1 (second bit from the right) = PlatformFlags_FSR
PlatformFlags_AutoLightingUsePressedAnimations

If set, panels will use the pressed animation when pressed, and stepColor is ignored.
If unset, panels will be lit solid using stepColor.
This flag is only used when masterVersion >= 4. Previous versions always use stepColor.

PlatformFlags_FSR

If set, panels are using FSRs, otherwise load cells.

debounceNodelayMilliseconds

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 0x0000 (0)
Size: 1 Unsigned 16-Bit Byte
Bytes: [15, 0]
Bytes (Little Endian): [0, 15]
Value: 15

debounceDelayMilliseconds

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 0x0000 (0)
Size: 1 Unsigned 16-Bit Byte
Bytes: [0, 0]
Value: 0

panelDebounceMicroseconds

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 0x0FA0 (4000)
Size: 1 Unsigned 16-Bit Byte
Bytes: [160, 15]
Bytes (Little Endian): [15, 160]
Value: 4000

autoCalibrationMaxDeviation

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 0x64 (100)
Size: 1 Unsigned 8-Bit Byte
Bytes: [100]
Value: 100

badSensorMinimumDelaySeconds

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 0x0F (15)
Size: 1 Unsigned 8-Bit Byte
Bytes: [15]
Value: 15

autoCalibrationAveragesPerUpdate

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 0x003C (60)
Size: 1 Unsigned 16-Bit Byte
Bytes: [44, 1]
Bytes (Little Endian): [1, 44]
Value: 300

autoCalibrationSamplesPerAverage

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 01F4 (500)
Size: 1 Unsigned 16-Bit Byte
Bytes: [100, 0]
Bytes (Little Endian): [0, 100]
Value: 100

autoCalibrationMaxTare

Source Comments:

The maximum tare value to calibrate to (except on startup).

Data Interpretation:

Default: 0xFFFF (65535)
Size: 1 Unsigned 16-Bit Byte
Bytes: [255, 255]
Bytes (Little Endian): [255, 255]
Value: 65535

enabledSensors

Source Comments:

Which sensors on each panel to enable. This can be used to disable sensors that we know aren't populated.
This is packed, with four sensors on two panels per byte:
enabledSensors[0[ & 1 is the first sensor on the first panel, and so on.

Data Interpretation:

Default: 0x00 (0) [Note: This might default to 0xFF? I'm not actually sure]
Size: 5 Unsigned 8-Bit Bytes
Bytes: [15, 15, 15, 15, 0]
Values: [15, 15, 15, 15, 0]

Decoding Data:

As mentioned in the source comments, this is packed so that we are storing 9 panels x 4 sensors worth of data in 5 bytes.

Let's first note that panels are ordered from left to right, top to bottom such that panel 0 is Up Left, panel 3 is Left, and panel 8 is Down Right.
We can also note that sensors are ordered in the following way: Left, Right, Up, and Down.

Lets see the 5 bytes as binary data:

00001111 00001111 00001111 00001111 00000000

Now break them down into nibbles (4-bits):

 0000   1111   0000   1111   0000   1111   0000   1111   0000   0000
|____| |____| |____| |____| |____| |____| |____| |____| |____| |____|
|      |      |      |      |      |      |      |      |      |
|      |      |      |      |      |      |      |      |      |______ 9: This is always 0 as there are only 9 panels
|      |      |      |      |      |      |      |      |_____________ 8: Panel 8 (Down Right) has 0 sensors enabled
|      |      |      |      |      |      |      |____________________ 7: Panel 7 (Down) has all 4 sensors enabled
|      |      |      |      |      |      |___________________________ 6: Panel 6 (Down Left) has 0 sensors enabled
|      |      |      |      |      |__________________________________ 5: Panel 5 (Right) has all 4 sensors enabled
|      |      |      |      |_________________________________________ 4: Panel 4 (Center) has 0 sensors enabled
|      |      |      |________________________________________________ 3: Panel 3 (Left) has all 4 sensors enabled
|      |      |_______________________________________________________ 2: Panel 2 (Up Right) has 0 sensors enabled
|      |______________________________________________________________ 1: Panel 1 (Up) has all 4 sensors enabled
|_____________________________________________________________________ 0: Panel 0 (Up Left) has 0 sensors enabled

We can see what it might look like to turn off 2 sensors:

0101
||||__ Sensor Down is Enabled
|||___ Sensor Up is Disabled
||____ Sensor Right is Enabled
|_____ Sensor Left is Disabled

⚠️ Warning: Turning off individual sensors ⚠️

The original config source has a comment stating that it is not recommended to turn off individual sensors, due to how the panel PCBs work. So ideally you should only ever see a fully activated or fully disabled nibble per panel.

autoLightsTimeout

Source Comments:

How long the master controller will wait for a lights command before assuming the game has gone away and resume auto-lights.
This is defined in 128ms units. 

Data Interpretation:

Default: 0x07 (7) [This default is the result of integer division for 1000/128 which is 896 milliseconds]
Size: 1 Unsigned 8-Bit Byte
Bytes: [7]
Value: 7

stepColor

Source Comments:

The color to use for each panel when auto-lighting in master mode.
This doesn't apply when the pads are in autonomous lighting mode (no master), since they don't
store any configuration by themselves.

These colors should be scaled to the 0-170 range.

Data Interpretation:

Default: 0x00 (0) [Note: This might default to 0xFF? I'm not actually sure]
Size: 9 groups of 3 Unsigned 8-Bit Bytes
Bytes: [
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
]
Values: [
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
    170, 170, 170,
]

Decoding Data:

As with similar fields, this is essentially just RGB values for all 9 panels. The order is in fact Red, Green, Blue but the values are scaled between 0-170 instead of the typical 0-255. When reading/writing these values, please take the scaling into consideration.

platformStripColor

Note: The comments refer to the platform LED strip. This is referring to the under glow LEDs on the stage itself.

Source Comments:

The default color to set the platform LED strip to.

Data Interpretation:

Default: 0x00 (0) [Note: This might default to 0xFF? I'm not actually sure]
Size: 3 Unsigned 8-Bit Bytes
Bytes: [0, 72, 143]
Values: [0, 72, 143]

Decoding Data:

Similar to the stepColor fields, these 3 bytes are just Red, Green, and Blue.

⚠️ I'm not sure if these also scale to 0-170 or are 0-255.

autoLightPanelMask

Source Comments:

Which panels to enable auto-lighting for. Disabled panels will be unlit.
0x01 = panel 0, 0x02 = panel 1, 0x04 = panel 2, etc.
This only affects the master controller's built-in auto lighting and not lights data sent from the SDK.

Data Interpretation:

Default: 0xFFFF (65535)
Size: 1 Unsigned 16-Bit Byte
Bytes: [170, 0]
Bytes (Little Endian): [0, 170]
Value: 170

Decoding Data:

⚠️ This section needs to be confirmed. The source comments imply that panel 0 (up left) is held in the LSB, but typically it's held in the MSB. For now, let's assume the comments are correct. ⚠️

Given the value 170, let's take a look at the binary.

0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0
|___________| | | | | | | | | |_ 170 & 0x0001: Panel 0 (Up Left) is disabled
|             | | | | | | | |___ 170 & 0x0002: Panel 1 (Up) is enabled
|             | | | | | | |_____ 170 & 0x0004: Panel 2 (Up Right) is disabled
|             | | | | | |_______ 170 & 0x0008: Panel 3 (Left) is enabled
|             | | | | |_________ 170 & 0x0010: Panel 4 (Center) is disabled
|             | | | |___________ 170 & 0x0020: Panel 5 (Right) is enabled
|             | | |_____________ 170 & 0x0040: Panel 6 (Down Left) is disabled
|             | |_______________ 170 & 0x0080: Panel 7 (Down) is enabled
|             |_________________ 170 & 0x0100: Panel 8 (Down Right) is disabled
|_______________________________ These bits are never used

panelRotation

Source Comments:

The rotation of the panel, where 0 is the standard rotation, 1 means the panel is rotated
right 90 degrees, 2 is rotated 180 degrees, and 3 is rotated 270 degrees.

This value is unused.

Data Interpretation:

Default: 0x00 (0)
Size: 1 Unsigned 8-Bit Byte
Bytes: [0]
Value: 0

panelSettings

Source Comments:

Per-panel sensor settings.

Data Interpretation:

Default: 0x00 (0) [Note: This might default to 0xFF? I'm not actually sure]
Size: 16 Unsigned 8-Bit Bytes (10 8-bit bytes, 3 16-bit bytes)
Bytes: [
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
]
Bytes (Little Endian): [
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 255, 255, 255, 255, 0, 0,
]
Values: [
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
    33, 42, 235, 235, 235, 235, 238, 238, 238, 238, 65535, 65535, 0,
]

Decoding Data:

See Decoding packed_sensor_settings_t

preDetailsDelayMilliseconds

⚠️ It is unknown what this tunable actually does. Any information to help fill this out would be appreciated.

Source Comments:

This is an internal tunable that should be left unchanged.

Data Interpretation:

Default: 0x05 (5)
Size: 1 Unsigned 8-Bit Byte
Bytes: [5]
Value: 5

padding

Source Comments:

Pad the struct to 250 bytes. This keeps this struct size from changing as we add fields,
so the Application Binary Interface (ABI) doesn't change. Applications should leave any data
in here unchanged when calling SMX_SetConfig.

Data Interpretation:

Default: 0xFF (255)
Size: 49 Unsigned 8-Bit Bytes

Decoding packed_sensor_settings_t

Since all 9 panels are configured the exact same in my example, lets pull one row and look at how it's structured.

// loadCellLowThreshold
33,
// loadCellHighThreshold
42,
// fsrLowThreshold[4]
235, 235, 235, 235,
// fsrHighThreshold[4]
238, 238, 238, 238,
// combinedLowThreshold
255, 255,
// combinedHighThreshold
255, 255,
// reserved
0, 0,

loadCellLowThreshold

Load cells only get a low and high threshold value for all 4 load cells. These can't be modified individually.

Source Comments:

Load cell threshold

Data Interpretation:

Default: 0x00 (0) [or 0xFF (255)?]
Size: 1 Unsigned 8-Bit Byte
Bytes: [33]
Value: 33

loadCellHighThreshold

Load cells only get a low and high threshold value for all 4 load cells. These can't be modified individually.

Source Comments:

Load cell threshold

Data Interpretation:

Default: 0x00 (0) [or 0xFF (255)?]
Size: 1 Unsigned 8-Bit Byte
Bytes: [42]
Value: 42

fsrLowThreshold

Source Comments:

FSR threshold

Data Interpretation:

Default: 0x00 (0) [or 0xFF (255)?]
Size: 4 Unsigned 8-Bit Bytes
Bytes: [235, 235, 235, 235]
Values: [235, 235, 235, 235]

Decoding Data:

As with other sensor based values, this contains 1 byte for each sensor in the following order: Left, Right, Up, Down

[
    235, // Left Sensor
    235, // Right Sensor
    235, // Up Sensor
    235, // Down Sensor
]

fsrHighThreshold

Source Comments:

FSR threshold

Data Interpretation:

Default: 0x00 (0) [or 0xFF (255)?]
Size: 4 Unsigned 8-Bit Bytes
Bytes: [235, 235, 235, 235]
Values: [235, 235, 235, 235]

Decoding Data:

As with other sensor based values, this contains 1 byte for each sensor in the following order: Left, Right, Up, Down

[
    235, // Left Sensor
    235, // Right Sensor
    235, // Up Sensor
    235, // Down Sensor
]

combinedLowThreshold

⚠️ There is no documentation on this value, so I am unsure what it is used for.

Source Comments: None

Data Interpretation:

Default: 0x0000 (0) [or 0xFFFF (65535)?]
Size: 1 Unsigned 16-Bit Byte
Bytes: [255, 255]
Bytes (Little Endian): [255, 255]
Value: 65535

combinedHighThreshold

⚠️ There is no documentation on this value, so I am unsure what it is used for.

Source Comments: None

Data Interpretation:

Default: 0x0000 (0) [or 0xFFFF (65535)?]
Size: 1 Unsigned 16-Bit Byte
Bytes: [255, 255]
Bytes (Little Endian): [255, 255]
Value: 65535

reserved

⚠️ There is no documentation on this value, so I am unsure what it is used for.

Source Comments:

This must be left unchanged

Data Interpretation:

Default: 0x0000 (0) [or 0xFFFF (65535)?]
Size: 1 Unsigned 16-Bit Byte
Bytes: [0, 0]
Bytes (Little Endian): [0, 0]
Value: 0

Write Config

To write the config data back to the stage, you will send the "W" command (or "w" command for the old config).

You need to prepend the

Command: "W" (87, 0x57), or "w" (119, 0x77)
Data: Config
Packet Count: 5
Packet   1: [5, 4, 61, 87, 250, data] // Report ID, Start of Command, Length of Data within Packet, Write Command, Size of Config Data, data
Packet 2-4: [5, 0, 61, data]          // More data
Packet   5: [5, 1, 8, data]           // Report ID, End of Command, the last data bytes

Considerations

There are 2 things to note here:

  1. You need to prepend your data with the total config data size (in bytes) that will be sent. Typically this is always 250, but as we will see further down, this might be smaller for older configs.
  2. You will need to encode your config data back in to the same structure that you read it in as. Remember that this must be encoded in Little Endian.

Old Config

The config as defined above is used for firmware_version 5 and above. For any earlier firmwares, we use the old config as defined here:

struct OldSMXConfig {
    uint8_t unused1 = 0xFF, unused2 = 0xFF;
    uint8_t unused3 = 0xFF, unused4 = 0xFF;
    uint8_t unused5 = 0xFF, unused6 = 0xFF;
    uint16_t masterDebounceMilliseconds = 0;
    uint8_t panelThreshold7Low = 0xFF, panelThreshold7High = 0xFF; // was "cardinal"
    uint8_t panelThreshold4Low = 0xFF, panelThreshold4High = 0xFF; // was "center"
    uint8_t panelThreshold2Low = 0xFF, panelThreshold2High = 0xFF; // was "corner"
    uint16_t panelDebounceMicroseconds = 4000;
    uint16_t autoCalibrationPeriodMilliseconds = 1000;
    uint8_t autoCalibrationMaxDeviation = 100;
    uint8_t badSensorMinimumDelaySeconds = 15;
    uint16_t autoCalibrationAveragesPerUpdate = 60;
    uint8_t unused7 = 0xFF, unused8 = 0xFF;
    uint8_t panelThreshold1Low = 0xFF, panelThreshold1High = 0xFF; // was "up"
    uint8_t enabledSensors[5];
    uint8_t autoLightsTimeout = 1000/128; // 1 second
    uint8_t stepColor[3*9];
    uint8_t panelRotation;
    uint16_t autoCalibrationSamplesPerAverage = 500;
    uint8_t masterVersion = 0xFF;
    uint8_t configVersion = 0x03;
    uint8_t unused9[10];
    uint8_t panelThreshold0Low, panelThreshold0High;
    uint8_t panelThreshold3Low, panelThreshold3High;
    uint8_t panelThreshold5Low, panelThreshold5High;
    uint8_t panelThreshold6Low, panelThreshold6High;
    uint8_t panelThreshold8Low, panelThreshold8High;
    uint16_t debounceDelayMilliseconds = 0;
    uint8_t padding[164];
};

It seems to be that this old config was used before FSR sensors were introduced, so you will most likely only see this on Load Cell stages.

The way this config seems to be handled is that when read in, it is converted to the new config so that the software can deal with the same config regardless of firmware version. It is then converted back into the old config when written back to the device.

Converting between config types

The way this seems to work in the original source, and similarly in the smx-config-web source is that you would initialize a blank new config object, and copy over certain values from the old config into the new one. You can look at the code to convert the old config to the new one to understand what values go where. The code to convert the new config back to the old config, is essentially the reverse of that.

Differences between config versions

As seen in the code linked above, there are some values that don't exist in earlier versions of the stage configs.

Config version less than 3 doesn't contain the debounceDelayMilliseconds value.

Config version less than 2 doesn't contain the values for the Up Left, Left, Right, Down Left, and Down Right panels as these would be grouped before this version.

Config version 0xFF means that the config you are looking at is from the first version where masterVersion nor configVersion existed.

Based on these versions we don't bother trying to read specific data from the config if we know it doesn't exist.

Write Config

As mentioned in the considerations above for writing the new config, we must make sure to take into account how large the old config is. For earlier versions, you will find that the config data is only 128 bytes (or lower) rather than 250. When reading in the config, you must keep account of how many bytes the config data was, so that you can send back the same number of bytes and have that value as the first value in your data.

The only caveat to this is that the original source seems to pad the data to 128 bytes at a minimum, so be careful of that.