Repository navigation
Description
The device
| USB id | Firmware | Path | |
|---|---|---|---|
| ATK ZERO, wired | 0x373b:0x1154 |
mouse 3.0.3 | direct |
| ATK ZERO, 8K receiver | 0x373b:0x1155 |
mouse 3.0.3, receiver 3.0.2 | via receiver |
Sensor PAW3950 Ultra. Both report cid,mid = 1,1.
Why it is invisible today
SUPPORTED_HID_FILTERS asks ATK for { 0x373b, usagePage 0xff02, usage 2 }, and
AtkHidClient.isSupported requires the same collection. An ATK ZERO exposes no
0xff02 at all. Its collections, read from the device:
0xff00 / 0x0001 feature #3, 63 B
0xff01 / 0x0001 feature #4, 63 B
0xff05 / 0x0001 input #8 + output #8, 63 B <- the config channel
So the mouse never reaches the browser's device picker, and vendor id alone was
never enough to conclude coverage of this family.
The protocol
ATK's current configurator calls the family BITMOUSE. It is unrelated to the
16-byte EEPROM protocol in src/atk/ — 63-byte frames on report 0x08:
request [0] checksum = Σ bytes 1..62 & 0xff
[1] 0x72 [2] paramLen [3] cmdSn [4] target
[5] commandId [6] cmdLen [7..] payload
reply no checksum byte, so fields sit one earlier:
[0] 0x72 [1] status (0xff = error) [2] cmdSn [3] target
[4] commandId [5] payload length [6..] payload
A receiver answers its own commands on target 0 and relays mouse commands on
target 1.
Two firmware behaviours that bit me, both now pinned by tests:
- Replies are not zero-padded. Bytes past the reported length are the
previous exchange's leftovers, e.g.GetDeviceTypeanswering
72 00 3a 00 1a 01 | 03 | ff ff 00 00 00 01 01 01 08 07 04where only03
is valid. - The DPI table's count byte is the table size, not the enabled count. It
reads 8 on a mouse holding two configured stages; empty slots read 0 DPI while
keeping stale colour bytes.
Verified on hardware
Reads on both transports: version, cid/mid, device type, battery, charging
state, config block, DPI stage table.
Writes, each confirmed by reading back and then restored: polling rate
2000 → 1000 Hz, DPI 800 → 1600, debounce 4 → 8 ms, sleep 1800 → 600 s, motion
sync and ripple control on → off.
What I could not establish
- Lift-off distance. The config byte reads 0 on both transports, and the
vendor's own accessors overlap a 16-bit DPI value at +3 with an 8-bit lift-off
byte at +4. I left it unreported rather than guess. - Angle snapping. A
SetSensorAnglecommand exists; not exercised. GetChipId(71) andGetMouseOnlineStatus(35) both answer zero on
hardware that is plainly online.cmdSnis a constant0x3ain every reply captured; meaning unknown.
Evidence
Framing, command ids, field offsets and request lengths came from the vendor's
own WebHID configurator (ATK HUB v3.2.21, served publicly from hub.atk.pro),
and every value used was then confirmed against the device. No vendor code is
copied.
The mouseCidMid command (74) is the same identification route as the open
#44, approached from this protocol's side.
Proposed shape, before I open a PR
A codec under src/bitmouse/ (framing, command table, config and DPI decoders,
sensor DPI ranges, product table), a driver at
src/drivers/atk/bitmouse-hid.ts, registry entry, and PID-scoped filters. Only
the two verified products are claimed; the same protocol drives much of the
current ATK/VXE range, but each model wants its own hardware check.
Branch, if it is easier to read than prose:
https://github.com/itsnttt/mouse-protocol/tree/bitmouse-atk-zero
npm run check is clean there (843 tests), and openmouse builds and passes
its 112 tests against the local package. registry.test.ts needed 0xff05
added to USAGE_PAGES, exactly as its own assertion message instructs.
Two things I would rather agree before opening the PR:
- Where the codec belongs. I put it at
src/bitmouse/on the reading that
this is a shared OEM family likesrc/compx/, not an ATK-only thing. If you
would rather it live undersrc/atk/as a second module, that is an easy
move. - How far to claim. I scoped the product table to the two ids I own. If you
would prefer the filter to be vendor-wide on0xff05and let the driver
sort it out at runtime, say so — I deliberately avoided claiming models I
cannot test.
Activity
itsnttt commentedon Sep 5, 2026
Update: the driver is written, and driving it from the OpenMouse app rather than
from unit tests changed two of the answers above.
A DPI write stored the value and never applied it. The stored stage record is
eight bytes and a write prefixes it with the stage index, so the write byte that
lines up with the record's last byte is a literal zero at +8 — and
enable, at+9, has no counterpart in the table at all. Reading that last byte back as an
enable flag meant every write sent
enable=0: the mouse stored the DPI, answeredthe read-back with it, and left the sensor where it was. Read-back verification
passed throughout; only moving the pointer showed it.
enableis a command bit,true when the stage written is the active one.
Lift-off is a range, not three stops. I had it as unknown above. It is not
the config block's
silentHeightbyte (which reads zero) but `offsetCalibration, on the same register scale the A9 family already uses insrc/atk/index.ts`— tenths of a millimetre offset by six. Codes 1 to 11 are 0.7 mm to 1.7 mm,
matching the slider the vendor software shows.
Also now decoded and exercised: straight line correction (which is angle snapping
under the vendor's name, command 11), sensor sampling mode (address 74, command
31) and Ultra Long Range (address 75, command 27).
Everything in the driver is now verified on an ATK ZERO, over its receiver and
wired, each write confirmed by read-back and then restored. Lift-off and angle
snapping were additionally checked by hand — lifting the mouse, and dragging it
slowly — rather than by read-back alone, given the defect above.
Still not decoded, and left that way:
GetChipId(71) andGetMouseOnlineStatus(35) both answer zero on hardware that is plainly online;cmdSnis a constant0x3ain every reply captured; sensor angle (command 33,address 2692) is located but not implemented.
The two questions in the original post still stand — whether the codec belongs at
src/bitmouse/or undersrc/atk/, and whether the filter should stayPID-scoped. Opening the PR now so there is something concrete to react to; happy
to move either.
snekxs commentedon Oct 10, 2026
Closing: the ATK ZERO (
0x373b:0x1154wired /0x1155receiver) is supported onmainvia #58 (bitmouse: support the ATK ZERO on wired and receiver transports). The codec lives insrc/bitmouse/, the driver isAtkBitmouseHidClient(registered insrc/drivers/registry.ts), and the picker filters are PID-scoped throughBITMOUSE_PRODUCT_IDSinvendors.ts, which resolves the0xff02visibility problem described here. Thanks @itsnttt for the hardware-verified work. Lift-off, angle snapping and sampling mode were decoded after the original post;GetChipId,GetMouseOnlineStatusandcmdSnremain undecoded. Open a new issue if you want to track any of those.