Command map¶
Every known command, by category, with its params and reply in this site's byte convention (params and replies always start with the flag or status byte), what has been measured, what is only named in vendor software, and what must not be sent. The one thing to know first: every write on this protocol takes effect immediately and persists; there is no commit and no undo, and several commands drop bonds or wipe stored data.
At a glance
- Ten categories:
30REMAP,beBLE,caIC CHARGER,deMODULE,edLED,eeRESET,f1FIRMWARE,faSPI FLASH,feSYSTEM,ffMETA (host-side only). - REMAP (keymap, layer list, LED maps, module configs) is answered by the left half only.
edparams are00 <target> <values>; short params are zero-filled, so a shorted/1013writes a brightness ceiling of 0.- Several published reply bytes are checksums read as data; see the framing trap.
- The never-send list is at the end of this page.
MEASURED on this page means measured on the owner's board (one Create) unless a source is named; firmware and date follow the tag. Firmware is 3.41.0 unless a row says otherwise.
How to read the tables¶
There are ten categories (frame byte 4): 30 REMAP, be BLE, ca IC CHARGER, de
MODULE, ed LED, ee RESET, f1 FIRMWARE, fa SPI FLASH, fe SYSTEM, ff META. The names come
from nayactl's constants and from NayaCore's log classes ([IC CHARGER 0x, [FIRMWARE 0x, SYSTEM,
POWER, BLE, SPIFLASH, LED, MODULES, REMAP); NayaCore's own per-category command tables list the
ids in each category except ca, f1 and ff. STATIC167
Command ids are numbered per category and collide across categories: 10 0a is WRITE
MODULE CONFIG LIST in REMAP, SET ACTIVITY TIMEOUTS in SYSTEM, MODULE FILE FW VERSION in MODULE,
CLEAR BLE PROFILE in BLE and LEDs GREEN in LED. Always name a command with its category (30/100a,
fe/100a). STATIC1
The names used here are the vendor's: NayaCore's log strings, as carried by nayactl, whose reverse engineering matches NayaCore's worker names one to one. Where the measured meaning differs from the name, both are given (see names versus meaning). STATIC16
Columns: Params and Reply follow the byte convention; <layer>, <slot>, <target>
stand for one byte; "named only" means no source we hold sends it and nobody we know of has
measured it.
30 REMAP¶
Odd REMAP ids are reads and the next even id is the paired write; 10ca is CLEAR ALL
DATA. The whole category is answered by the left half only, and NayaFlow sends every REMAP frame
to 50. NayaCore's REMAP table holds exactly 1001 to 100e and 10ca, but NayaCore never
builds the macro ids 1005 to 1008.
MEASURED 3.41.0, 2026-09-01 STATIC7
REMAP writes are live
30/1002, 30/1004, 30/1006, 30/1008, 30/100a, 30/100c and 30/100e change the
keyboard at once and persist. Read and save the layer list, every layer, the LED maps and the
module configs first. 30/10ca is on the never-send list.
| Command | Vendor name | Params | Reply | Evidence |
|---|---|---|---|---|
30/1001 |
READ LAYER LIST | 00 00 |
00, the index echo 00, then one 20-byte entry per layer [idx][id][animation][10][<uuid16>]; 62 reply bytes and a 72-byte frame for three layers |
MEASURED 3.41.0, 2026-09-01 |
30/1002 |
WRITE LAYER LIST | 00 00 + only the entries added, replaced or deleted (delete = [idx] 00 00 00) |
00 00 |
MEASURED NayaFlow 1.25.1, 2026-09-01 and 2026-09-03 |
30/1003 |
READ LAYER DATA | 00 <layer>, then 01 <layer> per further chunk |
chunks starting with the layer echo; records on Keymap | MEASURED 3.41.0 |
30/1004 |
WRITE LAYER DATA | 00 <layer> + any number of records [position][type][len][param], sparse or full |
00 <layer> (01 <layer> on non-final chunks) |
MEASURED 3.41.0, 2026-09-01 |
30/1005 to 30/1008 |
READ / WRITE MACRO LIST, READ / WRITE MACRO DATA | see below | see below | MEASURED STATIC |
30/1009 |
READ MODULE CONFIG LIST | 00 00 |
00, a leading byte, a slot-0 entry 00 00 80 05 + five bytes, then one 20-byte entry per stored profile [slot][list id][type][10][<uuid16>] |
MEASURED 3.41.0 STATIC7 |
30/100a |
WRITE MODULE CONFIG LIST | 00 00 + changed entries: add 00 00 05 05 01 10 <uuid16> (slot 5, a Track), delete 00 00 05 00 00 00 |
00 00 |
MEASURED NayaFlow 1.25.1, 2026-09-03 |
30/100b |
READ MODULE CONFIG DATA | 00 <slot>, then 01 <slot> |
chunked like layer data | MEASURED 3.41.0 |
30/100c |
WRITE MODULE CONFIG DATA | 00 <slot> + field records [field][type][len][value], sparse (NayaFlow sends single-field writes such as 00 01 0b 07 00) or full |
00 <slot> |
MEASURED 3.41.0, 2026-09-02 (written by us and captured from NayaFlow) |
30/100d |
READ LED MAP DATA | 00 <layer>, then 01 <layer> |
136 four-byte entries per layer (544 record bytes in 241 + 241 + 62); status 16 <layer> when a layer has no stored map |
MEASURED 3.41.0; status 16 on 3.28.7, 2026-09-19 |
30/100e |
WRITE LED MAP DATA | 00 <layer> + entries [led][hue lo][hue hi][sat], sparse (00 00 22 f0 00 64) or the full map in three chunked frames |
00 <layer> |
MEASURED 3.41.0, 2026-09-01 |
30/10ca |
CLEAR ALL DATA | NayaCore: 00 00 (naya-create-kb sent 01) |
naya-create-kb: 00 80 |
STATIC (NayaCore's params)7 REPORTED (params 01, the reply, the effect)14 |
Notes on the REMAP rows:
30/1001is the first REMAP read NayaCore sends; it reads the layer list (decode on Layers). The byte after the status is the index echo every REMAP read carries. NayaCore's request isaa 00 50 00 30 04 10 01 00 00 11 04, and three layers come back in a 72-byte frame. MEASURED 3.41.0, 2026-09-0130/1004: the byte after the flag is always the layer, so a write sent without it has its first position read as a layer number INFERRED. For reads of layers that do not exist our board answers status19and the index (MEASURED 3.41.0, 2026-09-01). Captured NayaFlow write for layer 1: params00 01 24 01 04 04 00 07 00 34 01 04 1e 00 07 00, ack00 01.- Macros: on 3.41.0 the macro list read answers without ever returning an entry (the
one read in our captures, sent with params
00only, answered status11with data00, 2026-09-01); the macro data read echoes the index with no body; seven write encodings were all acked and nothing was kept (2026-09-07). On 3.28.7 the macro ids answer status11(not implemented, 2026-09-19). NayaCore never builds them (its REMAP switch has no case for1005to1008) and flashes a macro binding as07 00(NONE). MEASURED STATIC7 30/1009: the 16 bytes of each profile entry are NayaFlow's profile ids. The slot-0 entry00 00 80 05 00 00 00 00 00read the same in five NayaFlow reads on our board (2026-09-01 to 2026-09-17); type80is NayaCore's 40-field template type. The byte before it read00in NayaFlow's reads. Decode on Modules.30/100b: the index byte is a module-config SLOT, not a layer.
30/10ca CLEAR ALL DATA has never been sent by us. NayaCore sends 30/10ca with
params 00 00 (frame aa 00 50 00 30 04 10 ca 00 00 da 04): in our disassembly of NayaCore 6.11.0
(macOS and Windows builds), _remapClearFlash passes one byte 00, built the same way as the index
byte of its 30/1001 read. STATIC7 naya-create-kb sent it on 3.41.0 with params 01
(aa 00 50 00 30 03 10 ca 01 db 04: in this site's convention a flag byte 01 and no data), and it
reports status 00 with data 80 and a formatted data partition.
REPORTED14 Whether 00 00 and 01 behave the same stays a
donor-board test. OPEN NayaFlow's "Clear all keymap data" sends
clear_data, which dispatches to ClearAllData and then 30/10ca (CM-121). What it wipes and how to
restore is on Factory reset.
30/10ca formats the keyboard's data partition
Reported to wipe the keymaps, LED maps, module configs and the layer list. Never sent by us; the restore procedure is untested. Back up everything first and make first attempts on a donor board. See Factory reset.
No REMAP write needs a commit: a 30/1004 write applies at once and persists across a
reboot. fe/100a is not a commit (see Settings and timing).
MEASURED 3.41.0
fe SYSTEM¶
| Command | Vendor name | Params | Reply | Evidence |
|---|---|---|---|---|
fe/1001 |
MEDIA ID REQUEST | 00 |
00 (status only); opens the protocol channel |
MEASURED 3.41.01 |
fe/1002 |
GET FW VERSION | 00 |
00 00 <major> <minor> <patch>: 00 00 03 29 00 = 3.41.0, 00 00 03 23 04 = 3.35.4, 00 00 03 1e 01 = 3.30.1 |
MEASURED 3.41.0, 3.35.4 REPORTED (3.30.1)3 |
fe/1003 |
MODULE BATTERY RECOVERY | 00 00 OFF, 00 01 ON; NayaCore refuses empty params and any other value |
never captured | STATIC16 |
fe/1004 |
GET HW ID NUMBER | 00 |
00 + ASCII equal to the half's USB serial string (<ascii>) |
MEASURED 3.41.0 |
fe/1005 |
SET HOST OS | 00 00 Windows, 00 01 macOS |
never sent by us | STATIC6 |
fe/1006 |
GET KB BATTERY LEVEL | 00 |
00 <mV hi> <mV lo>, the half's own cell, e.g. 00 0f f5 = 4085 mV |
MEASURED 3.41.0 |
fe/1007 |
SET RELEASE MODE | 00 00, 00 01 accepted; 00 ff refused (ea) |
00 |
MEASURED 3.41.0, 2026-09-02 STATIC |
fe/1008 |
TOGGLE KEYSCAN MODE | one data byte; nayactl sends 00 01 on, 00 00 off |
00 |
STATIC1 MEASURED 2026-09-20 |
fe/1009 |
KEYSCAN EVENT (device to host) | none | 00 <row> <col> <state> |
STATIC INFERRED REPORTED (polarity, as nayactl decodes it)1 |
fe/100a |
SET ACTIVITY TIMEOUTS | 00 + three u32 little-endian milliseconds |
00 |
MEASURED 3.41.0 STATIC |
fe/100b |
GET ACTIVITY TIMEOUTS | 00 |
00 + the same 12 bytes |
MEASURED 3.41.0 |
Notes on the SYSTEM rows:
fe/1001opens the protocol channel; see opening a session.fe/1002: our captures show00 00 03 29 00from both halves; the38that follows it in the frame is the checksum. The 3.30.1 reading is from the nayactl maintainer's board, a version in no public release. How the vendor writes versions is on Versions.fe/1003is NayaCore's rescue for a critically drained module. NayaCore accepts exactly one data byte,00(OFF) or01(ON), and refuses to send it with empty params or any other value STATIC16. It was never captured or sent by us.fe/1004: the ASCII string is the same as the USB serial string and NayaCore's hardware id; it is per device and never reproduced here1.fe/1005takes one data byte below 2,00Windows and01macOS, from NayaCore's checks and its enum order. STATIC6fe/1006readings are noisy; nayactl takes the median of five1.fe/1007: on the left half of a 3.41.0 board, toggling produced no new USB interface and no re-enumeration. NayaCore checks a two-value enum. NayaCore's captured connect sequence contains nofe/10071.fe/1008: NayaCore accepts two values that were not recovered. Keyscan events were read directly from a right half that did not type (143 events, 2026-09-20), so they bypass the split link.fe/1009: "state00= press" is how nayactl decodes it1; no test of ours pins it. NayaCore routes only this id to its integration worker. INFERREDfe/100avalues under 30 s are refused withea. Full detail on Settings and timing.
fe/1005, fe/1007, fe/1008 and fe/100a are writes
They change the host OS mode, the release mode, the keyscan mode and the activity timeouts.
fe/1007 was toggled on 3.41.0 without visible effect; fe/1005 has never been sent by us.
fe/1003 is on the never-send list.
ed LED¶
Framing, settled for this site (measured 2026-09-09): every ed request's params are
00 (the flag byte), then a TARGET byte, then the command's values. The target's value is ignored,
and short params are zero-filled from the end. Evidence, and how other tools frame it, is on
LEDs.
Every ed command is a write, including the short ones
A short or empty params string is zero-filled, not rejected: ed/1013 with params 00 sets a
brightness ceiling of 0, which would leave the keys dark across reboots (the ceiling is stored),
while still acking normally.
Keep the restore frame aa 00 50 00 ed 05 10 13 00 00 64 67 04 (ceiling 100) at hand.
| Command | Vendor name | Params | Effect | Evidence |
|---|---|---|---|---|
ed/1003, ed/1004, ed/1005 |
LEDs ON, LEDs OFF, LEDs TOGGLE | 00 <target> |
ed/1004 with params 00 alone (target zero-filled) switched the board off |
MEASURED (1004, 3.41.0, 2026-09-09) STATIC4 |
ed/100f, ed/1010, ed/100d |
HALT, RESUME, EFFECT CYCLE | 00 <target> |
named only by us | STATIC4 |
ed/1006, ed/1007 |
INCREMENT, DECREMENT | 00 <target> <amount>, amount below 101 |
not measured | STATIC4 |
ed/1008 |
ADJUST BRIGHTNESS | 00 <target> <level>, level 0 to 100 |
see below | MEASURED (left half, 3.41.0, 2026-09-09) STATIC |
ed/1009 to ed/100c |
RED, GREEN, BLUE, WHITE | not known | untested by anyone we know of | STATIC16 |
ed/100e |
HUE SATURATION | 00 <target> <hue lo> <hue hi> <sat>: hue below 361, saturation below 101 |
not measured | STATIC4 |
ed/1011 |
SELECT EFFECT | 00 <target> <effect>: 0 solid, 1 breathe, 2 swirl, 3 spectrum |
00 01 (effect zero-filled) left the board solid; 00 00 01 made it breathe |
MEASURED 3.41.0, 2026-09-09 STATIC |
ed/1012 |
SET SCANMODE PWM | 00 <target> <bool> |
a persistent setting (NayaFlow "LED scan mode", default on); visible only as flicker on camera | MEASURED 3.41.0, 2026-09-13 |
ed/1013 |
SET LED MAX BRIGHTNESS | 00 <target> <1-100> |
a persistent ceiling over the key array | MEASURED 3.41.0, 2026-09-13 REPORTED (module LEDs)4 |
ed/1014 |
SET LED LAYER OVERRIDE | 00 <target> <0 or 1> |
how long an LED action pressed on a key lasts | MEASURED 3.41.0, 2026-09-16 (left half) |
ed/1050 |
RGB BRIGHTNESS | 00 <target> <r> <g> <b> <brightness>, brightness below 101 |
untested by us | STATIC4 |
ed/10d1, ed/10d2 |
FORCE LEDs ON, FORCE LEDs OFF | not known | untested by us | STATIC7 |
Notes on the LED rows:
ed/1003toed/1005, HALT, RESUME and EFFECT CYCLE take the target only4. What HALT, RESUME and EFFECT CYCLE do on the device, and the order EFFECT CYCLE steps through, are not measured by us.ed/1006anded/1007: NayaCore requires an amount below 101 (its_constructLEDMessages, as published in nayactl PR #6)4.ed/1008: NayaCore clamps larger values to 100 with a warning. The measured series, params as sent:00 10off (target10, level 0),00 64off,00 00 0fdim,00 03 64full,00 c8 0fdim. nayactl's command line sends one byte after the flag and documents 0 to 255, so on 3.41.0 it sets brightness 014.ed/1009toed/100care named by NayaCore and nayactl; nobody we know of has tried them.ed/100e: the hue is a little-endian u16 (<hue lo> <hue hi>)4.ed/1011: the effect index uses NayaCore's order (0 solid, 1 breathe, 2 swirl, 3 spectrum), and NayaCore requires an index below its effect count. The effect is a runtime state, per half. The stored per-layer animation uses a different order (see Layers).ed/1013: 30 visibly dims the board and 100 restores it, and the value persists MEASURED 3.41.0, 2026-09-13. A ceiling of 0 would therefore leave the keys dark across reboots while every LED command still acks INFERRED, as the author of nayactl PR #6 reports REPORTED4; we have never sent 0. Module LEDs are reported to be outside this ceiling by the author of nayactl PR #6; no measurement of ours REPORTED4.ed/1014: 0 = an LED action pressed on a key lasts until the keyboard restarts; 1 = until the next layer change (NayaFlow's "LED action override", default "until keyboard restart"). We have sent it only to the left half, which acks; naya-create-kb reports that the right half does not answer it REPORTED12.- The three settings
ed/1012toed/1014have no read command: NayaCore 6.11.0 names every LED command it can send and none of them reads a setting. Their values can only be known by writing them. STATIC6 ed/1050: naya-create-kb reports it as a global color override that survives a reboot and that bulk30/100ewrites drop back to following the map REPORTED12.ed/10d1anded/10d2: NayaCore 6.11.0's own LED command table starts with10d1and10d2, and its log names start with FORCE LEDs ON / OFF, so these are the vendor's ids STATIC7. naya-create-kb reports no reply from either half REPORTED12; nayactl names them1.
Every well-formed ed frame is acked 00 with no data, whether or not its params made
sense, so every ack of one ed command from one half is the same frame, and its byte before 04
is only the checksum, 10 ^ <C1>: 03 for ed/1013, 18 for ed/1008, 01 for ed/1011. It
carries no value.
INFERRED (arithmetic)
MEASURED (identical acks, 3.41.0, 2026-09-09)
NayaCore refuses to build an LED command with no target ("Missing or empty target
parameter for LED command"), so NayaFlow never sends empty ed params; it clamps out-of-range
values to 100 with a warning. STATIC4
A board at brightness 0 lights again when its brightness is raised: in the series
above, level 15 sent after two frames that set level 0 lit the left half dim. Whether ON
(ed/1003) or INCREMENT (ed/1006) relights a board at brightness 0 is untested by us.
MEASURED left half, 3.41.0, 2026-09-09
Brightness and effect commands act on the half they are sent to: the right half stayed solid while the left breathed. MEASURED 3.41.0, 2026-09-10
be BLE¶
be/1001, be/1003, be/1004, be/100a and be/1010 change bonds
UNPAIR ALL (be/1004) drops every bond on the half, host bonds and the split link alike, and
CLEAR ALL SPLIT LINKS (be/1010) drops the link between the halves. Store both halves' own and
pair addresses before any of them, and use them only inside the repair sequence on
Split link. The read commands in this table are safe.
| Command | Vendor name | Params | Reply | Evidence |
|---|---|---|---|---|
be/1001 |
SET PAIR ADDRESS | 00 <addr6> (NayaCore: "1 parameter of 6 bytes") |
00 |
STATIC6 MEASURED (by hand, 3.35.4, 2026-09-20) |
be/1002 |
GET PAIR ADDRESS | 00 |
00 <addr6>: the OTHER half's address (the split-link partner) |
MEASURED 3.41.0 |
be/1003 |
UNPAIR PAIR ADDRESS | one 6-byte address | named only | STATIC6 |
be/1004 |
UNPAIR ALL PAIRS | 00 (empty data, INFERRED) |
drops every bond on that half | STATIC MEASURED (as a repair step, 2026-09-20) |
be/1005 |
GET ALL PAIRS | 00 |
00 <count> + <addr6> per bond; with no host bonded the only entry is the other half |
MEASURED 3.41.0 |
be/1006 |
GET BLE NAME | 00 |
00 <len> <ascii>; the stock name is "DefaultName" (00 0b + 11 bytes); status ff with no data when no name is set (3.28.7) |
MEASURED 3.41.0, 3.28.7 |
be/1007 |
SET BLE NAME | one parameter shorter than 256 bytes | never sent by us | STATIC6 |
be/1008 |
GET BLE ADDRESS | 00 |
00 <addr6>: the half's own split-link identity, not the identity a host pairs with |
MEASURED 3.41.0 |
be/1009, be/100a |
SELECT BLE PROFILE, CLEAR BLE PROFILE | one data byte, slot below 5 | select slot 1: aa 00 50 00 be 04 10 09 00 01 18 04 |
STATIC6 INFERRED (frame) |
be/100b |
SELECT BLE OUT | not known | named only | STATIC1 |
be/100c |
GET BLE STATUS | 00 |
00 + a 239-byte status blob; answered by the left half, not the right |
MEASURED 3.41.0 |
be/100d |
GET DONGLE ADDR | 00 |
the left half returns a stored dongle address (6 bytes; not the dongle's USB serial) | MEASURED 3.41.0, 2026-09-11 |
be/100e |
GET SLOTX ADDR | not known | named only | STATIC1 |
be/100f |
GET BLE FW VERSION | 00 |
00 02 on both halves: one data byte, BLE version 2 |
MEASURED 3.41.0 DOC9 |
be/1010 |
CLEAR ALL SPLIT LINKS | 00 (empty data, INFERRED) |
drops the link record between the halves | STATIC MEASURED (in the repair, 2026-09-20) |
Notes on the BLE rows:
be/1001is the first step of NayaCore's pairing repair.be/1002answers from both halves with LEN09(status + 6 address bytes) in our captures.be/1009andbe/100arefuse other sizes and values: "Invalid parameter size (%1) for SEL/CLEAR_BLE_PROFILE, should be 1", "should be less than 5"6.be/100c: the reply is a 250-byte frame (LENf2) around the 239-byte status blob. Decode on Split link and Bluetooth.be/100f: the frame from both halves isaa 5x 00 00 be 04 10 0f 00 02 1d 04(LEN04leaves one data byte;1dis the checksum). The version byte02fits the vendor's "BLE v2" (keyboard 3.35.4 onward)9.- On 3.28.7,
be/100c,be/100d,be/100eandbe/100freturn no frame at all on either half, whilebe/1002,be/1005,be/1006andbe/1008answer. MEASURED 3.28.7, 2026-09-19
de MODULE¶
Module commands go to the half the module is docked on; each half answers for its own dock.
de/1005 and de/1006 act on the docked module
MODULE FWUP programs the docked module from the keyboard's stored bundle, and RESET MODULE resets
the module. We have run MODULE FWUP through NayaFlow and OpenFlow (3.41.0, 2026-09-23): its type
byte must match the module (a Tune sent 03 was programmed with the Track's app and stayed dark
until 02 was forced), and one Track update never finished and hung the keyboard until a power
cycle. RESET MODULE has never been sent by us. An interrupted module update can leave the
keyboard's module store partly erased. The read commands in this table are safe.
| Command | Vendor name | Params | Reply | Evidence |
|---|---|---|---|---|
de/1001 |
SEND HANDSHAKE | 00 |
00 01 <addr> when a booted module answers (addr = dock address, type and side); 00 00 f0 (left) or 00 00 f1 (right) with nothing booted in the dock |
MEASURED 3.41.0 |
de/1002 |
MODULE DETECT | 00 |
00 01 for ANY docked module (presence only, never the type), 00 00 when empty |
MEASURED 3.41.0 |
de/1003 |
CHECK HANDSHAKE | 00 |
no reply on either half (3.41.0, 2026-09-01) | STATIC6 MEASURED (no reply) |
de/1004 |
(no name in any source) | not known | not known | STATIC (absence) |
de/1005 |
MODULE FWUP | 00 + one byte naming the module to program: 01 Touch, 02 Tune, 03 Track |
00 at once (an empty ack); the keyboard then programs the module and restarts |
MEASURED 3.41.0, 2026-09-23 STATIC15 |
de/1006 |
RESET MODULE | empty data (INFERRED) | never sent | STATIC6 |
de/1007 |
GET ADDRESS | 00 |
00 <addr>, the authoritative dock address |
MEASURED 3.41.0 |
de/1008 |
GET MODULE FW VERSION | 00 |
00 <addr> <flag> 00 <major> <minor> <patch>, e.g. 00 20 00 00 02 03 03 = Track left, 2.3.3 |
MEASURED 3.41.0 |
de/1009 |
GET BATTERY | 00 |
00 <b0> <batt hi> <batt lo> <rail hi> <rail lo>, both values big-endian in 0.1 mV |
MEASURED5 |
de/100a |
MODULE FILE FW VERSION | 00 |
left half: 00 00 02 03 03 (2.3.3); the right half does not answer |
MEASURED 3.41.0, 2026-09-16 |
de/100b |
GET PRECISE BATTERY LEVEL | 00 |
00 <mV hi> <mV lo> <valid> (valid 00 = good): the module's battery cell in plain millivolts |
MEASURED module 2.3.3 |
Notes on the MODULE rows:
de/1001: send it before other module queries on a connection. Our captures show left Tune00 01 40, left Track00 01 20, right Track00 01 21, right Tune00 01 41, right Touch00 01 11; a Touch docked left has address10(MEASURED 3.28.7, 2026-09-19; the nayactl maintainer saw the same2). Decode on Modules.de/1002: nayactl labeled every module "Touch" until this was fixed upstream2.de/1003: NayaCore's string "not implemented on version %1.%2.%3 of CORE" suggests the command is version-gated; nayactl sent it to both halves on 2026-09-01 and neither answered.de/1005: NayaCore requires one parameter ("parameter size should be 1") and falls back to AUTO_DETECT on an invalid type. The byte names the module to program:01Touch,02Tune,03Track. NayaFlow's module update sent01to a Touch,02to a Tune and03to a Track, NayaCore 6.11.0 builds 1, 2 and 3 for its Touch, Tune and Track uploads, and the keyboard acks at once with an empty reply. It is not nayactl's module numbering (1 Touch, 2 Track, 3 Tune):03sent to a Tune made the keyboard program it with the Track's app, and the Tune stayed dark until02was forced. MEASURED 3.41.0, 2026-09-23 STATIC15de/1008: the flag byte read01with an all-zero version while a module had not reported yet. Each half answers for its own dock, the right half included (our captures:00 21 00 00 02 03 03,00 11 00 00 02 01 02).de/1009:b0read00;railis the USB or Qi charging rail. It works on module 2.1.2, 2.2.2 and 2.3.3.de/100areads the version of the module firmware bundle stored on the keyboard, not the docked module's own version.de/100bis absent on module 2.1.2 (no data, about 1.09 s of timeout) and 2.2.2 (no reply). Its value is the module's battery cell in millivolts: back-to-back readings againstde/1009agree within about 30 mV5. Details on Modules.
fa, ee, ff, ca and f1¶
| Command | Vendor name | Params | Reply | Evidence |
|---|---|---|---|---|
fa/1001 |
SPIFLASH TEST (TEST FLASH) | 00 |
00 + 2 header bytes + 6 bytes per partition (status + five return codes): 32 data bytes on the left (5 partitions), 20 on the right (3) |
MEASURED 3.41.0, 2026-09-10 STATIC |
fa/1002 |
FORMAT PARTITION | one data byte, the partition (INFERRED) | never sent | STATIC1 |
fa/1006 |
ERASE CHIP | not known | never sent | STATIC1 |
ee/10ae |
MCU BOOT RESET | 00 |
the half re-enumerates in MCUboot | MEASURED 2026-09-16, 2026-09-2010 |
ee/10be |
DFU RESET | not known | never sent by us | STATIC6 |
ee/10ce |
NORMAL RESET | 00 |
reboots the half | MEASURED (in the pairing repair, 2026-09-20) |
ff/1000 to ff/1003 |
WAIT, VERIFY FLASH, ENQUEUE READ LAYERS, GET MODULE INFO IF PRESENT | host-side | ff/1000 sent to the left half got no reply |
STATIC6 MEASURED (ff/1000, 3.41.0, 2026-09-01) |
ca/xxxx, f1/xxxx |
IC CHARGER, FIRMWARE | none known | none known | STATIC6 |
Notes:
fa/1001is read-only; its decode belongs to Flash layout. Its reply frames are 43 bytes on the left and 31 on the right (MEASURED 3.41.0, 2026-09-10). Of the threefacommands nayactl knows, onlyfa/1002andfa/1006are destructive1.ee/10ae: the half re-enumerates at its MCUboot product id with two CDC ports and stays there (over a minute measured, no timeout) until an SMPos reseton the port that answers SMP (back in the application in about 8 s) or a power cycle (on USB the half's own switch is enough, see USB). It is the first step of every stock firmware update, so it is recoverable, but send it only with that exit at hand; the reset to the log port does nothing10. Details on Bootloader.ee/10beDFU RESET has never been sent by us, and no DFU product id has ever been seen.ee/10ceNORMAL RESET reboots the half and is the last step of NayaCore's pairing repair. It is the only command that reboots a half back into the application by itself:ee/10aealso resets the MCU, but stops in MCUboot; SMPos resetreboots a half from MCUboot; and on USB the half's switch is a reset. MEASURED 2026-09-16 to 2026-09-23 Like every reset,ee/10cepasses through the bootloader: the left half showed its MCUboot id for about 0.7 to 2 s and was back in the application about 3 s after the command; from the third restart in a row it stayed off USB until its cable was replugged MEASURED 3.41.0, 2026-09-23 (see Bootloader).ff: these are NayaCore job-queue steps ("WAIT meta command duration %1 ms exceeds max %2 ms, clamping"), not device commands; categoryffis absent from NayaCore's per-category device tables7, andff/1000sent to the left half got no reply (MEASURED 3.41.0, 2026-09-01).caandf1are category names only, from NayaCore's log classes; no command is known. Do not blind-probe them.
fa/1002, fa/1006, ee/10ae and ee/10be
FORMAT PARTITION and ERASE CHIP destroy stored data; nayactl refuses both without --force.
MCU BOOT RESET parks the half in its bootloader until an SMP os reset on the right port or a
power cycle. DFU RESET has an unknown effect. None of these belongs in an experiment on a board
you rely on.
Text commands¶
The retired text channel (fwvchk, clear_bonds, mcuboot_reset and others) is described on
Transport; on 3.41.0 it answers nothing.
Host operations that send these commands¶
These are NayaFlow 1.25.1 / NayaCore 6.11.0 operations; details on NayaFlow and NayaCore.
NayaFlow's renderer has one device-control path: POST /rpc/send-nayacore-zmq-message
with {"messages": [topic, event, ...frames]}, the topic always command. The UI's events are
update_create_fw, update_module_fw, create_pairing_start, force_touch_start,
force_track_start, force_tune_start, clear_data, repair_flash and clear_ble_devices;
NayaCore also knows flash_keymap, update_keymap, start_device_manager, close_device_manager,
update_fw_files and set_handshake_frequency. STATIC86
NayaFlow's "Clear all keymap data" (the Danger Zone button) sends clear_data, which
dispatches to ClearAllData and then 30/10ca: NayaCore's dispatcher maps clear_data (event 5) to
its clearAllData request, whose chain ends in doClearAllDataOperations, which queues 30/10ca; its
step names are Clearing, ReadData and VerifyDataCleared, and the vendor's text says the board clears
and restarts. STATIC867 No USB capture of the
button exists, so the bytes it puts on the wire are still unrecorded
OPEN.
repair_flash runs fa/1001, then fa/1002 on the partitions that report errors, a
normal restart and a new self-test; fa/1006 sits in the same code path. clear_ble_devices runs
NayaCore's ClearBLEDevices operation, and create_pairing_start runs the pairing repair (be/1001,
be/1010, be/1004, ee/10ce; see Split link).
STATIC6
NayaFlow's Danger Zone buttons (repair_flash, clear_data, clear_ble_devices) act
without a confirmation dialog, and its Hardware Manager binds Ctrl/Cmd+D to update_create_fw,
which flashes both halves when no warning is pending. STATIC8
NayaFlow's keymap flash (update_keymap, then flash_keymap) reads the board, writes
the layer list, layer data, module config list and data, LED maps and settings as a sparse diff of
its own profile, then reads everything again to verify ("Profile verification FAILED"). It pushes
the host's profile: a board configured elsewhere is overwritten wherever the profiles differ.
STATIC6 MEASURED 3.41.0,
2026-09-01 to 2026-09-17
Never-send list¶
The site's one never-send list is on Troubleshooting; the table below gives the protocol-level reason behind each command-related entry and agrees with it.
| Entry | What happens | Severity | Evidence |
|---|---|---|---|
30/10ca, fa/1002, fa/1006; be/1004 and be/1010 outside the repair sequence; de/1005 and fe/1003 |
formats the data partition (reported); formats a partition; erases the flash chip; drops host bonds and the split link; module update and rescue paths (a wrong de/1005 byte leaves the module dark) |
data loss or bond loss | STATIC1 REPORTED (the 30/10ca effect)14 |
hold-tap flavor 04 or higher in any hold-tap record |
accepted, stored, then every key stops until the board is unplugged; rewriting the record does not help | board stops until replug | MEASURED 3.41.0, 2026-09-03 |
| any write that needs three frames, on 3.28.7 | the half stops answering and typing until unplugged | board stops until replug | MEASURED 3.28.7, 2026-09-19 |
ed/1013 with 0, or with short or empty params |
the ceiling is stored (30 and 100 persist, measured), so 0 means a key array that stays dark across reboots; short or empty params are zero-filled into 0 | reversible (send 100) | MEASURED (persistence of 30 and 100, 3.41.0, 2026-09-13; zero-fill, 2026-09-09/10) INFERRED (the effect of 0) REPORTED4 |
a 0d TOGGLE record targeting layer 0 |
does not leave the current layer; strands the keyboard until a power cycle; NayaFlow does not offer it | stranded until power cycle | MEASURED 3.41.0 STATIC8 |
| BT_OUT pressed on battery with no reachable host | froze the keyboard (typing and layer switching) until a power cycle | stranded until power cycle | MEASURED 3.41.0, 2026-09-11 |
ee/10ae without an SMP client ready; ee/10be ever |
parks the half in MCUboot; unknown | recoverable with os reset; unknown |
MEASURED STATIC |
the text commands clear_bonds and mcuboot_reset |
nothing on 3.41.0 (the text channel is retired); they matter only on 3.30.1 and older | none on current firmware | MEASURED 3.41.0 DOC9 |
a burst of ed recovery writes (RESUME and ed/1050 RGB among them) sent to the RIGHT half's own port |
the naya-create-kb maintainer reports that one such burst left the right half and its module fully dark on 3.41.0; a single ed/1013 sent to the right half's own port was answered and parked nothing (3.35.4 and 3.41.0, 2026-09-20/22); the LEDs of both halves are driven from the left half's stores (see LEDs) |
lighting | REPORTED (the burst)11 MEASURED (the single write) |
nayactl gates only the five reset, format and erase ids and two text commands;
30/10ca and the Bluetooth clears pass through its raw command ungated1.
Replaying fe/100a bytes is not on this list: a replay writes that session's
timeouts and nothing more (values under 30 s are refused with ea). NayaFlow sends the same
fe/100a bytes on every flash, and every later command answered in our captures (3.41.0,
2026-09-01 and 2026-09-17); NayaFlow's captured frame, sent verbatim seven times on two boards
(3.41.0, 2026-09-16 to 2026-09-21), wedged nothing. This differs from naya-create-kb, which warns that replayed fe/100a bytes "wedge the
state machine" REPORTED13.
MEASURED 3.41.0
You can damage a half or lose data
Every entry above has a measured or reported hazard. Read the page each entry links to, and
make first attempts on a donor board. 30/10ca restore is untested by us after a format,
layer list included; see Factory reset.
Names versus measured meaning¶
Some vendor names mislead:
| Command | Name suggests | What it is |
|---|---|---|
de/100b |
a "precise level" of a supply rail | the module's battery cell voltage in millivolts |
de/1002 |
module detection | presence only; the type comes from de/1007 |
ff/10xx |
device commands | NayaCore job-queue steps |
Open questions¶
- OPEN
de/1004;be/100b,be/100e;ed/1009toed/100c; whated/10d1anded/10d2do; everything incaandf1; the payloads offe/1003,de/1006,be/1004,be/1010andfa/1002as NayaCore sends them (details). - OPEN Which two values
fe/1008accepts, and the meaning and polarity of thefe/1009bytes (details). - OPEN Whether
30/10cabehaves the same with params00 00(NayaCore) and01(the reported run under30REMAP), a donor-board test, and what its80reply byte means (details). - OPEN The
de/1008flag byte (seen00and01) (details). - OPEN The bytes NayaFlow's
clear_databutton puts on the wire (that it runs30/10cais read from NayaCore) (details). - OPEN What
ed/1050does and what clears it (details); the cycle order ofed/100d; whether ON or INCREMENT relights a board at brightness 0 (untested by us).
Sources¶
-
nayactl, github.com/Qonfused/nayactl (
constants.pycommand names andDANGEROUS_COMMANDS,cli/led.py,cli/keyscan.py,transport.py). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
nayactl, pull request #2 (module type from the dock address; Windows fixes). ↩↩
-
nayactl, pull request #5 and its comments (a board on 3.30.1 with modules on 2.2.2). ↩
-
nayactl, pull request #6 (NayaCore 6.11.0 LED command table and parameter checks; the LED ceiling; module LEDs). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩
-
NayaFlow 1.25.1, NayaCore 6.11.0 strings (static reading): command log names and categories, parameter checks, job-queue and operation step names. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩
-
NayaFlow 1.25.1, NayaCore 6.11.0 (Windows x64), our disassembly (2026-09-23): the per-category command tables,
ProtocolCDCProcessWorker::_remapStartStep(the0x10cacase and the missing macro cases),_constructRemapMessages, the module-config template type. ↩↩↩↩↩↩↩↩↩↩ -
NayaFlow 1.25.1 renderer and flow-bg-server strings (static reading): ZMQ events, Danger Zone texts, the Ctrl/Cmd+D binding, the Toggle target list. ↩↩↩↩
-
create-legacy-firmware, vendor release notes, changelogs/ (NayaFlow 1.17.2: SystemCDC retired; 1.19.1: BLE v2). ↩↩↩
-
create-legacy-firmware,
FLASHING-PROCEDURE.md, "Measured on hardware, both halves (2026-09-20)" (commit cdd897c). ↩↩ -
The naya-create-kb maintainer's published captures and dumps (USB CDC capture logs and a device-state note of 2026-09-22); raw data decoded by us, never copied. ↩
-
naya-create-kb, protocol/commands (commit 7668067). ↩↩↩
-
naya-create-kb, protocol/transport (commit 7668067). ↩
-
naya-create-kb, storage/factory-reset (commit 7668067). ↩↩↩
-
create-legacy-firmware,
FLASHING-PROCEDURE.md, "Module firmware update, captured from NayaFlow (2026-09-23)" (NayaFlow's module update and Force Update captured 2026-09-23; NayaCore 6.11.0'sdoUpdateModuleOperations). ↩↩ -
OpenFlow, commit fe1bc58: NayaCore 6.11.0's
_constructSystemMessagesacceptsfe/1003only with one data byte,00(OFF) or01(ON). ↩↩