Module firmware¶
The Touch, Track and Tune modules run their own firmware on their own microcontroller. This page
covers what runs in a module, how its firmware ships (the FlashMemory.bin bundle), where the
keyboard stores it, how NayaCore updates a module, the module recovery mode, the versions and what
they changed, and what is untested. The thing to know first: an update must name the module that is
physically docked (de/1005 takes 01 for a Touch, 02 for a Tune, 03 for a Track), because the
wrong byte programs the wrong app into the module, which then stays dark until a forced update with
the right one. The module recovery key, measured on 3.28.7, revived two dead modules.
At a glance
- Each module has an STM32F411 and no radio; its firmware is not an MCUboot image.
- Module firmware ships as
FlashMemory.bin: a 1 MiB LittleFS image of encrypted.sfbapps. - The keyboard stores the bundle on the left half and programs a docked module from it.
de/1005names the docked module:01Touch,02Tune,03Track (not nayactl's numbering).de/100areads the stored bundle's version (left half only);de/1008reads a docked module's own version.- The System-layer recovery key force-charges a drained module and brought two back (3.28.7, module 2.1.2).
What runs in a module¶
Each module (Touch, Track, Tune) has an ST STM32F411CEU6 microcontroller, a Maxic MT5705 Qi
receiver and a small LiPo cell; the boards expose SWD and boot-strap pads (SWDIO, SWCLK, BOOT0,
BOOT1, RST). Modules have no radio and no FCC ID of their own; they talk to the keyboard through
the dock contacts DOC1. The module microcontrollers moved from Holtek parts to the STM32F411
during development, for its resources, drivers, no license fees and machine-learning support
(Kickstarter update 13, 2024-05-08) DOC2. Hardware details: Tune,
Touch, Track, Module dock.
Module firmware lives in the STM32's internal flash; no external memory was seen on the module boards DOC INFERRED1.
The module's bootloader is unidentified. Module images are not MCUboot images, yet
NayaCore names module restart modes M_NORMAL_RESTART, M_MCUBOOT_RESTART and M_CLEAR_REMAP_FLASH
next to MCUBootWorker_%1_PortTwo_Module. Whether any key signs the module apps is open
STATIC OPEN4 (open question).
The bundle¶
From NayaFlow 1.11.0, every stable release ships module firmware as FlashMemory.bin
(Qt resource m_fw/): a 1 MiB (1 048 576-byte) LittleFS v2 image with 4 096-byte blocks (superblock
at offset 0x08). It is not an MCUboot image: no MCUboot magic, no TLVs, no swap trailer
STATIC6.
It contains one encrypted app per module type (Touch_UserApp.sfb, Track_UserApp.sfb,
Tune_UserApp.sfb, Float_UserApp.sfb, Query_UserApp.sfb); from 1.15.0 a <App>_HASH file beside
each app (the SHA-256 of the .sfb, described as the value the Create reports for the app); and
from 1.14.3 a VERSION file of four bytes, 00 MM mm pp STATIC6.
The .sfb payloads are encrypted (entropy about 7.98, no Cortex-M vector table): their
names and hashes are readable, their code is not STATIC6.
Float looks unreleased and Query deprecated or experimental, but both ship as apps in every bundle; the Float app kept being rebuilt (157 184 bytes in 1.14.x, 85 440 in 1.17.2 to 1.21.0, 85 312 in 1.25.x) STATIC6.
.sfb sizes in bytes STATIC6:
| Bundle | Touch | Track | Tune | Float | Query |
|---|---|---|---|---|---|
| 1.25.x (2.3.3) | 46 176 | 61 056 | 97 440 | 85 312 | 41 088 |
| 1.17.2 to 1.21.0 (2.3.2) | 46 304 | 61 184 | 97 728 | 85 440 | 41 216 |
Versions¶
Module firmware by stable release: 1.11.x unknown (no VERSION file yet); 2.1.1 in 1.14.3;
2.1.2 in 1.14.5; 2.2.0 in 1.15.0 and 1.15.1; 2.3.2 in 1.17.2 to 1.21.0; 2.3.3 in 1.25.0 and 1.25.1.
On the beta channel 2.3.2 is first carried by beta 1.17.1 (2026-02-11; the 1.16.0 note announces it,
but betas 1.16.0 to 1.17.0 carry 2.2.0) and 2.3.3 by beta 1.23.0 (2026-05-21) STATIC DOC671013.
What each version changed is on Versions.
Where the keyboard keeps the bundle¶
The keyboard keeps the bundle on the left half only, in a 1 MiB LittleFS partition
(candidate name M_Firmware), written by an SMP upload to direct id 4 (slot3_partition) while the
left half is in its bootloader. It is not an MCUboot slot and image slot info does not list it. The
right half has no module store: its flash self-test reports three partitions, the left's five
STATIC MEASURED8 (owner's board, 3.41.0, 2026-09-10 and 2026-09-16). See
Flash layout.
de/100a MODULE_FILE_FW_VERSION reads the stored bundle's VERSION from the left half with
no module docked: 00 00 02 03 03 (2.3.3) on a board updated by NayaFlow 1.25.1. The right half never
answers it MEASURED (owner's board, 3.41.0, 2026-09-16).
de/1008 GET_MODULE_FW_VERSION reads a docked module's own running firmware on the half
it is docked on, either half. The reply is 00 <addr> <flag> 00 <major> <minor> <patch>: status
00, the dock address, a flag byte, then the version with the vendor's leading zero. The right half
has answered 00 21 00 00 02 03 03 (a Track on 2.3.3) and 00 11 00 00 02 01 02 (a Touch on 2.1.2),
and while a module had not reported yet the flag read 01 with an all-zero version
(00 21 01 00 00 00 00) MEASURED (owner's board, 3.41.0, 2026-09). The nayactl maintainer
reports the same layout REPORTED19. Details on Modules.
How NayaCore updates a module¶
Module updates: tested by us, and two ways they went wrong
Safety level HIGH (it rewrites the left half's module store and then reprograms a module).
TESTED by us on the left half, 3.41.0, from 2026-09-23: NayaFlow's update and forced update, and
OpenFlow's. The de/1005 byte must match the module that is physically docked: 03 sent to a
Tune programmed it with the Track's app and left it dark until a forced update with 02 put the
Tune's app back. One Track update never finished: the keyboard hung until it was power cycled, and
from then on that Track answered like an empty bay, cause unknown
MEASURED20. Do not send de/1006 by hand: its payload is not known with certainty.
fe/1003 takes only 00 (off) or 01 (on) as its data byte and is NayaCore's rescue for a
critically drained module STATIC4 (SYSTEM commands).
NayaCore's module update sequence, from the strings of Naya_DeviceManager_ModuleFwUpdate.cpp
and our capture of NayaFlow 1.25.1 updating a Touch (left half, 3.41.0, 2026-09-23)
STATIC MEASURED4820:
- verify the file and read the stored bundle's version (
de/100a); if it already matches, skip the upload (steps 2 to 4); - put the left half into its bootloader (
ee/10ae); - upload
FlashMemory.binto the modules target, id 4 (uploadImageToModulesSlot=uploadImageToSlot(path, 4)), in 512-byte chunks; - let the half restart by itself about a second after the last chunk: NayaCore sends no
os reset, and there is no mark step and no swap (it is a filesystem); - send
de/1005MODULE_FWUP with one byte naming the docked module type (01Touch,02Tune,03Track); the keyboard acknowledges at once, programs the module from its own store (the module's LEDs go out for 10 to 15 s) and restarts about 32 to 40 s after the command; - compare the module's reported version (
de/1008) with the bundle'sVERSION.
On the owner's board the half came back on USB after each of those self-restarts only once its cable was unplugged and plugged in again (2 of 2 runs); NayaCore checks its step timeout only on a device event, so a run can sit until the replug MEASURED20.
NayaCore's step names for it are ModuleFW_FileVerification, ModuleFW_FileVerificationPostUpload, ModuleFW_Update, ModuleFW_VersionCheck, ModuleFW_Touch_Upload, ModuleFW_Track_Upload, ModuleFW_Tune_Upload and ModuleFW_Read_Upload. It can force an update to Touch, Track or Tune, or read the docked type ("Unknown, waiting for timeout" when none is docked); on a mismatch it logs "Module firmware version image does not match stored module firmware version for device ..." STATIC4.
de/1005 MODULE_FWUP takes one byte: NayaCore's validation requires a size of 1, and an
invalid type falls back to AUTO_DETECT STATIC4. The byte is 01 for a Touch, 02 for a
Tune and 03 for a Track: NayaCore builds it as a one-byte array in each forced branch, and all three
were captured on the wire from NayaFlow on 2026-09-23 STATIC MEASURED20.
It is neither the dock address nor nayactl's module-type numbering (1 Touch, 2 Track, 3 Tune)17:
03 sent to a Tune programmed it with the Track's app, and the Tune came back dark, reporting dock
address 0x4A, until a forced 02 restored it MEASURED20.
Host events: NayaFlow sends the ZMQ event update_module_fw, and NayaCore also accepts
force_touch_start, force_track_start and force_tune_start STATIC4. The old text commands
manual_fw_update_touch, _track and _tune and force_touch_start belonged to the engineering
text channel, retired in 3.31.1; every text command returns nothing on 3.41.0 MEASURED DOC18.
NayaFlow's forced update works on 3.41.0: Force Update to Tune restored the Tune above, and
Force Update to Track was captured on the wire MEASURED (left half,
2026-09-23)20.
An interrupted bundle upload would leave the modules partition partly erased and the keyboard firmware untouched, because the keyboard's image slots are not involved INFERRED.
The vendor's rules for module updates (NayaFlow text): only one up-to-date left half, with the module docked; the module switched on; a module can take up to 7 s to appear, and a Tune up to a minute the first time; a forced update can take up to 4 minutes. "Force update", in the Danger Zone, is meant for a module that does not answer after Battery Recovery was tried: pick the module type that is physically docked, and expect the keyboard and the module to restart several times DOC5. More on the host side: NayaFlow and NayaCore.
Force Module Update¶
Force Module Update arrived with NayaFlow 1.15.0, NayaCore 5.8.1, keyboard 3.29.1 and modules 2.2.0. The vendor's help page "Force Flashing Naya Modules" is no longer reachable; only its first line ("NayaFlow v1.16.x to 1.17.x and later") survives in the Wayback Machine DOC1114.
Vendor fixes to the update path: 3.29.1 "Module update method to prevent wrong firmware from being installed"; NayaCore 6.1.3 handles a corrupted module firmware file on the Create; 3.40.4 module updates no longer fail unless the LED effect is Solid DOC1013.
Module recovery mode¶
Module recovery mode is a System-layer key: LA5 is labeled "Module Force Charge" in the
Windows default layout and "Module Recovery Mode" in the macOS default (manual v1.1.0, pp. 20 and 23)
DOC15. On the wire it is a record of type 06 with parameter 401 (MODULE_FORCE_CHARGING);
the stock System layer holds 3e 06 04 91 01 00 00 at position 62 MEASURED (owner's board, 2026-09-08).
Recovery key: Safety LOW, tested on 3.28.7 with modules on 2.1.2
It changes nothing stored. While it runs, that half's LEDs are out and the module does not work; it ends by itself. TESTED by us on 3.28.7 with modules on 2.1.2 (2026-09-19); untested on 3.41.0 and module 2.3.3.
Behavior, measured on 3.28.7 with modules on 2.1.2: the key acts only on the half it is pressed on (bind one per side); it puts that half's LEDs out and force-charges its bay; the module does not work while the mode is active; the mode times out on its own, so it cannot run overnight (Qi charging is the long-charge option); a restart clears it MEASURED (owner's board, 2026-09-19).
On 2026-09-19 recovery mode revived a Tune and a Touch that no longer answered: the Tune's LED came back and the Touch moved the cursor. Hours on a Qi charger had not helped MEASURED.
The module manuals (v1.1.0): a module that will not charge may need to be "manually recovered using the Recovery Mode" keybinding; modules left docked for a long time can drain until the battery protection trips and leaves them unable to charge until recovered by hand; undock modules for long storage; do not connect the Create directly to a wall outlet (charge from a computer, USB 3.0, 5 V 1 A) DOC1615.
The vendor's history of the problem: "Battery Zero", a rare bug that can drain modules beyond recovery. The vendor planned fixes in firmware plus minor hardware changes, and a recovery mode for drained modules (Kickstarter update 21, 2025-06-16); NayaFlow 1.3.8 shipped "module recovery support for battery Zero" DOC312.
A dead-module rescue command exists in the configuration protocol: SYSTEM fe/1003
MODULE_BATTERY_RECOVERY (NayaCore's MODULE_BAT_RECOVERY). It takes one data byte, 00 (off) or 01
(on), and NayaCore refuses anything else; we have never sent it. RESET_MODULE de/1006's payload is
unknown and never sent STATIC174 (SYSTEM commands).
Known module-firmware problems¶
A module at about 0 to 1 % on dock power reports dock address 0xF0 or 0xF1, firmware
0.0.0 and no battery until it charges past about 1 % REPORTED18. On the owner's boards 0xF0 means
that no module answered (3.41.0, 2026-09-03; 3.28.7, 2026-09-19), and OpenFlow treats it as "no
answer" MEASURED; the link to the battery level is the nayactl maintainer's.
Keyboard 3.28.7 with modules on 2.1.2: modules can end up with their LEDs off and not working while still powering the keyboard, the Tune especially; charging does not fix it; the recovery key did (above), and a later firmware plus Force Module Update is the vendor's cure MEASURED (owner's board, 2026-09-19).
Module 2.1.2 does not implement GET_PRECISE_BATTERY (de/100b: about 1.09 s timeout per
request; de/1009 works), and a Tune on 2.1.2 colors only 6 of its 24 bay LED indices (88 to 93; 94 to
111 stay white) MEASURED (owner's board, 2026-09-19).
The vendor's module battery history: 3.35.4 module battery management; NayaCore 6.1.3
corrected battery reporting; 6.6.1 re-enabled module battery reading; NayaFlow 1.20.0 shows module
battery percentages, with "fluctuations between 5 and 10 percent"; module 2.3.3 disabled the battery
blink DOC1013. The percentage NayaFlow shows is
(clamp(mV, 3300, 4200) - 3300) * 100 / 900, truncated, then clamped to 1-100 %, from the de/100b
millivolts STATIC (NayaCore 6.11.0, Naya_Device::getModuleBatteryPercentage)4:
9 mV per point. Readings and calibration points: Power and batteries.
Before the bundle: d_fw.bin¶
Before the bundle (NayaFlow 0.1.0 to 1.6.10) the releases shipped d_fw.bin, an MCUboot
image signed with the keyboard key: dial or dongle firmware, open. From 1.11.0 create-legacy-firmware
describes the dial as folded into the modules bundle STATIC OPEN69
(Images).
The vendor's help pages¶
The vendor's help center had module troubleshooting pages whose bodies are lost: "Force Flashing Naya Modules", "Module no longer turns ON: Battery Recovery", "Modules not outputting anything after NayaCreate update" and "Naya Create not typing after update". Only their titles and first lines survive in the Wayback Machine's index DOC14.
Tested and untested¶
A module firmware update has been run on hardware by us: NayaFlow 1.25.1 updated a Touch from 2.1.2
to 2.3.3 in the left bay of a half on 3.41.0, captured end to end (2026-09-23), and OpenFlow's own
module update, now part of the app, has flashed modules on the owner's boards since, including a
Track downgrade that read back as a correct Track MEASURED20. Tested: the recovery key (3.28.7 with modules on 2.1.2), the
version reads (de/100a, de/1008).
Open questions¶
- OPEN RESET_MODULE's (
de/1006) payload as NayaCore sends it; MODULE_BATTERY_RECOVERY (fe/1003) has never been captured. - OPEN The module microcontroller's bootloader, and whether any key signs the
.sfbapps (details). - OPEN Whether recovery mode behaves the same on 3.41.0 with modules on 2.3.3, and how long it runs before it ends (details).
- OPEN Whether a Tune on 2.3.3 drives more than 6 of its 24 bay LED indices.
- OPEN The module version of the 1.11.x bundle (details).
- OPEN Whether
d_fw.binis dial or dongle firmware (details). - OPEN Why one Track update hung the keyboard and left the Track answering like an empty bay, and whether every half needs a cable replug to come back after a module update's restarts (details).
Sources¶
-
Parts list: FCC internal photos of the Tune, Touch and Track boards (exhibits of 2BQ4V0825CRL and 2BQ4V0825CRR). ↩↩
-
Kickstarter update 13, 2024-05-08. ↩
-
Kickstarter update 21, 2025-06-16. ↩
-
NayaFlow 1.25.1, NayaCore 6.11.0 strings (module restart modes,
Naya_DeviceManager_ModuleFwUpdate.cppstep names and messages,MODULE_BAT_RECOVERY, ZMQ events) and macOS symbols and code (Naya_Device::getModuleBatteryPercentage, our disassembly, 2026-09-23). ↩↩↩↩↩↩↩↩ -
NayaFlow 1.25.1, renderer strings (Hardware Manager module-update texts). ↩
-
create-legacy-firmware,
FIRMWARE-HISTORY.mdand the per-releasemanifest.jsonfiles (module bundle entries and.sfbapps). ↩↩↩↩↩↩↩ -
create-legacy-firmware,
firmware-history-beta/MANIFEST.json(theVERSIONfile of each beta bundle). ↩ -
create-legacy-firmware,
FLASHING-PROCEDURE.md, "Slot ids, vendor-exact". ↩↩ -
create-legacy-firmware,
FLASHING-PROCEDURE.md, "Firmware bundle layout". ↩ -
create-legacy-firmware,
CHANGELOG.md(vendor release notes). ↩↩↩ -
create-legacy-firmware,
CHANGELOG.mdL331-L351 (Force Module Update in NayaFlow 1.15.0, NayaCore 5.8.1, keyboard 3.29.1, modules 2.2.0). ↩ -
create-legacy-firmware,
CHANGELOG.mdL421 (NayaFlow 1.3.8). ↩ -
Vendor release notes of the beta channel, NayaTech/NayaFlow-beta-releases. ↩↩↩
-
Wayback Machine index of the vendor's help center, help.naya.tech captures (titles and first lines only). ↩↩
-
Naya Create User Manual v1.1.0, pp. 11, 20 and 23; see Manuals. ↩↩
-
Naya Touch, Tune and Track User Manuals v1.1.0, pp. 5 and 7; see Manuals. ↩
-
nayactl, github.com/Qonfused/nayactl (
constants.py: module types,fe/1003,de/1006). ↩↩ -
nayactl, pull request #2, comments by the maintainer (text commands silent on 3.41.0; a module at 0 to 1 % reporting
0xF0). ↩↩ -
nayactl, pull request #5 (module version replies). ↩
-
create-legacy-firmware,
FLASHING-PROCEDURE.md, "Module firmware update, captured from NayaFlow (2026-09-23)". ↩↩↩↩↩↩↩