Turn a cheap magnetic keyboard into real analog racing pedals.
Press W halfway and the car creeps. Press it home and it floors. No vendor software, no extra hardware, no browser.
Only if Windows shows it as VID 0416 / PID 7372. That's the Redragon M68 /
E-YOOSO HZ-68 and the various names the same board is rebadged under.
To check: Device Manager → Keyboards → your board → Details → Hardware Ids.
You're looking for HID\VID_0416&PID_7372.
| Board | VID | PID | Status |
|---|---|---|---|
| Redragon M68 / E-YOOSO HZ-68 | 0x0416 |
0x7372 |
✅ Verified working |
| your board here | — | — | contributions welcome |
Other magnetic boards (Wooting, Keychron, other Sinowealth chips) each speak their own private protocol and won't work as-is — but the device layer is pluggable, so adding one is a contained job. See below.
Magnetic ("Hall-effect") keyboards don't just know a key is pressed — they know exactly how far down it is, about 0 to 4 mm, in 40 steps. That's everything you need for a throttle.
The board keeps that data locked inside, speaking a private language only the vendor's web configurator understood. KeyAxis was built by wiretapping that conversation, working out the phrasebook, and then asking the keyboard the same question directly. It reads the raw ~1000 Hz travel stream, applies your deadzone and curve, and feeds a virtual Xbox controller that any game already knows how to talk to.
Nothing else does this for this board.
Grab KeyAxis-Setup.exe from Releases. It installs the
app, offers to install the ViGEmBus driver, and applies the Game Bar popup fix.
No Python needed.
python -m pip install -r requirements.txt
python app.pyOr just double-click KeyAxis.bat, which installs dependencies on first run.
Requirements: Windows 10/11 64-bit, Python 3.9+ (source only), and the ViGEmBus driver for pedal output. The Monitor tab works without ViGEmBus.
- Connect — plug the board in, hit Connect.
- Calibrate once (~1 min) — the wizard learns which physical switch each key is and how deep it truly goes. Saved forever, and it survives reinstalls.
- Bind pedals — hit Bind key, then just press the key you want (or click it on the on-screen board). Pick an axis, tune deadzone and curve.
- Start pedals — a virtual controller appears. Bind it in your sim like any wheel or pad.
The in-app Guide tab has diagrams, per-sim recipes and troubleshooting.
Xbox triggers (RT/LT) stay independent in modern XInput games — but they collapse onto a single shared Z axis in older DirectInput games. Bind two pedals to RT and LT in an old sim and they'll fight each other.
| Game | API | Recipe |
|---|---|---|
| BeamNG.drive | XInput | Throttle → RT, Brake → LT. Works perfectly. ✅ |
| Live for Speed | DirectInput | Use stick axes — Throttle → LSY, Brake → RSY, Clutch → RSX. |
| MX Bikes | DirectInput | Partial. Wants 5 independent axes; an Xbox pad can't give five. 3–4 work today; full support needs the planned vJoy output mode. |
If two pedals cancel each other out, that's this. Move one to a stick axis.
- Opens the keyboard's vendor HID interface (the enumerated entry with
usage_page == 0xff1b). - Sends an ARM command; the board streams event-driven frames whenever a key's depth changes.
- Each frame carries row, col, and depth 0–40 → 0.0–4.0 mm.
- Depth is normalised against that key's calibrated peak, then deadzone / curve / invert are applied, then it's written to a virtual-gamepad axis.
- A daemon thread owns the hardware and the gamepad. The UI only polls display state, so input latency never depends on the interface.
- DISARM is always sent on stop, disconnect and exit.
- Transient USB drops (this board does that) are healed in place rather than dropping your connection.
The UI is an HTML page in a native pywebview window (WebView2). JS talks to
Python via window.pywebview.api.*. Not WebHID — Chromium blocks HID reads on
keyboards, which is precisely why the hardware layer lives in Python.
This is the interesting bit, and the reason it's MIT.
Every magnetic keyboard has its own arm command and frame layout, but the
shape is nearly always the same: arm a stream, receive (row, col, depth).
The whole device definition is one DeviceProfile in
app.py — VID, PID, usage page, arm bytes, disarm bytes, max depth.
To add a board:
- Find its vendor interface and capture the traffic while its own software runs a "travel test" or similar (a WebHID console sniffer works well if the vendor tool is a web app).
- Identify the ARM write and the frame that carries row/col/depth.
- Add a
DeviceProfileand append it toSUPPORTED_PROFILES.
Open an issue with your capture even if you can't finish it — a raw log is often enough for someone else to close the loop.
| File | What it is |
|---|---|
app.py |
Backend: HID reader thread, ViGEm gamepad, calibration, JS↔Python API |
ui.html |
The entire interface (CSS + JS inline) |
keysuppress.py |
Optional low-level hook to stop a pedal key typing its letter |
KeyAxis.spec |
PyInstaller build definition |
installer/KeyAxis.iss |
Inno Setup installer script |
fix_gamebar.bat |
Kills the ms-gamebar popup (reversible; undo_gamebar.bat) |
tools/assets.py |
Build-time artwork helper — not shipped, not needed to run |
User data (calibration.json, settings.json) lives next to app.py when
running from source, and in %LOCALAPPDATA%\KeyAxis once installed.
Does the key still type its letter while it's a pedal? Yes, by default — and usually you want that, because games often use the same key for other functions (BeamNG uses W for more than throttle). If you genuinely need it silenced, switch on suppress pedal keys in Settings. It's a global hook, so it's best on keys you don't type mid-race.
A Microsoft Store popup keeps appearing.
Windows asks for an app to open ms-gamebar whenever a virtual controller
appears. Run fix_gamebar.bat once. Fully reversible.
"ViGEm not available." Install ViGEmBus and restart.
The controller doesn't appear in my game.
Check joy.cpl first. If axes move there, it's just the in-game binding. If it's
absent there, ViGEmBus isn't installed or pedals aren't started.
Will this hurt my keyboard? No. KeyAxis only reads the travel stream and sends the documented arm/disarm commands. It never writes firmware or changes stored settings.
- vJoy output mode — 8 genuinely independent axes, which unlocks MX Bikes.
- More boards via the pluggable device layer.
- Draggable response-curve editor.
- Multiple simultaneous virtual controllers.
MIT — see LICENSE. Use it, fork it, ship it.
All trademarks belong to their owners.