Developer APP
Developer APP is a clean controller canvas for host-side software. When the device is running Developer APP, external software can treat a Mystrix as an input surface and LED canvas instead of as a standalone Matrix OS application.
The Developer APP protocol is still evolving during the beta.
Enter Developer APPβ
Developer APP is hidden from the normal application launcher. A host enters it through System Remote API:
- Open the device over the Matrix OS HID interface.
- Send System Remote API command
OPEN_DEVELOPER_APP(0x44) on the system report. - Wait briefly for Matrix OS to switch apps.
- Send Developer APP
PING(0x00) until it replies. - Enable input reports with
SET_INPUT_REPORT(0x01) if the host needs live input.
ENTER_APP_VIA_ID (0x18) can launch apps by ID, but OPEN_DEVELOPER_APP is the direct system command for this workflow.
HID Transportβ
WebHID clients use the Matrix OS HID collection:
- vendor ID:
0x0203 - usage page:
0xFF00 - usage:
0x01 - system report ID:
0xCB - Developer APP report ID:
0xFF - Developer APP report payload size: 63 bytes
System Remote API commands are sent on report 0xCB. Developer APP commands are sent on report 0xFF after Developer APP is active.
Developer APP HID report layout:
byte 0 command
byte 1..62 command payload, padded with zeros by the host report layer
Replies use the same report ID:
byte 0 reply command
byte 1 status, for ACK/error replies
byte 2..62 reply data
For normal command replies, reply command = command | 0x80. For asynchronous input reports, reply command = 0x90.
Minimal HID Flowβ
System report 0xCB:
send 0x44 # OPEN_DEVELOPER_APP
wait for 0xC4 ACK
Developer APP report 0xFF:
send 0x00 # PING
expect 0x80 0x00 protocol_version width height led_count_msb led_count_lsb
Developer APP report 0xFF:
send 0x01 0x01 0x01 # SET_INPUT_REPORT, key info, enabled
expect 0x81 0x00 # command | 0x80, OK
For HID, SET_INPUT_REPORT replies on command | 0x80, so command 0x01 replies as 0x81 0x00 on success. The same byte value 0x81 is also the SysEx error reply command. Always interpret replies in the context of the transport.
SysEx Transportβ
Developer APP also accepts MIDI SysEx messages. SysEx payload bytes must be 7-bit clean.
Developer APP SysEx uses:
manufacturer id: 00 02 03
family id: 4D 58
SysEx command framing:
F0 00 02 03 4D 58 command payload... F7
SysEx ACK framing:
F0 00 02 03 4D 58 80 command 00 data... F7
SysEx error framing:
F0 00 02 03 4D 58 81 command status F7
SysEx input event framing:
F0 00 02 03 4D 58 90 event... F7
HID is the recommended transport for new browser/desktop tools because it supports full 8-bit RGB and RGB565 payloads. SysEx uses 7-bit values, so color and index fields are encoded differently where noted.
Basic MIDI Behaviorβ
Developer APP also exposes a simple MIDI controller surface:
- MIDI Start (
0xFA) or Continue (0xFB) enables MIDI key reports. - Activated or pressed grid input sends MIDI Note On on channel
0; the note number is the LED index and velocity is reduced to 7-bit. - Released grid input sends MIDI Note Off on channel
0. - Aftertouch sends MIDI poly aftertouch on channel
0; pressure is reduced to 7-bit. - Incoming Note On writes the note-index LED. Channel
0uses grayscale velocity; channels1..15use channel-based hue. - Incoming Note Off, or Note On with velocity
0, clears the note-index LED. - Control Change
120on channel0clears all LEDs.
Use HID for full-fidelity host tools. Use MIDI behavior when the host integration should look like a conventional MIDI controller.
Common Typesβ
Indexβ
HID uses big-endian 16-bit indexes:
index_msb index_lsb
SysEx uses two 7-bit bytes:
index_low7 index_high7
Signed Coordinateβ
HID coordinates are signed 8-bit values.
SysEx coordinates use a signed 7-bit value:
0..63means0..6364..127means-64..-1
LED Flagsβ
LED write commands use a flags byte:
| Bits | Name | Meaning |
|---|---|---|
0x01 | LED_FLAG_CANVAS | Queue LED writes without immediate refresh. Use LED_CANVAS_UPDATE to show them. |
0x0E | color mode bits | (color_mode << 1) |
Color modes:
| Mode | Value | HID bytes | SysEx bytes |
|---|---|---|---|
| RGB24 | 0x00 | r g b | r7 g7 b7 |
| RGBW32 | 0x01 | r g b w | r7 g7 b7 w7 |
| RGB565 | 0x02 | rgb565_msb rgb565_lsb | not supported |
For SysEx RGB/RGBW, each color component is 7-bit and Matrix OS expands it to 8-bit internally.
LED Partitionβ
LED write commands include a numeric partition index. On current Mystrix layouts:
| Partition | Value | Notes |
|---|---|---|
| Grid | 0x00 | Main grid LEDs. |
| Underglow | 0x01 | Underglow LEDs when present. |
Tools should use PING to read device size and LED count before assuming a fixed layout.
LED Addressingβ
PING returns the logical grid width, logical grid height, and total led_count. led_count is the full LED buffer count, including grid LEDs and any underglow LEDs.
For current 8x8 Mystrix layouts:
| Partition | Start index | Size | Notes |
|---|---|---|---|
| Grid | 0 | 64 | Main 8x8 grid. |
| Underglow | 64 | 32 | Present on devices with underglow. |
XY coordinates use the Matrix OS logical orientation:
(0, 0)is the top-left grid LED.xincreases to the right.yincreases downward.- Underglow can be addressed with perimeter coordinates such as
x = -1,x = width,y = -1, ory = heightwhen the device has underglow. - For perimeter coordinates, keep the other coordinate on the grid edge range, for example
0..width - 1or0..height - 1.
Do not hard-code one grid index order for all devices. Grid index order is device-model-specific. Use LED_WRITE_XY for position-based UI, and use LED_WRITE_INDEX_RANGE only when the host has a model-aware LED index map.
HID LED Payload Capacityβ
The Developer APP HID report carries 62 payload bytes after the command byte. For LED write commands, that gives these maximum entries per HID report:
| Command | RGB24 | RGBW32 | RGB565 |
|---|---|---|---|
LED_WRITE_INDEX | 11 LEDs | 9 LEDs | 14 LEDs |
LED_WRITE_XY | 11 LEDs | 9 LEDs | 14 LEDs |
LED_WRITE_INDEX_RANGE | 19 LEDs | 14 LEDs | 28 LEDs |
For full-frame uploads, prefer LED_WRITE_INDEX_RANGE with LED_FLAG_CANVAS, split the frame into chunks, then send one LED_CANVAS_UPDATE.
Keypad Stateβ
Input reports and key reads return keypad state values:
| State | Value |
|---|---|
| Idle | 0x00 |
| Activated | 0x01 |
| Pressed | 0x02 |
| Hold | 0x03 |
| Aftertouch | 0x04 |
| Released | 0x05 |
| Debouncing | 0xF0 |
| Release Debouncing | 0xF1 |
State meanings:
- Idle: input is inactive.
- Activated: input crossed into active contact.
- Pressed: input is pressed.
- Hold: input reached the hold threshold.
- Aftertouch: pressure changed while the input is active.
- Released: input was released.
- Debouncing / Release Debouncing: device input-layer transitional states that may appear in
KEY_READ_*polling.
Pressure and velocity are 0..65535 on HID replies and 0..127 reduced values on SysEx replies.
Input Debounce And Repeatβ
Asynchronous input reports are event-facing reports from the Matrix OS input layer. They do not report raw scan data.
KEY_READ_INDEX and KEY_READ_XY poll the current input snapshot. Snapshot reads can return non-event states such as Idle, Debouncing, and Release Debouncing; host tools should treat states 0x01..0x05 as the event-like keypad states.
Developer APP does not generate host-facing auto-repeat events. If a host tool needs repeat behavior, implement repeat timing in the host after receiving a press event and before the release event.
Hold events are filtered out of asynchronous input reports. If a host tool needs hold behavior, the most reliable path is to time the interval between press and release on the host. Use KEY_READ_INDEX or KEY_READ_XY only when the host needs a best-effort current-state snapshot of one input.
Command Referenceβ
The request payloads below start after the command byte.
| Command | ID | Request payload | Success reply |
|---|---|---|---|
PING | 0x00 | none | ACK with protocol_version width height led_count |
SET_INPUT_REPORT | 0x01 | report_type enabled | ACK with no data |
EXIT_APP | 0x02 | none | ACK with no data, then app exits |
LED_WRITE_INDEX | 0x10 | flags partition count (index color)... | no success ACK |
LED_WRITE_XY | 0x11 | flags partition count (x y color)... | no success ACK |
LED_WRITE_INDEX_RANGE | 0x12 | flags partition start_index count color... | no success ACK |
LED_CANVAS_UPDATE | 0x20 | none | no success ACK |
LED_CLEAR_SCREEN | 0x21 | [flags] | no success ACK |
KEY_READ_INDEX | 0x30 | index | ACK with key info record |
KEY_READ_XY | 0x31 | x y | ACK with key info record |
Commands documented as "no success ACK" still return an error reply if the request is malformed. Host tools should not wait for a success reply for those commands.
PING (0x00)β
Checks that Developer APP is active and returns device geometry.
Request payload: none.
ACK data:
protocol_version width height led_count_msb led_count_lsb
ACK data in this section means bytes after the HID status byte, or bytes after the SysEx command 0x00 prefix.
For SysEx, led_count uses two 7-bit index bytes instead of HID big-endian bytes.
SET_INPUT_REPORT (0x01)β
Enables or disables asynchronous input reports.
Request payload:
report_type enabled
| Field | Values |
|---|---|
report_type | 0x00 all reports, 0x01 key info reports |
enabled | 0x00 disabled, any non-zero value enabled |
ACK data: none.
Input events are sent as REPLY_INPUT_EVENT (0x90) while reports are enabled.
report_type values 0x00 and 0x01 currently both enable the same key info event stream.
EXIT_APP (0x02)β
Exits Developer APP and returns control to Matrix OS.
Request payload: none.
ACK data: none.
LED_WRITE_INDEX (0x10)β
Writes one or more LEDs by LED index.
Request payload:
flags partition count
index color
index color
...
Each index is two bytes. Each color uses the selected color mode. The number of entries must match count.
When LED_FLAG_CANVAS is not set, Matrix OS updates the LED output immediately. When it is set, call LED_CANVAS_UPDATE after sending one or more LED write commands.
Successful LED write commands do not send ACK replies. Errors reply with a non-zero status reply.
LED_WRITE_XY (0x11)β
Writes one or more LEDs by coordinate.
Request payload:
flags partition count
x y color
x y color
...
Each x and y is a signed coordinate. Each color uses the selected color mode. The number of entries must match count.
Successful LED write commands do not send ACK replies. Errors reply with a non-zero status reply.
LED_WRITE_INDEX_RANGE (0x12)β
Writes a contiguous LED range.
Request payload:
flags partition start_index count color color ...
start_index is two bytes. The payload contains count colors using the selected color mode.
For HID RGB565 mirror uploads, a typical payload starts with:
(LED_FLAG_CANVAS | (RGB565 << 1)) partition start_msb start_lsb count rgb565...
Successful LED write commands do not send ACK replies. Errors reply with a non-zero status reply.
LED_CANVAS_UPDATE (0x20)β
Refreshes LEDs after queued canvas writes.
Request payload: none.
Successful canvas updates do not send ACK replies. Errors reply with a non-zero status reply.
LED_CLEAR_SCREEN (0x21)β
Clears all LEDs to black.
Request payload:
[flags]
flags is optional and is specific to LED_CLEAR_SCREEN. It does not use the LED write flag layout above.
| Bit | Meaning |
|---|---|
0x01 | LED_CLEAR_FLAG_REFRESH: refresh LEDs immediately after clearing. |
When 0x01 is not set, the clear is queued and the host should send LED_CANVAS_UPDATE when it is ready to show the cleared frame.
0x21 # clear, queued
0x21 0x01 # clear and refresh immediately
Successful clear commands do not send ACK replies. Errors reply with a non-zero status reply.
KEY_READ_INDEX (0x30)β
Reads keypad state by LED index.
Request payload:
index
ACK data is a key info record. If the index cannot map to keypad input, the command returns bad payload.
KEY_READ_XY (0x31)β
Reads keypad state by coordinate.
Request payload:
x y
ACK data is a key info record. If the coordinate cannot map to keypad input, the command returns bad payload.
Key Info Recordβ
KEY_READ_* replies and asynchronous input reports use the same key info record.
HID layout:
event_type cluster_id member_msb member_lsb x y state pressure_msb pressure_lsb velocity_msb velocity_lsb
SysEx layout:
event_type cluster_id member_low7 member_high7 x7 y7 state pressure7 velocity7
Fields:
| Field | Meaning |
|---|---|
event_type | 0x01 for key info. |
cluster_id | Input cluster. Function key is cluster 0x00; primary grid is usually 0x01. |
member | Input member inside the cluster. |
x, y | Coordinate. Function key reports (0, 0). |
state | Keypad state value. |
pressure | Pressure value. HID is 16-bit; SysEx is 7-bit reduced. |
velocity | Velocity value. HID is 16-bit; SysEx is 7-bit reduced. |
Developer APP currently reports keypad-class input for the function key and primary grid.
Use KEY_READ_INDEX or KEY_READ_XY when a host needs the current state of one input. Use SET_INPUT_REPORT for live input events.
Replies And Errorsβ
HID reply commands:
| Reply | Value | Payload |
|---|---|---|
| ACK | `command | 0x80` |
| Error | `command | 0x80` |
| Input event | 0x90 | key info record |
SysEx reply commands:
| Reply | Value | Payload |
|---|---|---|
| ACK | 0x80 | command 0x00 data... |
| Error | 0x81 | command status |
| Input event | 0x90 | key info record |
Status/error codes:
| Status | Value | Meaning |
|---|---|---|
| OK | 0x00 | Command accepted. |
| Bad length | 0x02 | Payload length is wrong or incomplete. |
| Bad payload | 0x03 | Payload values are invalid for the current device/state. |
| Unknown command | 0x7F | Command ID is unsupported by Developer APP. |
Some high-throughput LED commands skip ACK on success. Treat lack of an ACK as success only for commands documented above as skipping ACK, and still watch for non-zero status replies.
Host Implementation Notesβ
- Keep at most one ACK-awaited command outstanding per report ID unless your host stack has a reply router.
- Route
0x90input reports separately from command replies. - For commands that skip success ACK, continue listening for error replies for a short timeout window.
- HID report ordering is the host's transport ordering, but Developer APP processes up to four HID reports per app loop. Chunk large LED frames and avoid unbounded bursts.
- A typical command timeout is
500..1000 ms. After entering Developer APP, pollPINGbecause app switching is asynchronous. - For LED mirroring, queue chunks with
LED_FLAG_CANVAS, then sendLED_CANVAS_UPDATEonce per completed frame.
Minimal WebHID Exampleβ
This example enters Developer APP, enables key info reports, and writes one red LED. Production tools should keep a pending-reply map and route 0x90 input events separately.
const VENDOR_ID = 0x0203
const USAGE_PAGE = 0xff00
const USAGE = 0x01
const SYSTEM_REPORT_ID = 0xcb
const APP_REPORT_ID = 0xff
function makeReport(...bytes) {
const report = new Uint8Array(63)
report.set(bytes)
return report
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
function waitForReport(device, reportId, match, timeoutMs = 1000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
cleanup()
reject(new Error("Timed out waiting for report"))
}, timeoutMs)
function cleanup() {
clearTimeout(timer)
device.removeEventListener("inputreport", onInputReport)
}
function onInputReport(event) {
if (event.reportId !== reportId) return
const data = new Uint8Array(
event.data.buffer,
event.data.byteOffset,
event.data.byteLength,
)
if (!match(data)) return
cleanup()
resolve(data)
}
device.addEventListener("inputreport", onInputReport)
})
}
async function sendAndWait(device, reportId, bytes, match, timeoutMs) {
const reply = waitForReport(device, reportId, match, timeoutMs)
await device.sendReport(reportId, makeReport(...bytes))
return reply
}
const [device] = await navigator.hid.requestDevice({
filters: [{ vendorId: VENDOR_ID, usagePage: USAGE_PAGE, usage: USAGE }],
})
if (!device) {
throw new Error("No Matrix OS HID device selected")
}
await device.open()
// Enter Developer APP through System Remote API.
await sendAndWait(
device,
SYSTEM_REPORT_ID,
[0x44],
(data) => data[0] === 0xc4,
)
await delay(250)
// Wait for Developer APP to respond.
let pingReply = null
for (let attempt = 0; attempt < 15 && pingReply === null; attempt++) {
try {
pingReply = await sendAndWait(
device,
APP_REPORT_ID,
[0x00],
(data) => data[0] === 0x80 && data[1] === 0x00,
250,
)
} catch {
await delay(150)
}
}
if (pingReply === null) {
throw new Error("Developer APP did not respond")
}
// Enable key info input events.
await sendAndWait(
device,
APP_REPORT_ID,
[0x01, 0x01, 0x01],
(data) => data[0] === 0x81 && data[1] === 0x00,
)
// Queue LED 0 as red using RGB565, then update the canvas.
await device.sendReport(
APP_REPORT_ID,
makeReport(0x12, 0x01 | (0x02 << 1), 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00),
)
await device.sendReport(APP_REPORT_ID, makeReport(0x20))
Comments