Skip to content

ATK ZERO (0x373b:0x1154 / 0x1155): a second ATK protocol the 0xff02 filter cannot reach #48

Closed

Description

@itsnttt

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. GetDeviceType answering
    72 00 3a 00 1a 01 | 03 | ff ff 00 00 00 01 01 01 08 07 04 where only 03
    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 SetSensorAngle command exists; not exercised.
  • GetChipId (71) and GetMouseOnlineStatus (35) both answer zero on
    hardware that is plainly online.
  • cmdSn is a constant 0x3a in 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:

  1. Where the codec belongs. I put it at src/bitmouse/ on the reading that
    this is a shared OEM family like src/compx/, not an ATK-only thing. If you
    would rather it live under src/atk/ as a second module, that is an easy
    move.
  2. 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 on 0xff05 and let the driver
    sort it out at runtime, say so — I deliberately avoided claiming models I
    cannot test.

Activity

  1. itsnttt commented on Sep 5, 2026

    @itsnttt
    ContributorAuthor

    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, answered
    the read-back with it, and left the sensor where it was. Read-back verification
    passed throughout; only moving the pointer showed it. enable is 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 silentHeight byte (which reads zero) but `offsetCalibration

    • 1, on the same register scale the A9 family already uses in src/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) and
    GetMouseOnlineStatus (35) both answer zero on hardware that is plainly online;
    cmdSn is a constant 0x3a in 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 under src/atk/, and whether the filter should stay
    PID-scoped. Opening the PR now so there is something concrete to react to; happy
    to move either.

  2. snekxs commented on Oct 10, 2026

    @snekxs
    Member

    Closing: the ATK ZERO (0x373b:0x1154 wired / 0x1155 receiver) is supported on main via #58 (bitmouse: support the ATK ZERO on wired and receiver transports). The codec lives in src/bitmouse/, the driver is AtkBitmouseHidClient (registered in src/drivers/registry.ts), and the picker filters are PID-scoped through BITMOUSE_PRODUCT_IDS in vendors.ts, which resolves the 0xff02 visibility problem described here. Thanks @itsnttt for the hardware-verified work. Lift-off, angle snapping and sampling mode were decoded after the original post; GetChipId, GetMouseOnlineStatus and cmdSn remain undecoded. Open a new issue if you want to track any of those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions