mchose: name the V3 by its product string, not its reported id - #100
Conversation
A diagnostic export from a real A7 V3 Ultra+ arrived, and the driver read everything correctly except the one thing everything else hangs off: the model. Its `0x0900` reply reports product id 0x4026, which MCHOSE's own table lists against the A5 V3 Ultra+, while its USB product string plainly reads "MCHOSE A7 V3 Ultra+". Trusting the id named the wrong mouse and through it handed out a 42,000 DPI ceiling and a three-step lift-off ladder to a 50,000 DPI five-step model; the DPI table in the same capture tops out at 50,000, which is the contradiction in one line. Whatever that field is -- a sensor or platform id shared across shells -- it is not a model id, and M HUB never treats it as one: every model lookup in the vendor bundle keys off navigator.device.productName. mchoseV3FindProduct now prefers the product string and keeps the id only as a fallback for a device whose string says nothing useful. Note this is the opposite of the A7 V2's rule, where the id inside the battery reply is decisive because the host-facing id is shared. The two generations genuinely differ here, so both call sites carry the reasoning. Also from the same capture: 0x0901 takes a target byte, 0 for the mouse and 1 for the receiver. Sent bare it answers with an empty data block rather than an error, which is why the export shows no firmware version at all. The reads are now hardware-confirmed -- identity, battery and charge state, the DPI table, polling, profile, sleep, debounce, sensor flags and the button table all decoded correctly -- so the driver and the docs stop saying they are not. The writes are still unexercised and there are still no setters; the status note now says plainly that settings can be read but not changed. 0x0009 remains untried: the capture was taken while the driver thought it was a three-step model, so it never sent it. The captured blocks are checked in as tests, so the id trap cannot come back. A MagDock export in the same batch read correctly with no changes, so its status note drops the "A7 V2" that no longer fits its owner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two fresh captures from the A7 V3 Ultra+, one over its cable and one over the receiver, show three reply shapes the driver was mishandling. Together they made a mouse that was answering every frame look like one that had stopped. The reply timeout was under the firmware's own deferral. On a cable 0x0900, 0x0002, 0x0003 and 0x0001 answer in 1-3 ms, but every reply that is not immediate takes almost exactly 1.001 s -- 0x0901 on the cable, 0x0900 over an idle receiver. The 600 ms budget sat just underneath that, so those replies were always missed, and each one then arrived during the next attempt and was discarded as unsolicited. Raised to 1500 ms, which only has to clear a delay that looks fixed rather than variable. 0x0901 is refused, not ignored. The firmware answers it with command 0x0000, the checksum flag clear and 0xff in the sequence byte. That never matches the requested id, so the driver spent its whole retry budget waiting for an answer that had already arrived. A refusal is now recognised and ends the command at once. A receiver with no mouse reachable answers 0x0900 with a single 0xff byte, meaning "ask again" rather than carrying data. The driver accepted it as a payload, the decoder rejected it as too short, and the status came back empty. It is now retried briefly -- M HUB's own read helper loops on exactly this -- and then reported as a mouse that is not reachable rather than one that said nothing. The test here is stricter than the vendor's, which checks the first payload byte alone: a button table legitimately starts with 0xff when the first button carries no assignment. Underneath all three, the unresponsive latch was too eager. It fired whenever a command exhausted its attempts, including when the device had plainly answered, and then suppressed every remaining command in the status read. That is why the receiver capture contains six 0x0900 frames and nothing else. It now fires only when nothing arrived at all.
|
Two more captures from the same A7 V3 Ultra+ (one on its cable, one over the receiver) turned up three reply shapes this driver was reading as silence. Together they made a mouse that was answering every single frame look like one that had stopped. Pushed as a second commit here. The reply timeout sat underneath the firmware's own deferral. On a cable
A receiver with no mouse reachable answers Underneath all three, the Also confirmed from these captures: the DPI button came back as type Docs updated with the three shapes and the measured timings. 77 V3 tests pass; |
Follows #84. A user with a real A7 V3 Ultra+ sent a diagnostic export, and the driver read everything correctly except the one thing everything else hangs off: the model.
The bug
Its
0x0900reply reports product id0x4026. MCHOSE's own table lists that id against the A5 V3 Ultra+. Its USB product string plainly readsMCHOSE A7 V3 Ultra+.Trusting the id named the wrong mouse, and through it:
0x0009The contradiction is visible in the same capture: the DPI table it returned tops out at 50000, which the A5 V3 Ultra+ cannot do.
Whatever that field is — a sensor or platform id shared across shells — it is not a model id, and M HUB never treats it as one: every model lookup in the vendor bundle keys off
navigator.device.productName.mchoseV3FindProductnow prefers the product string and keeps the id only as a fallback.This is deliberately the opposite of the A7 V2's rule, where the id inside the battery reply is decisive because the host-facing id is shared. The two generations genuinely differ, so both call sites carry the reasoning rather than looking like an inconsistency.
Also from the capture
0x0901takes a target byte — 0 for the mouse, 1 for the receiver. Sent bare it answers with an empty data block rather than an error, which is why the export shows no firmware version at all.What is now confirmed, and what still is not
The reads are hardware-confirmed: identity, battery and charge state, the DPI table, polling, profile, sleep, debounce, sensor flags and the button table all decoded correctly against a live mouse. The driver docblock, the protocol doc and the user-facing status note stop claiming otherwise — the note now says settings can be read but not changed.
The writes remain unexercised and there are still no setters. And
0x0009is still untried: the capture was taken while the driver believed it was a three-step model, so it read lift-off from the sensor byte and never sent it. With the model resolved correctly the Ultra+ now takes that branch; a device that does not answer degrades to a blank lift-off rather than a wrong one.A MagDock export arrived in the same batch and read correctly with no changes, so its status note just drops the "A7 V2" that no longer fits its owner.
Testing
The captured data blocks are checked in verbatim as tests, so the id trap cannot come back — including one that asserts the id alone names the wrong mouse and the product string must win. Plus a driver test replaying the capture end to end, and one asserting
0x0901is sent with its target byte.npm run checkpasses apart from the two pre-existingsrc/drivers/corsair/hid.test.tssetDpifailures, which also fail on a cleanmainlocally and are unrelated.🤖 Generated with Claude Code