やりたいこと
Linux 系でも小型 USB キーボードのレイヤ切り替えを行いたい。
キーにレイヤ切り替えのための機能をアサインすれば「キーを押す」ことでも対応できるけど、それだとちょっと自由度が足りないし、ユーザーにその操作を強いることになるので、できることならPython等で制御したい。
Linux では使いづらいレイヤ機能付きキーボード
フルキーボードであれば、Linux でもレイヤ切り替えが可能なものは多い。
QMK/VIA 対応の製品であれば特に問題なさそうなので。
けれど、どうしても小型デバイスでせいぜい5とか10もあれば事足りる機器を作りたいとき、QMK/VIA 対応のものからの選択はかなり狭くなる。
なので、QMK/VIA に非対応な小型キーボードで何とかならないか?
と思ったのでやってみた。
結論:できる
ちょっと癖はある。あるけど、短いコードで割とどうにでもなる。
今回買ったもの

まずはできるかどうか、手軽なWindowsでやってみた
仮想環境の準備
もう定番の仮想環境作成と、仮想環境をアクティベートして作業開始
F:\pythonTest\kbLayerChanger> python -m venv
F:\pythonTest\kbLayerChanger> .\Scripts\activate
(kbLayerChanger) F:\pythonTest\kbLayerChanger>
必要なライブラリのインストール
(kbLayerChanger) F:\pythonTest\kbLayerChanger> .\Scripts\pip.exe install hidapi==0.14.0.post2
USB キーボードの情報収集
次の Python コードを実行して、目的のUSBデバイスの VID と PID を確認する。
VID/PID はほぼほぼその製品固有の値になるので、一度調べてしまえば OK。
接続先を Windows から Linux に変えたからといって変わるものじゃない。
import hid
# 接続されている全てのUSB機器を表示
for d in hid.enumerate():
print(
hex(d['vendor_id']),
hex(d['product_id']),
d.get('interface_number'),
d.get('usage_page'),
d.get('usage'),
d.get('product_string')
)
実行結果は以下のようになる。
0x46d 0xc52b 1 1 128 USB Receiver
0x46d 0xc52b 0 1 6 USB Receiver
0x46d 0xc52b 1 65468 136 USB Receiver
0x46d 0xc52b 2 65280 1 USB Receiver
0x46d 0xc52b 2 65280 2 USB Receiver
0x46d 0xc52b 2 65280 4 USB Receiver
0x4fe 0x6 0 1 6 PFU-65 USB Keyboard
0x8bb 0x2902 3 12 1 USB Audio CODEC
0xb05 0x19af 2 65394 161 AURA LED Controller
0x46d 0xc52b 1 1 2 USB Receiver
0x46d 0xc52b 1 12 1 USB Receiver
0x483 0x5752 0 65280 1 4-key Smart Keypad
0x483 0x5752 1 1 6 4-key Smart Keypad
0x483 0x5752 2 65280 2 4-key Smart Keypad
0x483 0x5752 3 1 2 4-key Smart Keypad
0x483 0x5752 3 12 1 4-key Smart Keypad
0x483 0x5752 3 1 128 4-key Smart Keypad
今回、4キーの小型キーボードを繋げていて、そいつのレイヤを Python で切り替えたいので、VID/PIDは…
VID:0x483
PID:0x5752
となる。
VID/PID がわかったところで次を実行。
import hid
for d in hid.enumerate(0x0483, 0x5752): #hid.enumerate(VID, PID)
print(
"path:", d['path'],
"interface:", d.get('interface_number'),
"usage_page:", hex(d.get('usage_page', 0)),
"usage:", hex(d.get('usage', 0)),
"product:", d.get('product_string')
)
得られるのは下記のような情報で、欲しいのは path の部分。
(長いので[省略]としちゃってる)
path: b'\\\\?\\HID#VID_0483&PID_5752&MI_00#7&[省略]' interface: 0 usage_page: 0xff00 usage: 0x1 product: 4-key Smart Keypad
path: b'\\\\?\\HID#VID_0483&PID_5752&MI_01#7&[省略]\\KBD' interface: 1 usage_page: 0x1 usage: 0x6 product: 4-key Smart Keypad
path: b'\\\\?\\HID#VID_0483&PID_5752&MI_02#7&[省略]' interface: 2 usage_page: 0xff00 usage: 0x2 product: 4-key Smart Keypad
path: b'\\\\?\\HID#VID_0483&PID_5752&MI_03&Col01#7&[省略]' interface: 3 usage_page: 0x1 usage: 0x2 product: 4-key Smart Keypad
path: b'\\\\?\\HID#VID_0483&PID_5752&MI_03&Col02#7&[省略]' interface: 3 usage_page: 0xc usage: 0x1 product: 4-key Smart Keypad
path: b'\\\\?\\HID#VID_0483&PID_5752&MI_03&Col03#7&[省略]' interface: 3 usage_page: 0x1 usage: 0x80 product: 4-key Smart Keypad
6つも path が表示されるけどどれなのよ!?
となるけど、見分けるには usage_page を見ればよい。
usage_page が 0xff00 になっているものが設定用のインターフェース。
また、0xff00が2つあるけど、たいていは若い方でいいはず。
ここに特定のコマンドを投げると、レイヤが切り替えられる。
コマンドを投げてみる
どんなコマンドを投げればいいか?
は Vaydeer の常駐アプリからレイヤ切替操作を実行するとこを、Wireshark で覗いてみればいいんだけど…
まぁ、細かいことは後回し。
とりあえず、下記コードで切り替えられる。
import sys, hid
dev = hid.device()
openPath = b'\\\\?\\HID#VID_0483&PID_5752&MI_00#7&[省略]'
dev.open_path(openPath)
def set_layer(layerNo: int):
layerNum = layerNo & 0xFF
report = bytearray(64)
report[1] = 0x64
report[2] = 0x01
report[3] = layerNum
report[4] = 0x64 ^ 0x01 ^ layerNum
ret = dev.write(report)
print("write returned:", ret)
# レイヤ切替
if len(sys.argv) != 2:
print("usage: python.exe changeLayer.py [0-5]")
sys.exit(1)
try:
val = int(sys.argv[1])
except ValueError:
print("引数は0~5の数字にしてください")
sys.exit(1)
set_layer(val)
このコードが kbLayerChanger.py だったりするならこんな感じ。
$ python kbLayerChanger.py 5
本命のLinuxで同じことをやってみる
いろいろ定番処理
libudev-dev や libhidapi-hidraw0 のインストールは NanoPi NEO 用 Armbian だと必要だったけど、他は不要かも?(要は適宜環境に合わせてインストールしてね)
$ sudo apt update
$ sudo apt install libudev-dev libhidapi-hidraw0
$ python.exe -m venv kbLayerChanger
$ cd kbLayerChanger
$ source ./bin/activate
(kbLayerChanger) $ ./bin/pip install hidapi==0.14.0.post2
USBキーボードの情報収集
VID/PID は変わらないので、path だけ確認すればよい。
なので、次のコードを実行する。
import hid
for d in hid.enumerate(0x0483, 0x5752): #hid.enumerate(VID, PID)
print(
"path:", d['path'],
"interface:", d.get('interface_number'),
"usage_page:", hex(d.get('usage_page', 0)),
"usage:", hex(d.get('usage', 0)),
"product:", d.get('product_string')
)
その結果は以下のようになる。
path: b'/dev/hidraw0' interface: 0 usage_page: 0xff00 usage: 0x1 product: 4-key Smart Keypad
path: b'/dev/hidraw1' interface: 1 usage_page: 0x1 usage: 0x6 product: 4-key Smart Keypad
path: b'/dev/hidraw2' interface: 2 usage_page: 0xff00 usage: 0x2 product: 4-key Smart Keypad
path: b'/dev/hidraw3' interface: 3 usage_page: 0x1 usage: 0x2 product: 4-key Smart Keypad
path: b'/dev/hidraw3' interface: 3 usage_page: 0x1 usage: 0x1 product: 4-key Smart Keypad
path: b'/dev/hidraw3' interface: 3 usage_page: 0xc usage: 0x1 product: 4-key Smart Keypad
path: b'/dev/hidraw3' interface: 3 usage_page: 0x1 usage: 0x80 product: 4-key Smart Keypad
目的の path は usage_page が 0xff00 でそのうちの一番若いのは、/dev/hidraw0 が該当。
また、あとでもう一つの 0xff00 の方も使うので、/dev/hidraw2 もメモっとく。
尚、この hidrawx の数字部分は USB の抜き差しで変わるし、いつ挿したかでも変わる。
一時的なものと考えて、抜き差しが起きても大丈夫なようにするとか、そういったのはまた別途考えよう。
レイヤを Python から切り替えてみる
てことで、Windows で使ったコードをちょっと修正して…
import sys, hid
# とりあえず試すだけなら、/dev/hidraw0 を決め打ちでもいい
openPath = b'/dev/hidraw0'
# USBの抜き差しがあるなら目的のデバイス&インターフェースを都度都度探す方が安全
for d in hid.enumerate(0x0483, 0x5752):
if d['interface_number'] == 0 and d['product_string'] == "4-key Smart Keypad":
openPath = d['path']
dev = hid.Device(path=openPath)
def set_layer(layerNo: int):
layerNum = layerNo & 0xFF
report = bytes([ 0x64,
0x01,
layerNum,
0x64 ^ 0x01 ^ layerNum ]
+ [0]*60
])
print("HID Data:", report.hex())
ret = dev.write(report)
print("write returned:", ret)
# レイヤ切替
if len(sys.argv) != 2:
print("usage: python changeLayer.py [0-5]")
sys.exit(1)
try:
val = int(sys.argv[1])
except ValueError:
print("引数は0~5の数字にしてください"
sys.exit(1)
set_layer(val)
これを実行すればいいんだけど、/dev/hidrawxの読み書きは管理者権限が必要。
なので、コマンドはsudo付けて実行する。
$ sudo kbLaygerChanger 1
みたいな感じで。
動作確認
Linux 系の OS ではレイヤ機能付きのキーボードがほぼほぼ沈黙してるので、下記で起きっぱにする。
$ hexdump -C /dev/hidraw2
この時、2番じゃなくて3番かもしれないし、あるいは5番かもしれない。
どれが該当するかは path を確認した時の内容をもう一度見てみて、2つ目のインターフェースがどの番号の hidraw に割り当てられているかを見ればOK
$ hexdump -C /dev/hidraw2
00000000 fb 03 00 00 00 f8 00 00 00 00 00 00 00 00 00 00 |................|
00000010 fb 03 00 00 02 fa 00 00 00 00 00 00 00 00 00 00 |................|
00000020 fb 03 00 03 00 fb 00 00 00 00 00 00 00 00 00 00 |................|
00000030 fb 03 00 03 02 f9 00 00 00 00 00 00 00 00 00 00 |................|
00000040 fb 03 05 00 00 fd 00 00 00 00 00 00 00 00 00 00 |................|
00000050 fb 03 05 00 02 ff 00 00 00 00 00 00 00 00 00 00 |................|
00000060 fb 03 05 03 00 fe 00 00 00 00 00 00 00 00 00 00 |................|
00000070 fb 03 05 03 02 fc 00 00 00 00 00 00 00 00 00 00 |................|
で、実行していると気にキーを押すと、こんな感じでキーを押したときの履歴が見れる。
最後の行が例としてわかりやすい。
fb 03:ID や識別番号。今回はあまり深堀しなくてもいい部分。
05:レイヤ番号。Vaydeer の 4key は6つのレイヤを持ってるので一番最後のレイヤに切り替えた。
03:操作されたキーのハードウェア番号。3なので一番最後の4つ目のキーのこと。
02:キーがリリースされた。00で押下。
fc:チェックサム
次に、この状態で別のコンソールを開いて、実際に到着しているキーコードを確認してみる。
evtest というコマンドを実行すると、どのイベントを見るのか選択する画面が出る。
動作確認のときにしか使わない?かもしれないので、とりあえずは総当たりで試してもいいけど、まぁ、下記のように表示されたら event0 がそれっぽいよね。
$ evtest
No device specified, trying to scan all of /dev/input/event*
Not running as root, no devices may be available.
Available devices:
/dev/input/event0: Vaydeer 4-key Smart Keypad
/dev/input/event1: Vaydeer 4-key Smart Keypad Mouse
/dev/input/event2: Vaydeer 4-key Smart Keypad Consumer Control
/dev/input/event3: Vaydeer 4-key Smart Keypad System Control
/dev/input/event4: gpio-keys
/dev/input/event5: Virtual Keyboard
Select the device event number [0-5]:
てことで、0 を選択すれば /dev/input/event0 になるので、0 を選択。
ずらずらとサポートしているイベントコードが出力された後に、待ち受け状態になる。
そこで何かキーを押してみると…
Testing ... (interrupt to exit)
Event: time 1768188109.082095, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70004
Event: time 1768188109.082095, type 1 (EV_KEY), code 30 (KEY_A), value 1
Event: time 1768188109.082095, -------------- SYN_REPORT ------------
Event: time 1768188109.154083, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70004
Event: time 1768188109.154083, type 1 (EV_KEY), code 30 (KEY_A), value 0
Event: time 1768188109.154083, -------------- SYN_REPORT ------------
Event: time 1768188109.418116, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70005
Event: time 1768188109.418116, type 1 (EV_KEY), code 48 (KEY_B), value 1
Event: time 1768188109.418116, -------------- SYN_REPORT ------------
Event: time 1768188109.486104, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70005
Event: time 1768188109.486104, type 1 (EV_KEY), code 48 (KEY_B), value 0
Event: time 1768188109.486104, -------------- SYN_REPORT ------------
Event: time 1768188109.718140, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1768188109.718140, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1768188109.718140, -------------- SYN_REPORT ------------
Event: time 1768188109.790126, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1768188109.790126, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1768188109.790126, -------------- SYN_REPORT ------------
Event: time 1768188109.958148, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70007
Event: time 1768188109.958148, type 1 (EV_KEY), code 32 (KEY_D), value 1
Event: time 1768188109.958148, -------------- SYN_REPORT ------------
Event: time 1768188110.026138, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70007
Event: time 1768188110.026138, type 1 (EV_KEY), code 32 (KEY_D), value 0
Event: time 1768188110.026138, -------------- SYN_REPORT ------------
押されたキーがどのキーコードを出しているかわかる。
USB でやり取りされている内容の確認方法:Windows の場合
Windows なら Wireshark 一択じゃないかな。
ただ、確か Wireshark を何も考えずにインストールすると、USBcap のドライバはインストールされなかったはずなので、その時は個別で USBcap のみインストールするか、Wireshark ごと入れ直すか。

で、単純にキャプチャしているとログはどんどん流れる。
なので、ある程度フィルタリングで絞ってあげると良い。
今回は以下のようなフィルタで Vaydeer 純正アプリがデバイスにどんなコマンドを送っているかを見ていた。
usb.endpoint_address.direction == 0 && _ws.col.info == “URB_INTERRUPT out”
USB でやり取りされている内容の確認方法:Linux の場合
準備
普通のキーボードなら準備なんていらない。
けれど、レイヤ機能付きで QMK/VIA 非対応なものは準備が必要。
/dev/hidraw の何番をそのキーボードが出力用に使っているか、を調べ、それをオープンする必要がある。
まぁ、ぶっちゃけUSB機器が少なければ総当たりで試してもいいけど…
権限がグループとその他にも rw がついてる 2 の可能性は高いからそれで見てもいいかも。
$ ls -al /dev/hidraw*
crw------- 1 root root 245, 0 1月 12 11:28 /dev/hidraw0
crw------- 1 root root 245, 1 1月 12 11:28 /dev/hidraw1
crw-rw-rw- 1 root root 245, 4 1月 12 11:28 /dev/hidraw2
crw------- 1 root root 245, 5 1月 12 11:28 /dev/hidraw3
$ hexdump -C /dev/hidraw2
この状態で、以下の方法を試す必要がある
usbmon を使う
まずは対象のデバイスを特定する。
$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 006 Device 004: ID 0483:5752 STMicroelectronics 4-key Smart Keypad
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
4keyなデバイスなので Bus 006 が該当機器。
$ sudo cat /sys/kernel/debug/usb/usbmon/6u
c2258a00 1666281797 C Ii:6:004:3 0:4 16 = fb030103 00fa0000 00000000 00000000
c2258a00 1666281904 S Ii:6:004:3 -115:4 16 <
c9f5a000 1666291792 C Ii:6:004:2 0:4 12 = 00000400 00000000 00000000
c9f5a000 1666291890 S Ii:6:004:2 -115:4 12 <
c2258a00 1666349798 C Ii:6:004:3 0:4 16 = fb030103 02f80000 00000000 00000000
c2258a00 1666349871 S Ii:6:004:3 -115:4 16 <
c9f5a000 1666359794 C Ii:6:004:2 0:4 12 = 00000000 00000000 00000000
c9f5a000 1666359876 S Ii:6:004:2 -115:4 12 <
キーを押すたびにその内容が表示される。
evtest コマンドを使う
$ evtest
No device specified, trying to scan all of /dev/input/event*
Not running as root, no devices may be available.
Available devices:
/dev/input/event0: Vaydeer 4-key Smart Keypad
/dev/input/event1: Vaydeer 4-key Smart Keypad Mouse
/dev/input/event2: Vaydeer 4-key Smart Keypad Consumer Control
/dev/input/event3: Vaydeer 4-key Smart Keypad System Control
/dev/input/event4: gpio-keys
Select the device event number [0-4]:
event0 がそれっぽいので、0 と入力すれば /dev/input/event0 が選択される。
ずらずらとサポートしているイベントコードが出力された後に、待ち受け状態になり、そこで何かキーを押してみるとキーイベントの詳細がいろいろ見られる。
USB機器の詳細情報を確認する
$ udevadm info -a -n /dev/hidraw0
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/platform/soc/1c1b400.usb/usb6/6-1/6-1:1.0/0003:04>
KERNEL=="hidraw0"
SUBSYSTEM=="hidraw"
DRIVER==""
ATTR{power/control}=="auto"
ATTR{power/runtime_active_time}=="0"
ATTR{power/runtime_status}=="unsupported"
ATTR{power/runtime_suspended_time}=="0"
[…省略…]
まだまだずらずらと出るけど、ここ見ておけばUSBのどこにどう繋がっているかの親子関係等を含めていろいろわかる。
この記事にコメントしてみる