Skip to content

Add MCHOSE A7 V2 driver and MagDock lighting - #55

Merged
snekxs merged 1 commit into
OpenMouse-Project:mainfrom
jamezrin:add-mchose-driver
Sep 6, 2026
Merged

Add MCHOSE A7 V2 driver and MagDock lighting#55
snekxs merged 1 commit into
OpenMouse-Project:mainfrom
jamezrin:add-mchose-driver

Conversation

@jamezrin

Copy link
Copy Markdown
Contributor

Adds support for the MCHOSE A7 V2 family and for the MCHOSE MagDock charging base, both reverse-engineered from scratch and verified end-to-end against an A7 V2 Ultra+ over its 2.4 GHz receiver and over its cable.

The protocol

Two unrelated protocols live under the same vendor ID (0x3837), so they get two codecs and two drivers:

Mouse — feature reports 0x11 (20 bytes) and 0x12 (64 bytes) on usage page 0xff01. Every byte of the command body is bit-inverted (XOR 0xFF) on the wire; the encoder inverts on the way out and the decoder un-inverts behind the report-ID byte. This is why earlier 0xff01 traffic looks like garbage or like a "loopback" — it isn't, the inversion is the framing.

MagDock — plain, non-inverted frames on an unnumbered output report (report 0), usage page 0xff00, shaped [0xaa][cmd][cmdType][seq][total][paramLen][params…]. Only lighting is exposed.

docs/mchose-protocol.md is the full write-up: report layout, the config block field by field, the button vocabulary, and the dead ends (including a whole 0x4D-magic codec built on a wrong guess and thrown away) so nobody re-walks them.

What the mouse driver supports

Read and write, each round-tripped on hardware and restored to its entry value:

  • DPI — current value, per-stage values, active stage, stage count
  • polling rate, with the wired and wireless halves of the config kept independent
  • lift-off distance, motion sync, angle snapping, ripple control
  • the three-way power mode (Performance / eSports / Ultra, bits 6–7 of the sensor byte)
  • angle tuning, a signed −30…+30 stored two's-complement at config offset 49
  • debounce time and sleep timeout
  • onboard profile switching, with the device-stored profile names
  • button remapping across the full vendor vocabulary (mouse, keyboard, media, DPI, system, profile, disable)

Notes on the implementation

  • Reply plausibility. The firmware serves stale replies out of a shared buffer, so a read can hand you a battery payload where a config block should be. request() requires both a command echo and a caller-supplied accept() check before it trusts a frame. A regression test covers exactly this (it is named after the write that nearly went out with a battery payload in it).
  • Settle timings are per-command constants rather than one global delay; the performance write in particular needs several attempts before it sticks.
  • LOD mask is 0x03, not 0x07. Bit 2 of that byte is ripple control — masking three bits made LOD read as level 4 and disappear from the UI whenever ripple was on. There is a test decoding 0x84 for it.
  • The mouse driver explicitly excludes the dock's PID so the two drivers never both claim a device; the registry probe matrix enforces it.

The A7 V2 Ultra+ is verified hardware. The other A7 V2 variants share the protocol and are marked likely — they have not been tested.

🤖 Generated with Claude Code

Adds support for the MCHOSE A7 V2 family (vendor 0x3837) and, as a second
driver, the MagDock charging base that carries the family's RGB.
The protocol was recovered from MCHOSE's own M HUB web driver and verified
against an A7 V2 Ultra+ over both its 2.4 GHz receiver and its cable. Every
setter was round-tripped on hardware and restored; docs/mchose-protocol.md
records the wire format, the dead ends, and the traps.
Mouse (src/mchose, src/drivers/mchose/hid.ts):
- model, firmware, battery and charge state
- six DPI stages with editable values and stage count
- polling rate, per link
- lift-off distance, motion sync, ripple control, angle snapping
- performance mode (Performance / eSports / Ultra), angle tuning
- debounce, auto-sleep
- three onboard profiles, with the names the device stores
- button remapping across mouse, keyboard, media, DPI, system and profile
actions; an assigned macro is preserved and named, not clobbered
MagDock (src/mchose/dock.ts, src/drivers/mchose/dock-hid.ts):
- a separate device with a separate protocol: plain, non-inverted frames on
unnumbered report 0. Shaped as a non-mouse status carrying only lighting.
Three details are easy to get wrong and are covered by tests:
- the whole command body is transmitted bit-inverted, which makes the config
channel look like a loopback until you notice
- every command shares one reply buffer, so a read needs both a command-echo
check and a plausibility check or it can return another command's answer
- MCHOSE ships two contradictory schemas for the config blob; the write schema
is the correct one, established by reading the device at known settings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jamezrin@snekxs