I recently discovered the existence of USB volume knobs. A Reddit user posted an article about reflashing the firmware on one to convert it to a keyboard.
Â
Inspired, I picked one up for $18 on eBay (“USB Volume Controller Knob Adjuster Switcher for Tablet PC Speaker Audio”) and thought that I might be able to do something similar.
It turns out, under Linux, this is pretty easy.
First, I plugged in the volume knob and saw that Linux detected it correctly and used it to adjust the volume. That was a promising start. I could see it show the “HDMI / DisplayPort” volume - and it went up when I turned the knob to the right, down when I turned the knob to the left, and muted when I pressed the knob.
Next, I wanted to see what events were being generated. I found some very useful instructions at https://yulistic.gitlab.io/2017/12/linux-keymapping-with-udev-hwdb/ and did them:
$ cat /proc/bus/input/devices
...
I: Bus=0003 Vendor=0483 Product=572d Version=0111
N: Name="STMicroelectronics USB Volume Control"
P: Phys=usb-0000:00:1d.0-1.7.2.4.3.1/input0
S: Sysfs=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.7/2-1.7.2/2-1.7.2.4/2-1.7.2.4.3/2-1.7.2.4.3.1/2-1.7.2.4.3.1:1.0/0003:0483:572D.0008/input/input14
U: Uniq=2070363C4250
H: Handlers=kbd event8
B: PROP=0
B: EV=13
B: KEY=3800000000 e000000000000 0
B: MSC=10
...
This showed me a few useful things:
- The device vendor for my device is 0483 (the “I:” line)
- The product ID for my device is 572d (also on the “I:” line)
- The device is attached on /dev/input/event8 (on the “H:” line)
So now I could scan the events that came across when I moved the knob:
$ sudo evtest /dev/input/event8
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x483 product 0x572d version 0x111
Input device name: "STMicroelectronics USB Volume Control"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 113 (KEY_MUTE)
Event code 114 (KEY_VOLUMEDOWN)
Event code 115 (KEY_VOLUMEUP)
Event code 163 (KEY_NEXTSONG)
Event code 164 (KEY_PLAYPAUSE)
Event code 165 (KEY_PREVIOUSSONG)
Event type 4 (EV_MSC)
Event code 4 (MSC_SCAN)
Properties:
Testing ... (interrupt to exit)
Event: time 1576479720.245227, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e9
Event: time 1576479720.245227, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 1
Event: time 1576479720.245227, -------------- SYN_REPORT ------------
Event: time 1576479720.253248, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e9
Event: time 1576479720.253248, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 0
Event: time 1576479720.253248, -------------- SYN_REPORT ------------
Event: time 1576479722.325231, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00ea
Event: time 1576479722.325231, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 1
Event: time 1576479722.325231, -------------- SYN_REPORT ------------
Event: time 1576479722.333224, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00ea
Event: time 1576479722.333224, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 0
Event: time 1576479722.333224, -------------- SYN_REPORT ------------
Event: time 1576479724.381251, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e2
Event: time 1576479724.381251, type 1 (EV_KEY), code 113 (KEY_MUTE), value 1
Event: time 1576479724.381251, -------------- SYN_REPORT ------------
Event: time 1576479724.389251, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e2
Event: time 1576479724.389251, type 1 (EV_KEY), code 113 (KEY_MUTE), value 0
Event: time 1576479724.389251, -------------- SYN_REPORT ------------
Neat, even more useful things. In particular:
- When I turn the knob to the right, I get an MSC_SCAN event of type c00e9 (along with a KEY_VOLUMEUP event)
- When I turn the knob to the left, I get an MSC_SCAN event of type c00ea (along with a KEY_VOLUMEDOWN event)
- When I push on the knob, I get an MSC_SCAN event of type c00e2 (along with a KEY_MUTE event)
- Apparently the firmware supports KEY_NEXTSONG, KEY_PREVIOUSSONG and KEY_PLAYPAUSE as well. Huh.
I want to map those MSC_SCAN events to different key codes. In particular, I want a cursor-left key when I turn the knob to the left, a cursor-right key when I turn the knob to the right, and something useful (say, pressing the “m” key) when I press the knob. So I created a hwdb file for my device:
$ cat /etc/udev/hwdb.d/99-usb-knob.hwdb
evdev:input:b*v0483p572D*
KEYBOARD_KEY_c00ea=left
KEYBOARD_KEY_c00e9=right
KEYBOARD_KEY_c00e2=m
You’ll recognize the vendor (0483) and the device (572d) that I found earlier. It’s important to use uppercase hex codes for vendor and product in the hwdb file - but not for the scan codes, which should be lowercase. The values on the right have to be lowercase, and correspond to the KEY_LEFT, KEY_RIGHT and KEY_M values from /usr/include/linux/input-event-codes.h. (You can pick any of the KEY_ values from there.) Then a quick bit of Linux magic to update the hardware database:
$ sudo systemd-hwdb update
$ sudo udevadm trigger
…and… exactly the same as before. I got the volume control displayed when I turned the knob.
After scratching my head and doing some searching, I happened on https://catswhisker.xyz/log/2018/8/27/use_vecinfinity_usb_foot_pedal_as_a_keyboard_under_linux/ which gave me the clue I needed. My knob was being detected, but not as a keyboard - so it wasn’t being used as a keyboard input device.
So I created this file:
$ cat /etc/udev/rules.d/99-usb-knob.rules
ACTION=="add|change", KERNEL=="event[0-9]*",
ATTRS{idVendor}=="0483", ATTRS{idProduct}=="572d",
ENV{ID_INPUT_KEYBOARD}="1"
(That’s all on one line on my machine.) You’ll recognize the vendor and product ID from earlier, using lowercase for the hex this time. I added ID_INPUT_KEYBOARD to the list of attributes for this device. Unplug the device, plug it back in, and hooray! I’m doing what I wanted to! When I turn the knob left, I go left. When I turn the knob right, I go right. When I press the knob, “m” shows up on the screen.
Now I just need to install an SDR program… and an SDR…