USB¶
This page covers what a Naya Create half (and the Speedlink dongle) looks like on USB in every mode: vendor and product ids, the brief bootloader pass at every power-on, the interfaces each firmware exposes, which port answers the configuration protocol, the events that re-enumerate a half, and the host-side behavior that trips up tools. The one thing to know first: a half that shows its bootloader product id for a second or two is booting normally; only a half that stays there is parked in MCUboot.
Unless another source is named, MEASURED means measured on the owner's board, with the keyboard firmware and date given beside the tag. Byte strings follow the byte convention. Serial numbers and addresses are never reproduced.
At a glance
- Vendor id
0x37D1; application product ids: left0x0064, right0x00C8, dongle0x012C. - Every power-on passes through MCUboot for about 1-2 s (left
0x006F, right0x00D3). - On 3.41.0 the left half is CDC + HID and the right half is CDC only; on 3.28.7 each half has two CDC interfaces and only one answers.
- One program at a time can hold a half's port: quit NayaFlow before running another tool.
- On USB the half's own power switch (OFF, then ON) resets it.
Ids at a glance¶
Every Naya USB device uses vendor id 0x37D1. In application mode the left half is product
id 0x0064 (decimal 100), the right half 0x00C8 (200) and the dongle 0x012C (300)
MEASURED 3.41.0; dongle 2026-09-11
STATIC1.
NayaCore 6.11.0 decides what a Create device is from its product id alone
(Naya_Device::setCreateFlashGenerationFromPid): it masks the id with 0xEFFF and accepts exactly
two families of three, and bit 0x1000 marks flash generation B, whose firmware images (_64) are
not interchangeable with generation A STATIC3. Only four of
these ids have ever been seen on hardware MEASURED.
pid & 0xEFFF |
Half | Mode | Generation B id | Seen on hardware | Evidence |
|---|---|---|---|---|---|
0x064 |
left | application | 0x1064 |
yes (0x0064) |
STATIC MEASURED |
0x06F |
left | MCUboot | 0x106F |
yes (0x006F) |
STATIC MEASURED |
0x07A |
left | third mode | 0x107A |
never | STATIC |
0x0C8 |
right | application | 0x10C8 |
yes (0x00C8) |
STATIC MEASURED |
0x0D3 |
right | MCUboot | 0x10D3 |
yes (0x00D3) |
STATIC MEASURED |
0x0DE |
right | third mode | 0x10DE |
never | STATIC |
| (outside the table) | dongle | application | none | yes (0x012C) |
MEASURED 2026-09-11 |
| (outside the table) | dongle | boot pass | none | yes (0x0137) |
MEASURED 2026-09-11 |
The third mode is unnamed in NayaCore. Nordic's own DFU bootloader would enumerate under Nordic's
vendor id, so calling it "DFU" is only a guess; create-legacy-firmware's product-id table labels 0x07A /
0x0DE "DFU", while this site says "third mode"3. No generation-B id has ever been seen.
See Bootloader for what each mode does.
The boot pass¶
Every Naya device first enumerates at its application product id plus 0x0B (left
0x006F, right 0x00D3, dongle 0x0137) and then re-enumerates at its application id. On the halves
that first identity is MCUboot: each power-on passes through it for about 1 to 1.7 s (donor halves
1.6-1.7 s; on a 3.41.0 board the right half showed 0x00D3 for 0.98 s). Switching only the right half
on also rebooted the untouched left half through MCUboot (0x006F for 1.40 s, starting 2.2 s after
the right), because the central reboots when its peer re-links. In a single observation the half's LED
changing from green or red to white marked the re-enumeration
MEASURED 2026-09-10, 2026-09-11, 2026-09-19, 2026-09-22.
So a tool should report a half as "in the bootloader" only if it is still at the MCUboot id a few seconds later (OpenFlow waits 3 s); a one-shot scan can catch a normal boot pass MEASURED INFERRED (the waiting policy is our reasoning from the timings above).
Timeline of a power-on, from the measurements above:
| Event | Left half | Right half | Evidence |
|---|---|---|---|
| Power on (switch or plug) | 0x006F for about 1-1.7 s, then 0x0064 |
0x00D3 for about 1-1.7 s (0.98 s once), then 0x00C8 |
MEASURED 2026-09-10 to 2026-09-22 |
| Only the right half switched on | reboots through 0x006F (1.40 s), starting about 2.2 s after the right |
normal boot pass | MEASURED 2026-09-11 |
| Peer re-links (either half) | the central (left) drops off USB for about 2 s and passes through MCUboot again | MEASURED 2026-09-22 (see Split link) | |
| Switch OFF then ON while on USB | the half shuts off, then resets | same | MEASURED 2026-09-23 |
The power switch works on USB. Switching a half OFF while it is connected over USB shuts it off, and switching it back ON works as a reset, so on USB the switch alone gives a reset; unplugging is not needed for one MEASURED owner's board, 2026-09-23. This agrees with the manual ("Create will respect the ON/OFF state even while connected over USB") DOC13. Whether the half's MCUboot boot pass was seen on that reset was not recorded (OPEN). A reset does not erase stored settings, so whether a switch reset clears a stuck LED or settings state is a separate question.
Interfaces per firmware and mode¶
On 3.41.0 with both halves on USB, the left half exposes MI_00 CDC plus MI_02 HID
(keyboard, mouse, consumer), and the right half exposes MI_00 CDC only, with no HID: the right half's
keys travel over the split link and reach the host through the left
MEASURED 3.41.0, 2026-09-01. This matches the Kickstarter campaign's
description of wired mode, in which the halves still link over RF and only the left half sends data
over USB DOC11. A right half alone on USB was not recorded.
On 3.28.7 each half exposes two CDC interfaces at once (right MI_00 + MI_02, left
MI_00 + MI_03), both healthy and sharing one USB serial number, and only one of them answers the
protocol: on the donor board the right half answered on MI_02 and the left on MI_03, neither on
MI_00, and which one answers is not predictable. 3.41.0 exposes one. A tool must try each interface
of the same serial number and remember which one answered
MEASURED 3.28.7, 2026-09-19.
A half sent into MCUboot by ee/10ae enumerates at its MCUboot id with two CDC ports,
neither labeled: a data port that answers SMP and a log port that accepts the open and silently
swallows frames (a reset sent there does nothing). It stays there (over a minute measured; it does not
time out) until an SMP os reset on the data port (back in the application in about 8 s) or a power
cycle (on USB the half's own switch is enough, as above)
MEASURED 3.35.4 and 3.41.0, 2026-09-16, 2026-09-204.
See Bootloader.
ee/10ae parks a half in MCUboot
The half stops typing and lighting until an SMP os reset reaches its data port or it is power
cycled. It is the first step of every stock update, so it is recoverable, but send it only with
an SMP client ready; ee/10be is never to be sent. See the
never-send list.
| Firmware / mode | Left half | Right half | Evidence |
|---|---|---|---|
| 3.41.0, application, both on USB | MI_00 CDC + MI_02 HID |
MI_00 CDC, no HID |
MEASURED 2026-09-01 |
| 3.35.4, application | not recorded | not recorded | OPEN |
| 3.28.7, application | MI_00 + MI_03 CDC (one answers) |
MI_00 + MI_02 CDC (one answers) |
MEASURED 2026-09-19 |
| MCUboot (any) | two CDC ports: SMP data port and log port | same | MEASURED 2026-09-16, 2026-09-20 |
| Dongle, application | CDC communication + CDC data (IAD composite) | MEASURED 2026-09-11 |
Descriptors: product strings "Naya Create Left" / "Naya Create Right", interface strings
"Left Keyboard" / "Right Keyboard". The half's USB serial string equals the reply to fe/1004 and
NayaCore's hardware id (per device, not reproduced) MEASURED 3.41.0.
USB endpoints: host frames go OUT on bulk endpoint 0x01 (11-byte frames for commands with
no data); replies come IN on bulk 0x82, which USBPcap logs one byte per transfer, so a capture must
concatenate the IN stream before it can split frames
MEASURED 3.41.0, 2026-09-11.
Whether the keyboard's own USB HID interface offers the boot protocol is not known; over Bluetooth the keyboard offers report protocol only (see Bluetooth) INFERRED OPEN.
Which port answers what¶
The application CDC port answers the binary protocol (fe/1001 opens it; see
Transport). On the right half it answers system, Bluetooth and module
reads but no REMAP command, because the right half holds no keymap, LED or module stores. On 3.28.7
only one of the two ports answers. In MCUboot only the data port answers, and only SMP. The text
channel is dead on 3.41.0 MEASURED 3.41.0, 3.28.7.
The configuration protocol and the firmware-update protocol (SMP) share the same CDC port; SMP answers only while the half sits in MCUboot STATIC. The configuration channel stays up when the keyboard's output is switched to Bluetooth with the cable in MEASURED 3.41.0, 2026-09-10. So a tool needs the cable, not USB output mode.
NayaCore gives each half's ports roles: Broker, ProtocolCDC (binary), SystemCDC (text), MCUBootPort1 and MCUBootPort2. It adds a port directly as ProtocolCDC when the firmware version is known and otherwise brokers it; a ProtocolCDC worker that does not register within 15 s is quit. It expects exactly two MCUboot ports per half ("duplicate MCUBoot port types", "Log port detected, polling serial for boot log until data port worker finishes.") STATIC7.
NayaCore's device types are CreateLeft, CreateRight, Dongle, ModDock, TypeLeft, TypeRight and TypeOne STATIC7; the last four match later product names INFERRED. It keys a device by hardware id and checks it against each port's USB serial ("HWID mismatch - provided port serial (%1) does not match device HWID (%2)") STATIC7.
NayaCore picks the firmware generation from the product id (from NayaFlow 1.25.0) and refuses to flash bundled firmware when the generation is unknown ("Conflicting Create flash generation for device %1: observed PID 0x%2 does not match stored generation") STATIC76.
Events that re-enumerate a half¶
Docking or undocking a module re-enumerates that half's USB: open handles go stale, and USB addresses change on every re-enumeration MEASURED 3.41.0. NayaFlow relaunches itself when a module is re-docked (see Modules).
A half sitting in MCUboot neither types nor lights and looks bricked, although its application image is untouched. Conversely, a half whose USB data link has failed can keep its LEDs lit from its internal cell and look alive MEASURED 2026-09-20. See Recovery.
Other re-enumerations: every power-on and every peer re-link (the boot pass above), ee/10ae (into
MCUboot) and SMP os reset (back to the application).
Identity for tools¶
Key each half by its USB serial string, not by side: two attached Creates are two
legitimate left halves. Join two halves into one keyboard only when the claim is mutual (each half's
be/1002 pair address equals the other's be/1008 own address); this works even when the halves run
different firmware and cannot talk, because the pairing record survives while the link is dead
MEASURED two boards attached, 2026-09-20. Some older firmware may
report no USB serial (which one is not recorded); a tool then falls back to side and product id
INFERRED. A donor board's right half enumerated 1.7 s before its
left, so order halves by side, not by arrival MEASURED 2026-09-20.
Host notes¶
One program at a time. A CDC port can be opened by one program. NayaFlow (its NayaCore process) holds both ports while it runs, so quit it (or use OpenFlow's release button) before another tool. On Windows the second opener gets "Access is denied"; a killed script's leftover Python process can hold the port and look like a dead board MEASURED 3.41.0.
Windows serial settings. With pyserial dsrdtr=True, Windows enables DSR output flow
control; the Create never raises DSR, so the first write blocks forever. Open with dsrdtr=False and a
write timeout (fixed upstream in nayactl PR #22). The baud setting is nominal (see
Transport) MEASURED 3.41.0, Windows.
nayactl then asserts DTR and RTS, which is its reading of NayaCore's own port setup
STATIC15.
More Windows behavior. A port whose device detached uncleanly stays listed ("ghost") and looks live until opened; a half that dropped off USB came back on a new COM number. After a power cycle a held handle is still "open" but writes fail with "The device does not recognize the command" (reopen it). The bootloader's CDC does not support comm-state queries, so resetting its input buffer fails with "ClearCommError failed" (drain by reading instead), and Windows can refuse the bootloader port for a moment right after it enumerates MEASURED 2026-09-08, 2026-09-10.
In one setup a USB hub gave the halves and the dongle power but no data (nothing enumerated); captures were then made on a motherboard port. Single observation MEASURED 2026-09-11.
Captures. A USB capture of the keyboard includes its HID interface and so records everything typed during the capture INFERRED; USBPcap must run from an already elevated shell MEASURED.
Linux. Halves appear as /dev/ttyACM* owned by dialout (uucp on Arch) and cannot be
opened by a normal user without a udev rule, and ModemManager probes new CDC ports for seconds after
plug-in REPORTED1. OpenFlow ships the rule below
STATIC9. None of this has been measured on a Linux machine
with a keyboard attached: the port ownership is what nayactl's README and OpenFlow's rule header
state, and that the rule fixes both problems is inferred (it was tested in packaging only)
INFERRED. A read-only check on a Linux host settles it
(open questions).
On Linux a half in its recovery bootloader is also a CDC tty, so the same rule covers it STATIC.
# 70-openflow.rules (from OpenFlow)
ACTION!="remove", SUBSYSTEMS=="usb", ATTRS{idVendor}=="37d1", ENV{ID_MM_DEVICE_IGNORE}="1"
ACTION!="remove", SUBSYSTEM=="tty", ATTRS{idVendor}=="37d1", TAG+="uaccess"
macOS. macOS names a USB CDC port /dev/cu.usbmodem*, so each half's data port
should be one such node, and a half in MCUboot recovery should add a second, matching the two MCUboot
ports measured on Windows (see the MCUboot interfaces above) INFERRED
(not checked by us on a Mac; the two ports MEASURED Windows, 2026-09-16).
More per-platform notes are on Platforms.
| Symptom | Cause | Fix | Evidence |
|---|---|---|---|
| "Access is denied" / port busy | NayaFlow, another tool or a leftover process holds the port | quit NayaFlow or the other tool; end stray Python processes | MEASURED |
| First write never returns (Windows) | dsrdtr=True turns on DSR flow control |
open with dsrdtr=False and a write timeout |
MEASURED |
| Writes fail with "The device does not recognize the command" | handle opened before a power cycle or a module dock | reopen the port | MEASURED |
| "ClearCommError failed" on a bootloader port | MCUboot's CDC lacks comm-state queries | drain by reading | MEASURED |
| A half seems gone, then returns on a new COM number | unclean detach left a ghost entry | rescan ports | MEASURED |
| Two ports per half, one silent (3.28.7) | two CDC interfaces, one answers | try each interface of the same serial | MEASURED 3.28.7 |
A half sits at 0x006F / 0x00D3 |
parked in MCUboot (normal if it lasts only 1-2 s) | SMP os reset on the data port, or the switch |
MEASURED |
Permission denied on /dev/ttyACM* (Linux) |
dialout / uucp ownership |
udev rule above | REPORTED (nayactl README1); rule STATIC9 |
| Nothing enumerates through a hub | hub gave power only (one setup) | use a motherboard port | MEASURED once |
Cabling guidance from the vendor¶
The manual (v1.1.x) says to use a USB 3.0 port and the long end of the Y-cable, 5 V / 1 A (v1.0.6 said "USB 2.0 or newer"), and to update the halves one at a time on a standalone cable DOC1314. NayaFlow 1.25.1 asks for two direct USB-C to USB-C cables for updates and pairing ("Y-cables known to cause issues"), refuses to work with four or more halves connected, and targets "0 or 2 HWIDs" STATIC8. The manual adds that the keyboard respects its ON/OFF switch even while on USB (see the boot pass section) DOC13.
The dongle and community hardware on USB¶
The Speedlink dongle enumerates with vendor id 0x37D1, product id 0x012C (0x0137
during its boot pass), device class EF/02/01 (interface association, composite) with only a CDC
communication (02) and a CDC data (0a) interface and no DFU class, product string "Dongle", and
bcdDevice 0x0307 MEASURED 2026-09-11. The halves' bootloader
identities report the same 0x0307 (their applications 0x0300), so the value most likely reflects
the USB stack's default (Zephyr 3.7), not a dongle firmware version
INFERRED. It becomes a serial port but does not answer the halves'
opener (fe/1001 and fe/1002 at dst 0x50, three tries each), alone or with both halves on USB;
instead the port streams ASCII dots (2e: a burst of about 1 040 when the port opens, then three
every 200 ms) MEASURED 2026-09-11. No NayaFlow release we archive carries a dongle
image: 1.21.0 bundles only the two keyboard images and the module bundle, and 1.25.1 the four keyboard
images (generations A and B) and the module bundle STATIC5. The
createflow-dongle project reports the same frame protocol with no known address answering, an MCUboot
bootloader with a vendor key and no serial-recovery window, and the same absence of a dongle image
REPORTED10. That the dongle runs MCUboot is ours too: its
transient 0x0137 identity has a configuration descriptor byte-identical to the halves' MCUboot
identities MEASURED 2026-09-11, and NayaCore has a dongle bootloader
worker STATIC; the vendor key and the missing recovery window are untested
by us (open questions). The plug is USB-A: Kickstarter update 15 moved it from
a planned USB-C receiver DOC12. Its radio side is on
Bluetooth: the dongle's SAR report describes the radio only as "2.4G SRD" at 2 Mbps.
Hardware details are on Speedlink dongle.
A device with vendor id 0x1915, product id 0x520F ("createflow Dongle") is the community
createflow-dongle replacement stick (an nRF52840 USB stick acting as a Bluetooth-to-USB bridge for the
Create), not Naya hardware REPORTED (source checked)10.
Safety notes¶
Before you run a tool on a half
- Do not unplug a half while a tool is writing to it, and do not run two tools on one port.
- A half at an MCUboot id for more than a few seconds is parked, not broken: it needs an SMP
os reseton its data port (see Bootloader); a reset sent to the log port does nothing. - To reset a half on USB, its own switch (OFF, then ON) is enough; a recipe does not need "unplug, undock and switch" for a plain reset.
- On 3.28.7, if a write wedges a half (see Differences by firmware), unplug and replug it.
Open questions¶
- OPEN The third-mode ids
0x07A/0x0DEand every generation-B id have never been seen (details). - OPEN Interfaces of a right half alone on USB, and interfaces on 3.35.4 (details).
- OPEN Which firmware first lacks a USB serial number (details).
- OPEN What the dongle's CDC port answers, if anything, and whether only while bridging (details).
- OPEN Whether the dongle's MCUboot (its boot identity
0x0137) has a serial-recovery window (details). - OPEN USB boot-protocol support of the keyboard's HID interface (details).
- OPEN Whether a power-switch reset on USB shows the MCUboot boot pass (details).
- OPEN Linux behavior with a keyboard attached: nothing has been measured on a Linux machine (details).
Sources¶
-
nayactl, github.com/Qonfused/nayactl (
constants.pyvendor and product ids; README, Linux permissions). ↩↩↩ -
nayactl, pull request #2 (Windows serial fixes). ↩
-
create-legacy-firmware,
FLASHING-PROCEDURE.md, "Product ids, vendor-exact" (NayaCore'ssetCreateFlashGenerationFromPid; its table labels the third mode "DFU"). ↩↩ -
create-legacy-firmware,
FLASHING-PROCEDURE.md, "Measured on hardware, both halves (2026-09-20)" (commit cdd897c: two MCUboot ports,os resetto the data port, no narrow recovery window). ↩ -
create-legacy-firmware,
firmware-history/(v1.21.0 and v1.25.1 image sets). ↩ -
create-legacy-firmware, vendor release notes,
changelogs/(v1.25.0). ↩ -
NayaFlow 1.25.1, NayaCore 6.11.0 strings (static reading). ↩↩↩↩
-
NayaFlow 1.25.1 renderer and flow-bg-server strings (static reading). ↩
-
OpenFlow: its udev rule
70-openflow.rulesand port handling. ↩↩ -
createflow-dongle,
docs/findings.mdand its firmware README (third party, Apache-2.0). ↩↩ -
Kickstarter campaign page, naya-create/naya-create (2023), "Connectivity". ↩
-
Kickstarter update 15, 2024-09-03. ↩
-
Naya Create User Manual v1.1.x ("Turning Create ON/OFF", cabling); see Manuals. ↩↩↩
-
Naya Create User Manual v1.0.6, FCC ID 2BQ4V0825CRR user manual exhibits (fccid.io/2BQ4V0825CRR). ↩
-
nayactl, github.com/Qonfused/nayactl (
transport.py: opens withdsrdtr=False, then asserts DTR and RTS;docs/cdc-wire-format.md: NayaCore's serial configuration, DTR and RTS enabled). ↩