Skip to content

feat: add SteelSeries Aerox 3 Wireless support - #165

Merged
snekxs merged 2 commits into
OpenMouse-Project:mainfrom
vexcited:feat/steelseries-aerox3-wireless
Oct 6, 2026
Merged

snekxs merged 2 commits into
OpenMouse-Project:mainfrom
vexcited:feat/steelseries-aerox3-wireless

Conversation

@vexcited

@vexcited vexcited commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Adds the SteelSeries Aerox 3 Wireless as its own aerox3-wireless family, with a codec under src/steelseries/ and a WebHID driver under src/drivers/steelseries/.

Devices

PID Model Connection Verified
1038:183a Aerox 3 Wireless USB cable yes
1038:1838 Aerox 3 Wireless 2.4 GHz dongle yes
1038:187a Aerox 3 Wireless CS2 Dragon Lore Edition USB cable no
1038:1878 Aerox 3 Wireless CS2 Dragon Lore Edition 2.4 GHz dongle no

Bluetooth mode is not configurable through this protocol. The firmware version cannot be read: rivalcfg defines no firmware query for this device.

Evidence

The protocol is transcribed from rivalcfg's aerox3_wireless_wired.py and aerox3_wireless_wireless.py. The settings are byte-identical to the Aerox 5 Wireless except for the button mapping: 6 buttons plus scroll up/down make a 40-byte packet instead of 55. It is a separate family from the wired Aerox 3 (0x1836), whose polling bytes, zone packing, rainbow command and DPI preset base all differ. Over the dongle every command has 0x40 ORed into its first byte.

Tested on hardware

Tested on macOS with the bytes produced by the new codec, over the cable and the dongle. Writes are made one at a time on purpose.

  • Config channel: usage page 0xFFC0 usage 0x01, interface 3 over the cable, with a 64-byte output report and no report ID. The dongle exposes the same collection.
  • Every write is acked with an input report that echoes the command byte followed by 00, for example 2b 00 then 11 00. Over the dongle the third byte is 01.
  • Battery: 92 95 over the cable (charging, 100%) and d2 15 over the dongle (discharging, 100%).
  • Polling rate: 125 Hz and 1000 Hz measured on the mouse input collection over both connections, with median report gaps of 8.00 ms and 1.00 ms.
  • DPI presets: a 400/1600 table cycled exactly two stages with the DPI button and survived a replug. The 5-stage default written over the dongle cycled five stages.
  • Zone colors land on the top, middle and bottom LEDs in that order.
  • Reactive color flashes the strip on click.
  • Default lighting rainbow makes the mouse boot into a rainbow cycle.
  • Dim timer: 0 keeps the LEDs on, 5 seconds dims them after 5 idle seconds.
  • Sleep timer: with 1 minute set over the dongle, the mouse stopped answering between 60 and 70 idle seconds.
  • Button mapping: the DPI button remapped to the A key typed a, and the other buttons kept their defaults.
  • OpenMouse: npm run check passes against this build (266 tests), and the mouse connects and is configurable in the app.

Differences from rivalcfg

  • rivalcfg's runtime rainbow 22 FF is acked but leaves the strip dark, and 22 07 does too, so the driver does not offer a runtime rainbow. The codec keeps the encoder with a note. Rainbow works as startup lighting through 27 01 00.
  • Zone colors are not kept across a power cycle: the mouse boots into its default lighting. DPI, polling, timers and buttons persist.
  • While the mouse sleeps or is out of range, the dongle answers every query with an unsolicited 40 ff 01 report. The same report also arrives once right after a polling rate change. The battery probe matches replies by their echoed command byte, and reports an asleep mouse with its own error instead of the "close SteelSeries GG" hint.

Driver notes

  • Chrome grants every collection of the mouse at once, and OpenMouse uses the highest-scoring granted interface that has a client. isSupported therefore claims only the 0xFFC0:0x01 collection. Claiming every interface sent writes to the mouse collection and failed with "Failed to write the report". The Aerox 5 Wireless, Aerox 9 Wireless and Prime Mini Wireless drivers claim every interface the same way and may hit this too; I have no hardware to confirm.
  • Nothing except the battery can be read back. The driver keeps a cache of the last-written values, seeded with rivalcfg's defaults, and reports valuesVerified: false.
  • It plugs into the shared UI:
    • DPI stage editor: up to 5 stages, 100 to 18,000 DPI in steps of 100
    • lighting zones: Top, Middle, Bottom and Click reaction
    • button remapper: mouse buttons, DPI cycle, media keys, and A to Z, 0 to 9, F1 to F24, Enter, Escape, Tab and Space
    • sleep timeout: whole minutes up to 20
  • The dim timer and startup lighting have driver methods but no shared UI card yet.
  • Remapping one button keeps the others at their defaults, matching rivalcfg. A zero-filled field would disable that button.
  • 0xFFC0 is added to the usage pages in registry.test.ts, so the overlap and picker tests cover this driver.

vexcited and others added 2 commits October 5, 2026 02:15
setZoneColor caches the colour it writes, so the Off branch read back the
black it had just written: the zone reported #000000 and switching it back
to Static wrote black instead of the colour the user had chosen.

Capture the colour before the Off write and cache that, and assert the
read-back and the Static restore in the zone test.
@snekxs
snekxs merged commit 68f2a6d into OpenMouse-Project:main Oct 6, 2026
5 checks passed
@vexcited
vexcited deleted the feat/steelseries-aerox3-wireless branch October 6, 2026 00:17
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.24.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants