Skip to content

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: 30 REMAP, be BLE, ca IC CHARGER, de MODULE, ed LED, ee RESET, f1 FIRMWARE, fa SPI FLASH, fe SYSTEM, ff META (host-side only).
  • REMAP (keymap, layer list, LED maps, module configs) is answered by the left half only.
  • ed params are 00 <target> <values>; short params are zero-filled, so a short ed/1013 writes 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/1001 is 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 is aa 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-01
  • 30/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 status 19 and the index (MEASURED 3.41.0, 2026-09-01). Captured NayaFlow write for layer 1: params 00 01 24 01 04 04 00 07 00 34 01 04 1e 00 07 00, ack 00 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 00 only, answered status 11 with data 00, 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 status 11 (not implemented, 2026-09-19). NayaCore never builds them (its REMAP switch has no case for 1005 to 1008) and flashes a macro binding as 07 00 (NONE). MEASURED STATIC7
  • 30/1009: the 16 bytes of each profile entry are NayaFlow's profile ids. The slot-0 entry 00 00 80 05 00 00 00 00 00 read the same in five NayaFlow reads on our board (2026-09-01 to 2026-09-17); type 80 is NayaCore's 40-field template type. The byte before it read 00 in 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/1001 opens the protocol channel; see opening a session.
  • fe/1002: our captures show 00 00 03 29 00 from both halves; the 38 that 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/1003 is NayaCore's rescue for a critically drained module. NayaCore accepts exactly one data byte, 00 (OFF) or 01 (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/1005 takes one data byte below 2, 00 Windows and 01 macOS, from NayaCore's checks and its enum order. STATIC6
  • fe/1006 readings 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 no fe/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: "state 00 = press" is how nayactl decodes it1; no test of ours pins it. NayaCore routes only this id to its integration worker. INFERRED
  • fe/100a values under 30 s are refused with ea. 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/1003 to ed/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/1006 and ed/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 10 off (target 10, level 0), 00 64 off, 00 00 0f dim, 00 03 64 full, 00 c8 0f dim. 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/1009 to ed/100c are 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/1012 to ed/1014 have 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 bulk 30/100e writes drop back to following the map REPORTED12.
  • ed/10d1 and ed/10d2: NayaCore 6.11.0's own LED command table starts with 10d1 and 10d2, 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/1001 is the first step of NayaCore's pairing repair.
  • be/1002 answers from both halves with LEN 09 (status + 6 address bytes) in our captures.
  • be/1009 and be/100a refuse 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 (LEN f2) around the 239-byte status blob. Decode on Split link and Bluetooth.
  • be/100f: the frame from both halves is aa 5x 00 00 be 04 10 0f 00 02 1d 04 (LEN 04 leaves one data byte; 1d is the checksum). The version byte 02 fits the vendor's "BLE v2" (keyboard 3.35.4 onward)9.
  • On 3.28.7, be/100c, be/100d, be/100e and be/100f return no frame at all on either half, while be/1002, be/1005, be/1006 and be/1008 answer. 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 Tune 00 01 40, left Track 00 01 20, right Track 00 01 21, right Tune 00 01 41, right Touch 00 01 11; a Touch docked left has address 10 (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: 01 Touch, 02 Tune, 03 Track. NayaFlow's module update sent 01 to a Touch, 02 to a Tune and 03 to 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): 03 sent to a Tune made the keyboard program it with the Track's app, and the Tune stayed dark until 02 was forced. MEASURED 3.41.0, 2026-09-23 STATIC15
  • de/1008: the flag byte read 01 with 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: b0 read 00; rail is the USB or Qi charging rail. It works on module 2.1.2, 2.2.2 and 2.3.3.
  • de/100a reads the version of the module firmware bundle stored on the keyboard, not the docked module's own version.
  • de/100b is 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 against de/1009 agree 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/1001 is 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 three fa commands nayactl knows, only fa/1002 and fa/1006 are 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 SMP os reset on 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/10be DFU RESET has never been sent by us, and no DFU product id has ever been seen. ee/10ce NORMAL 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/10ae also resets the MCU, but stops in MCUboot; SMP os reset reboots 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/10ce passes 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; category ff is absent from NayaCore's per-category device tables7, and ff/1000 sent to the left half got no reply (MEASURED 3.41.0, 2026-09-01).
  • ca and f1 are 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/1009 to ed/100c; what ed/10d1 and ed/10d2 do; everything in ca and f1; the payloads of fe/1003, de/1006, be/1004, be/1010 and fa/1002 as NayaCore sends them (details).
  • OPEN Which two values fe/1008 accepts, and the meaning and polarity of the fe/1009 bytes (details).
  • OPEN Whether 30/10ca behaves the same with params 00 00 (NayaCore) and 01 (the reported run under 30 REMAP), a donor-board test, and what its 80 reply byte means (details).
  • OPEN The de/1008 flag byte (seen 00 and 01) (details).
  • OPEN The bytes NayaFlow's clear_data button puts on the wire (that it runs 30/10ca is read from NayaCore) (details).
  • OPEN What ed/1050 does and what clears it (details); the cycle order of ed/100d; whether ON or INCREMENT relights a board at brightness 0 (untested by us).

Sources


  1. nayactl, github.com/Qonfused/nayactl (constants.py command names and DANGEROUS_COMMANDS, cli/led.py, cli/keyscan.py, transport.py). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. nayactl, pull request #2 (module type from the dock address; Windows fixes). ↩↩

  3. nayactl, pull request #5 and its comments (a board on 3.30.1 with modules on 2.2.2). ↩

  4. nayactl, pull request #6 (NayaCore 6.11.0 LED command table and parameter checks; the LED ceiling; module LEDs). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. nayactl, issue #4 (module battery samples). ↩↩

  6. NayaFlow 1.25.1, NayaCore 6.11.0 strings (static reading): command log names and categories, parameter checks, job-queue and operation step names. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  7. NayaFlow 1.25.1, NayaCore 6.11.0 (Windows x64), our disassembly (2026-09-23): the per-category command tables, ProtocolCDCProcessWorker::_remapStartStep (the 0x10ca case and the missing macro cases), _constructRemapMessages, the module-config template type. ↩↩↩↩↩↩↩↩↩↩

  8. 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. ↩↩↩↩

  9. create-legacy-firmware, vendor release notes, changelogs/ (NayaFlow 1.17.2: SystemCDC retired; 1.19.1: BLE v2). ↩↩↩

  10. create-legacy-firmware, FLASHING-PROCEDURE.md, "Measured on hardware, both halves (2026-09-20)" (commit cdd897c). ↩↩

  11. 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. ↩

  12. naya-create-kb, protocol/commands (commit 7668067). ↩↩↩

  13. naya-create-kb, protocol/transport (commit 7668067). ↩

  14. naya-create-kb, storage/factory-reset (commit 7668067). ↩↩↩

  15. 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's doUpdateModuleOperations). ↩↩

  16. OpenFlow, commit fe1bc58: NayaCore 6.11.0's _constructSystemMessages accepts fe/1003 only with one data byte, 00 (OFF) or 01 (ON). ↩↩