AnalogSense Python SDK
Python port of AnalogSense.js for analog keyboard input.
Supported Keyboards/Devices
- Everything by WootingT
- Everything by NuPhy
- Everything by DrunkDeer
- Razer Huntsman V2 AnalogR
- Razer Huntsman Mini AnalogR
- Razer Huntsman V3 ProR
- Razer Huntsman V3 Pro MiniR
- Razer Huntsman V3 Pro TenkeylessR
- Keychron Q1 HEP, F
- Keychron Q3 HEP, F
- Keychron Q5 HEP, F
- Keychron K2 HEP, F
- Lemokey P1 HEP, F
- Madlions MAD60HEP
- Madlions MAD68HEP
- Madlions MAD68RP
- Redragon K709HEP, T
T Tested and should work. Feel free to open issues/prs about boards not being picked up properly
R Razer Synapse needs to be installed and running for analogue inputs to be received from this keyboard.
P The official firmware only supports polling, which can lead to lag and missed inputs.
F Custom firmware with full analog report functionality is available.
Installation
pip install AnalogSensePy
On Linux you may need udev rules or sudo for hid
printf '%s\n' \
'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="31e3", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="03eb", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="1532", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="19f5", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="352d", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="3434", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="362d", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="373b", MODE="0660", GROUP="input"' \
'SUBSYSTEM=="usb", ATTRS{idVendor}=="372e", MODE="0660", GROUP="input"' \
| sudo tee /etc/udev/rules.d/99-analogsense.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
Usage
from analogsense import AnalogSense
as_ = AnalogSense()
devices = as_.get_devices()
if not devices:
print("no supported analog keyboards found.")
exit()
if len(devices) == 1:
dev = devices[0]
else:
for i, d in enumerate(devices):
print(f" [{i}] {d.product_name}")
dev = devices[int(input("select device index: "))]
def on_keys(active_keys):
for k in active_keys:
# converting the scancode to a nice readable string
name = as_.scancode_to_string(k["scancode"])
# some providers can return the digital state of the key
# (what the system perceives the key as pressed or not)
# other providers that do not support this will return None
if k["digital"] is not None:
digital = " [down]" if k["digital"] else " [up]"
else:
digital = ""
print(f"{name:20s} {k['value']:.4f}{digital}")
dev.start_listening(on_keys)
input("press keys... enter to stop.\n")
dev.stop_listening()
The following functions are available on AnalogSense:
get_devices() -> list[Device]open_device(vendor_id, product_id) -> Device | Nonescancode_to_string(scancode: int) -> str
A device instance has the following members:
start_listening(handler: Callable[[list[{"scancode": int, "value": float}]], None])stop_listening()product_name: strforget()dev: DeviceHandle
Scancodes
The scancodes provided by this library are primarily HID scancodes; most keys are mapped as seen on usage page 0x07 (A = 0x04, B = 0x05, ...).
Control keys (usage page 0x0C) are mapped in the 0x3__ range, modulo 0x100:
0x3B5= Next Track0x3B6= Previous Track0x3B7= Stop Media0x3CD= Play/Pause0x394= Open File Explorer0x323= Open Browser Home Page
OEM-specific keys are mapped in the 0x4__ range:
0x401= Brightness Up0x402= Brightness Down0x403= Profile 10x404= Profile 20x405= Profile 30x408= Profile Switch0x409= Function Key (Fn)
Metadata
Release files for AnalogSensePy 1.0.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| analogsensepy-1.0.7.tar.gz | 12.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| analogsensepy-1.0.7-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.9 kB
Release files / analogsensepy-1.0.7.tar.gz
| Download URL | analogsensepy-1.0.7.tar.gz |
|---|---|
| Size | 12.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7939778f1516bff028f29c5f597f64ec09e0d528067b313b76cd15ef4f252488
|
|
BLAKE2b-256 checksum How to use checksums |
a0f6e02b2fdc3cee6a58d9e279a07081fe6a3c63383b471fb3f81414c4691443
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 6, 2026.
Transparency logRelease files / analogsensepy-1.0.7-py3-none-any.whl
| Download URL | analogsensepy-1.0.7-py3-none-any.whl |
|---|---|
| Size | 13.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e33d489602a255b1bc4e6d0b12e4e967849b8d1a3ee037862333fb017d7d8ceb
|
|
BLAKE2b-256 checksum How to use checksums |
92c53af4cdee8e968c5b373e15fc9b28754b1ec10c668aeb8455feb2d4e12c7e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 6, 2026.
Transparency log