Skip to content

Razer: DeathStalker V2 Pro / TKL and BlackWidow HyperSpeed keyboards from OpenRazer (#106), unit tests - #122

Merged
HeyOkay merged 5 commits into
HeyOkay:mainfrom
ahmedkhursheed23:razer-keyboards
Oct 5, 2026
Merged

HeyOkay merged 5 commits into
HeyOkay:mainfrom
ahmedkhursheed23:razer-keyboards

Conversation

@ahmedkhursheed23

@ahmedkhursheed23 ahmedkhursheed23 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Confirmed on hardware (#106): DeathStalker V2 Pro TKL 0296, 45%, equal to Synapse (tid=9f, 07:80 reply 0x73).

This PR adds the Razer keyboards with a battery that OpenRazer's keyboard driver reads. The main one is the DeathStalker V2 Pro TKL on its HyperSpeed receiver from #106. The app skipped it: skipped: not a known wireless device, because the product string DSV2Pro TKL has no "wireless" word and 0296 was not in KNOWN.

Base and other PRs

Source

OpenRazer, driver/razerkbd_driver.c and razerkbd_driver.h at 6820f9d:

  • razer_attr_read_charge_level() and razer_attr_read_charge_status() give the transaction id.
  • razer_get_report_params() gives report_index. OpenRazer sends the request with USB_RECIP_INTERFACE and wIndex = report_index (razercommon.c), so report_index is the USB interface that takes the commands.
  • The daemon (openrazer_daemon/hardware/keyboards.py) lists get_battery for all ten. The wireless classes inherit it from the wired classes.
PID OpenRazer name Transaction id Interface
0290 DEATHSTALKER_V2_PRO_WIRELESS 0x9F 2
0292 DEATHSTALKER_V2_PRO_WIRED 0x1F 3
0296 DEATHSTALKER_V2_PRO_TKL_WIRELESS 0x9F 2
0298 DEATHSTALKER_V2_PRO_TKL_WIRED 0x1F 3
0271 BLACKWIDOW_V3_MINI_HYPERSPEED_WIRELESS 0x9F 3
0258 BLACKWIDOW_V3_MINI_HYPERSPEED_WIRED 0x1F 3
02BA BLACKWIDOW_V4_MINI_HYPERSPEED_WIRELESS 0x9F 3
02B9 BLACKWIDOW_V4_MINI_HYPERSPEED_WIRED 0x1F 3
02D5 BLACKWIDOW_V4_TENKEYLESS_HYPERSPEED_WIRELESS 0x9F 2
02D7 BLACKWIDOW_V4_TENKEYLESS_HYPERSPEED_WIRED 0x1F 3

The command is the one that the provider already sends to the mice: class 0x07, id 0x80 (level, 0-255) and 0x84 (charging). The wired ids are included for the same reason as for the mice (the table follows OpenRazer's charge_level list): on the cable the keyboard charges and still reports its level.

The change

  • KNOWN: the ten ids, with the names above and the transaction ids from OpenRazer. The wireless and wired ids of one keyboard have the same name.
  • KEYBOARD_INTERFACE: the interface from report_index. _poll_group() asks that interface first. The other interfaces stay a fallback, in the same order as before.
  • These devices get kind="keyboard", so the tray shows the keyboard pictogram and not the mouse. Mice do not change (kind="", automatic).

What this PR does not do

In #106 a Basilisk V3 Pro mouse is paired to the same multi-device receiver. No public source says how to address a second device through the keyboard's receiver (openrazer#1892 is open). This PR does not guess.

Tests

tests/test_razer_keyboards.py (new, 8 tests). It uses FakeMouse and FakeBus from tests/test_razer.py and the 12 collections of the #106 receiver.

  • Device detection issue: Razer Multi-Device dongle (DSV2Pro TKL) & HyperX Cloud III S Wireless (Unverified) #106: only interface 2 opens; level 71%, kind="keyboard", two requests with tid 0x9F (level, charging).
  • When every collection would answer, only interface 2 gets a request.
  • When interface 2 says "not supported", another interface is still tried.
  • A sleeping keyboard (status 04) keeps the keyboard pictogram (greyed icon).
  • A mouse (00B9) keeps kind="".
  • The table agrees with OpenRazer (tid and interface for all ten).
  • maybe_wireless(0x0296, " DSV2Pro TKL") is true.
  • Wireless and wired ids share a name.

Mutation checks: I changed the interface ranking, the kind, the greyed-icon kind, one transaction id and one interface, and each time at least one test failed. All 249 tests pass.

Part of #106 (the keyboard).

ahmedkhursheed23 and others added 2 commits September 28, 2026 19:08
…eyOkay#106)

Ten ids from OpenRazer's keyboard driver (razerkbd_driver.c @6820f9d):
transaction id 0x9F on the receiver and 0x1F on the cable
(razer_attr_read_charge_level), and the USB interface that takes the
commands, 2 or 3 (report_index in razer_get_report_params), asked first.
The icon shows the keyboard pictogram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmGsnjgXs8VbKmmxLkPsf6
@casparas123

Copy link
Copy Markdown
Contributor

Heads-up from a fellow PR author: I've opened #126, which shortens the README's support table to Device | Connection | Verified on hardware and moves the per-device protocol detail to a new docs/protocols.md, one section per device.

Our trial merge shows #126 conflicts with this PR in README.md only. The fix is mechanical: the row you added in the old format becomes a short row in the new table, and the detail text from that row becomes a section in docs/protocols.md. If it helps, I can prepare both as a patch you can drop in, or rebase this branch for you - whichever is easier. Just didn't want the future conflict to be a surprise.

@HeyOkay

HeyOkay commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Thanks for this! PR #107 and a batch of other PRs were just merged into main (the listen-only PlayStation handling from e80dfd1 is kept, and make_providers() in halo_battery.pyw is now the single provider list), so this PR now conflicts with main. Could you merge main into it or rebase it? A new provider also needs an entry in make_providers() and PROVIDER_LABELS (the Device types menu) — test_every_provider_has_a_label checks that. Once it is green on top of main I'll merge it for 1.13.0.

@casparas123

Copy link
Copy Markdown
Contributor

I trial-merged this against today's main to check it's still mergeable. It conflicts in CHANGELOG.md, README.md and providers/razer.py.

After that the full suite passes against current main (430 tests).

@HeyOkay HeyOkay added this to the v1.14.0 milestone Sep 29, 2026
ahmedkhursheed23 and others added 2 commits September 30, 2026 05:45
Resolved CHANGELOG.md (bullet under Unreleased), README.md (short rows for the
new table, detail moved to docs/protocols.md; TKL verified, others not);
providers/razer.py merged cleanly with main's BlackWidow V3 Pro rows, which
now also get the keyboard icon and interface 2 first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmGsnjgXs8VbKmmxLkPsf6
…as it is

The merge had added 0x025A / 0x025C to KEYBOARD_INTERFACE, which changes
the interface order and pictogram of a device this PR does not cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmGsnjgXs8VbKmmxLkPsf6
@ahmedkhursheed23

Copy link
Copy Markdown
Contributor Author

Merged main (1.13.0) into this branch; it is mergeable again. providers/razer.py keeps main's BlackWidow V3 Pro rows (#112) unchanged next to the new keyboards. The README has two short rows now (the DeathStalker V2 Pro TKL yes, verified in #106; the other keyboards no), and the detail is in docs/protocols.md. Thank you @casparas123 for the trial-merge notes. Full suite: OK.

@HeyOkay
HeyOkay merged commit 910a483 into HeyOkay:main Oct 5, 2026
HeyOkay added a commit that referenced this pull request Oct 5, 2026
casparas123 pushed a commit to casparas123/HaloBattery that referenced this pull request Oct 5, 2026
casparas123 pushed a commit to casparas123/HaloBattery that referenced this pull request Oct 5, 2026
…S 06BE

- The six new device rows from HeyOkay#122, HeyOkay#123, HeyOkay#124, HeyOkay#125 and HeyOkay#141 go to
  docs/devices.md, where HeyOkay#128 moved the table.
- tests/test_steelseries_elite.py: find make_providers( with its new
  parameter from HeyOkay#134.
- Cloud III S: the 06BE revision and the charging state are confirmed in
  HeyOkay#156 (docs/protocols.md, CHANGELOG).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GfhP6tYaD8Rt4qP2YtmJ4
Sign up for free to 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.

3 participants