以前の構成
この記事のときはラズパイだったけど、ラズパイがちょっとオーバースペックすぎ。
なので、今は Nano Pi になってる。
Nano Pi の良いところは、有線LAN x1、USB Type-A x1、ピンヘッダで USB があと2つ出せることかな。
そして、今回、「レイヤ機能搭載キーボード」を試してみることに。
今回購入したもの

メーカー:Vaydeer
https://www.vaydeer.com/products/vaydeer-one-handed-macro-keyboard-9-programmable-keys
レイヤ機能が使える。これが大前提だった。
そして、多くの製品が3層なのに対して、こいつはなんと6層w
なのに、4000円ちょっとと安い。
注意事項
Windows ではなくて Linux で使う場合のお話。
公式ページにもあるように Windows は動作保証されているので特に問題ない。
が、Linux だと別で、基本この手のキーボードは「キーボードではない HID」みたいな感じで認識される。
マクロキーボードとかプログラマブルキーボード、そして、こうしたレイヤ機能を搭載したキーボードもご多分に漏れず、HID だけどキーボードと認識されないものがある。
あるいは、なぜかキーコードが一切出てこないとかもある。
端的に、Linux(Ubuntu)では使えない。
普通に繋げただけじゃね。
目的
前回同様、今回も「GPIO」の代わりにしたいだけ。
が、注意事項の通り、普通に繋げただけじゃ使えない。
キーイベントが拾えれば何とかなる。はず。
結論
使える。
レイヤ切り替えもボタンにその機能を割り当てる必要があるが、問題ない。
使えるようにする方法
このキーボードは Linux 機にただ挿しただけだとキー出力をしないので、下記を実行する。
/etc/udev/rules.d/99-vaydeer-hidraw.rules を作る
SUBSYSTEM=="hidraw", \
KERNEL=="hidraw*", \
ENV{DEVPATH}=="*0483:5752*", \
ATTRS{bInterfaceNumber}=="02", \
SYMLINK+="vaydeer-hidraw", \
MODE="0666"
ベンダIDとプロダクトID、インターフェース番号で絞り込んでるので同じ型番の製品であれば汎用が効くはず。
動かない場合は
ENV{DEVPATH}==”*0483:5752*”, \
ATTRS{bInterfaceNumber}==”02″, \
この2行を手元の機器に合わせればOK
この辺りの値の調べ方は後述。
反映とファイルが作成されたかの確認
# sudo udevadm control --reload-rules
# sudo udevadm trigger
# ls -al /dev/vaydeer-*
/dev/vaydeer-hidraw -> hidraw2
バックグラウンドで走らせるコード
import os, time
def keepHidrawOpen():
devPath = "/dev/vaydeer-hidraw" # シンボリックリンクのpath
while True: # 無限ループ
if os.path.exists(devPath): # シンボリックリンクがあれば実行
try:
fd = os.open(devPath, os.O_RDONLY) # 読み込みのみ
while True: # hidrawが開けたらずっと繰り返す
try:
os.read(fd, 64) # 実読み込みだけど読み込むだけで何もしない
except:
break # 途中で抜き差し等されたらwhileを抜ける
time.sleep(0.01) # ちょっとだけ間を入れた方がいいかも?
except OSError:
print("not found layered keyboard")
time.sleep(5) # シンボリックリンクがなければ5秒待ってから繰り返し
if __name__ == '__main__':
keepHidrawOpen()
これを
$ python3 test.py &
としてもいいし、サービスに登録して起動時に自動実行させても良い。
これで無事、「挿しただけでは使えないキーボード」が「視しただけで使える」ようになる。
なぜこれは挿しただけじゃ使えないのか
ざっくり言うと、HID の生データを読みに行くような処理がない限り、自分からキーコードを出さないから。
なんでこんな仕様にしてあるんだろう…(謎)
使えるようになるまでの経緯
まぁ、購入時に「レイヤ機能付きだから、素直には使えないよな…」と覚悟はしていたので、USB 挿してキーをどれだけ押そうが反応しないのは当然か。
ということで以下のコマンドを実行して、このキーボードの状態を確認してみる。
つらつらと各デバイスの情報が表示される。
cat /proc/bus/input/devices
その中で、
I: Bus=0003 Vendor=0483 Product=5752 Version=0111
で始まるブロックがいくつかあった。
今回の製品では4つ、これが他の製品なら増減するかもしれない。
その4つから、「N: Name=」の部分を抜き出してみると以下の通りになっていた。
Vaydeer 9-key Smart Keypad
Vaydeer 9-key Smart Keypad Mouse
Vaydeer 9-key Smart Keypad Consumer Control
Vaydeer 9-key Smart Keypad System Control
この4つのうち、どれかからキーイベントが発火していればいいのだけれど…
ということで、次のコマンド。
sudo evtest /dev/input/event0
そうすると、下記のようにずらずら出てきた。
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x483 product 0x5752 version 0x111
Input device name: "Vaydeer 9-key Smart Keypad"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 1 (KEY_ESC)
………
………
………
………
………
Properties:
Testing ... (interrupt to exit)
出てきたんだけど、本来ならここでキーを押すとキーコード等の情報が続いて表示されるはずなんだけど、何を押してもなにも出てこない…
event0~4 まで存在していたが、そのどれもが何も出ていない。
そりゃぁ、なんも入力できんわけだわな。
OS 側から見ると、USB キーボードが繋がったけど、キー入力動作は何も行われてないと見える。
じゃぁ、これはどうだ?
hexdump -C /dev/hidraw0
これは evtest よりも低レベルで入力状態を見ることができる。
このコマンド実行を実行してからキーを入力すると16進数の何かがずらずら出るはず。
こちらは hidraw0~3 まであるので、全て試してみると… hidraw2 で出てきた。他は出ない。
けれども、ここで拾えるものがあるということは、希望がある。
要は、HID デバイスとしてはしっかり認識されていて、なんらかのデータがこのキーボードからは出ていて、それをOS 側でも捉えられているということになるから。
そして、この2つのコマンドを実行していて気づいたことがある。
hexdump -C /dev/hidraw0
を実行している間は
sudo evtest /dev/input/event0
でキーコードが出てくる。
なるほど。
何かが HID を読みに行ったときに応答する感じなのか。
ということで、超雑デバッグとして下記を実行してみた。
sudo hexdump -C /dev/hidraw2 &
すると、うん、正常に入力できる。
レイヤの切り替えもボタンに「次へ」か「前へ」を割り当てておけば、正常にレイヤも切り替わる。
とういことで、第一段階としてはここまで。
より良い回避策の模索
上で実行したコマンド
sudo hexdump -C /dev/hidraw2 &
これはなるべく避けた方がいい。
必ずしも hidraw2 にある、2 が 2 であるかはわからないから。
完全に使用するデバイスが固定で、他に USB の抜き差しがないとか再起動がないとかなら決め打ちでも問題ないけど、変わった瞬間死ぬのでデバッグ用と考えた方が無難。
それに、hexdump で /dev/hidraw2 を掴みっぱなしってのもなんか気持ち悪い。
なので、hidraw2 の番号が変わっても大丈夫なようなシンボリックリンクを作ることにした。
/etc/udev/rules.d/ 内に 99-vaydeer-hidraw.rules というようなファイルを作っておくと、このファイルの内容に一致する /dev/hidraw[x] へのシンボリックリンクが自動的に作成されるようになる。
まずは今の情報を確認
udevadm info -a -n /dev/hidraw2
これを実行するとずらずらといろんな情報が出てくる。
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:0483:5752.0001/hidraw>
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"
looking at parent device '/devices/platform/soc/1c1b400.usb/usb6/6-1/6-1:1.0/0003:0483:5752.0001>
KERNELS=="0003:0483:5752.0001"
SUBSYSTEMS=="hid"
DRIVERS=="hid-generic"
ATTRS{country}=="00"
ATTRS{power/control}=="auto"
ATTRS{power/runtime_active_time}=="0"
ATTRS{power/runtime_status}=="unsupported"
ATTRS{power/runtime_suspended_time}=="0"
looking at parent device '/devices/platform/soc/1c1b400.usb/usb6/6-1/6-1:1.0':
KERNELS=="6-1:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="usbhid"
ATTRS{authorized}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceClass}=="03"
ATTRS{bInterfaceNumber}=="00"
ATTRS{bInterfaceProtocol}=="00"
ATTRS{bInterfaceSubClass}=="00"
ATTRS{bNumEndpoints}=="02"
ATTRS{supports_autosuspend}=="1"
この中の情報から他の hidraw 情報とは異なる部分を探す。
当然、他の機器が接続されたときにもそれらとは区別できなければならない。
よくあるのは、ベンダID、プロダクトID、シリアル、インターフェースあたりか。
ただ、これらの情報が同一ブロックにまとまってないとダメ。
ブロック同士は親子関係にあるけど、ブロックをまたがった情報での絞り込みはできない。
で、眺めていると、やはり、ベンダID、プロダクトID、シリアル、がまとまっているブロックがあった。
が、インターフェース等、hidraw[x] のxに当たる数字を区別できる情報がない…
ならば、ENV 情報から持ってくるか?
udevadm info -n /dev/hidraw2
こうすると以下のような情報が出てくる。
P: /devices/platform/soc/1c1b400.usb/usb6/6-1/6-1:1.2/0003:0483:5752.0003/hidraw2
M: hidraw2
R: 2
J: c245:2
U: hidraw
D: c 245:2
N: hidraw2
L: 0
S: vaydeer-hidraw
E: DEVPATH=/devices/platform/soc/1c1b400.usb/usb6/6-1/6-1:1.2/0003:0483:5752.0003/hidraw/hidraw2
E: DEVNAME=/dev/hidraw2
E: MAJOR=245
E: MINOR=2
E: SUBSYSTEM=hidraw
E: USEC_INITIALIZED=4821818
E: ID_VENDOR_FROM_DATABASE=STMicroelectronics
E: ID_PATH_WITH_USB_REVISION=platform-1c1b400.usb-usbv1-0:1:1.2
E: ID_PATH=platform-1c1b400.usb-usb-0:1:1.2
E: ID_PATH_TAG=platform-1c1b400_usb-usb-0_1_1_2
E: ID_FOR_SEAT=hidraw-platform-1c1b400_usb-usb-0_1_1_2
E: DEVLINKS=/dev/vaydeer-hidraw
E: TAGS=:seat:
E: CURRENT_TAGS=:seat:
…って、ベンダIDとかプロダクトIDが個別には用意されてないのね…
てことで、この2つの情報を組み合わせて このこのキーボードが接続されたとき、キーコードを吐いてくれる hidraw を特定できるような条件を作る。
ルールファイルの作成
上で得られた情報をもとに、下記のような内容のファイルを作った。
ファイル:/etc/udev/rules.d/ 内に 99-vaydeer-hidraw.rules
SUBSYSTEM=="hidraw", \
KERNEL=="hidraw*", \
ENV{DEVPATH}=="*0483:5752*", \
ATTRS{bInterfaceNumber}=="02", \
SYMLINK+="vaydeer-hidraw", \
MODE="0666"
ENV の DEVPATH にベンダIDとプロダクトIDが入ってるので、それをワイルドカード入りの条件で拾う。
目的の hidraw に該当するインターフェース番号である02を探す。
あ、このルールファイル「1行で書く」が大前提なので、開業するときは末尾に「\」(バックスラッシュ)が必要。
これを入れずに「,」だけで改行しちゃうと一致条件が全く意味なくなるので注意。
そんな感じでファイルを作った後、下記を実行。
# sudo udevadm control --reload-rules
# sudo udevadm trigger
# ls -al /dev/vaydeer-*
/dev/vaydeer-hidraw -> hidraw2
と、シンボリックリンクが作成されればOK
これで、再起動や抜き差しでhidraw2がhidraw3に変わったとしても、hidraw1に変わったとしても、それが vaydeer-hidraw に紐づけされる。…はず。
常に hidraw を読んでいる状態を作る
とりあえず、下記のようなコードを作っておいてバックグラウンドで実行させておく。
(サービス化しても良い)
import os, time
def keepHidrawOpen():
devPath = "/dev/vaydeer-hidraw" # シンボリックリンクのpath
while True: # 無限ループ
if os.path.exists(devPath): # シンボリックリンクがあれば実行
try:
fd = os.open(devPath, os.O_RDONLY) # 読み込みのみ
while True: # hidrawが開けたらずっと繰り返す
try:
os.read(fd, 64) # 実読み込みだけど読み込むだけで何もしない
except:
break # 正常に読めなかったらwhileを抜ける
time.sleep(0.01) # ちょっとだけ間を入れた方がいいかも?
except OSError:
print("not found layered keyboard")
time.sleep(5) # シンボリックリンクがなければ5秒待ってから繰り返し
if __name__ == '__main__':
keepHidrawOpen()
これが実行されている限り、hidraw ファイルにアクセスされ続けるので、キーボードがキーコードを出力し、キー入力ができるようになる。

この記事にコメントしてみる