Open21

安価なUSB HIDを作りたい

Akihiro MATOBAAkihiro MATOBA

Windowsにぶっ刺してみる。ポピン! cccccccccccccccccccccccc
どのキーを押しても、ノブを回しても、ノブを押し込んでも 「c」が入力される。なんじゃこりゃ。
→ そういう仕様らしい。アプリでキーアサインしてねということらしい

Akihiro MATOBAAkihiro MATOBA

Windows-X M でデバイスマネージャを見てみる。「HIDキーボードデバイス」×2として認識している模様。
「接続別」で見てみると、Composite Device の下に5個もぶら下がっていた。

インタフェースが4つ。それぞれのエンドポイントが1つずつ (EP1IN/IF0、EP2OUT/IF1、EP3IN/IF2、EP2IN/IF3)。なんかへんだけど、まあそういうものなのかな。

Akihiro MATOBAAkihiro MATOBA

安価なUSBデバイスといえば、おそらく中身はCH552だろう、と思いつつ、開腹してみた。
予想通り、CH552Gだった。

Akihiro MATOBAAkihiro MATOBA

パターンを追いかけて回路図を起こしてみた。
USBコネクタのCC1とCC2がNot Connectedなので、USB-C/USB-Cケーブルだと使えない (規格上、給電されない)。試してみたらやっぱり使えなかった。5kΩの抵抗2本を加えてGNDにプルダウンしておくと給電されるので使えるようになるはず。

オープンになっているR12を短絡して、USBのD+に接続した状態でUSBケーブルを接続すれば、CH552GのDFUモードに入れるのだと思う。R10が実装されているのはなんでだろう……ここもオープンにしてコストを抑えてもよさそうなのに。回路を読み間違えているのだろうか。

Akihiro MATOBAAkihiro MATOBA

WebHID で叩いてみる:

await navigator.hid.requestDevice({filters: [{vendorId: 0x1189, productId: 0x8890}]})
await navigator.hid.requestDevice({filters: [{vendorId: 0x1189}]})
Akihiro MATOBAAkihiro MATOBA

アプリをダウンロードして、Wiresharkしつつ、アプリからキーアサインを設定してみた。
「HID準拠ベンダー定義デバイス」あてに Output Report 0x03 を何枚か連続して送るみたい。

Key1に「ab」を設定した場合:

03 a1 01  # これから送るよー
03 01 11 02  # 03 Key Device Qty
03 01 11 02 01 00 04  # 03 Key Device Qty Nth Mod Keycode
03 01 11 02 02 00 05  # 03 Key Device Qty Nth Mod Keycode
03 aa aa  # おわり

3行目の説明

  • Key: Key1~Key12=01~0c, ダイヤル1左/押/右=0d/0e/0f、ダイヤル2左/押/右=10/11/12、ダイヤル3左/押/右=13,14,15
  • Device: 上位バイトがLayerで0x10~0x30、下位バイトがデバイスで Keyboard=01
  • キーボードの場合(5アクションまで設定できる)
    • Qty はそのキーに格納したキーコード配列の長さ
    • Nth はキーコード配列の添え字
    • Mod はモディファイヤ、なし=00、Ctrl=01、Alt=04、Shift=02、Win=08、右Ctrl=10、右Alt=40、右Shift=20、右Win=80
    • Keycode は USB HID キーコード(Keyboard/Keypad Page (0x07))

Key1に「Play/Pause(マルチメディア操作)」を設定した場合

03 a1 01  # これから送るよー
03 01 12 cd  # 03 Key Device Action
03 aa aa  # おわり
  • Device: 上位バイトがLayerで0x10~0x30、下位バイトがデバイスで multimedia=02
  • Actionとして、Consumer Page (0x0C)
    • Play/Pauseがcd、nextがb5、Prevがb6、Vol+がe9、Vol-がea、Muteがe2

Key1に「Mouse Left Button」を設定した場合

03 a1 01  # これから送るよー
03 01 13 01  # 03 Key Device Action
03 aa aa  # おわり
  • Device: 上位バイトがLayerで0x10~0x30、下位バイトがデバイスで Mouse=03
  • Actionとして、Button Page (0x09)
  • Leftが01、Rightが03、Centerが04
  • Wheel Upを割り当てたときは、03 01 13 00 00 00 01
  • Wheel Dnを割り当てたときは、03 01 13 00 00 00 ff
  • Wheel Dn + Shiftを割り当てたときは、03 01 13 00 00 00 ff 02
  • Actionの2バイト目以降の00 00 00 あたり、アプリでいろいろ設定しても変化しないのでよくわからない。

LED Mode01を設定した場合

03 a1 01  # これから送るよー
03 b0 18 01  # 03 Key Device Action
03 aa aa  # おわり
  • KeyとDeviceが b0 ?8 固定
  • ActionがLED Modeで、オフ=0、固定Blink=01、流れる=02
Akihiro MATOBAAkihiro MATOBA

WebHID経由で自由にOutput Report を送れるので試してみた。

手順

  • https://nondebug.github.io/webhid-explorer/ を開く
  • 念のため別タブで chrome://device-log/?refresh=1 を開いておく
  • Connect を押して、1189:8890 (=今回使っているマクロパッド) を選択する
  • Connect右側のドロップダウンに選択肢が4つ追加されるので、矢印キーで選び、Device Info でUsage: FF00:0001 (Vendor-defined page 0xFF00 usage 0x0001) となっているものを選ぶ
  • Output Report 欄に 03 b0 18 01と入力して SEND を押す → LEDがMode1(Blink)になる
  • Output Report 欄に 03 b0 18 02と入力して SEND を押す → LEDがMode2(NightRider)になる
  • Output Report 欄に 03 01 13 00 01と入力して SEND を押す → Key1がマウス右移動になる

Mouse の謎パラメータが判明した:
03 01 13 Button dX dY Wheel Modifier

Akihiro MATOBAAkihiro MATOBA

REMAP(VIAプロトコル)に対応しているCH552なキーボード、ただしマウスキーなどの対応がされていない模様。
https://github.com/yswallow/CH552duinoKeyboard

ビルドしてみた。
CH552Gは Flash 16kb, RAMが1280(1k+256), DataFlashが128で、
USB Setting: USER CODE w/ 148B ram とのことなので、

最大14336バイトのフラッシュメモリのうち、スケッチが6569バイト(45%)を使っています。
最大876バイトのRAMのうち、グローバル変数が127バイト(14%)を使っていて、ローカル変数で749バイト使うことができます。

インタフェースが1つ、エンドポイントが2つ (EP1IN, EP1OUT)、レポートが一つに「キーボード+ベンダー定義+マウス」が収容されている。

USB Device Tree Viewer で見てみた。
String Descriptor #2 が返ってきてない……。なんでだ……。

間違いを発見。0x16じゃなくて0x1cにしたら動いた。あとで報告しておこう。

レポートを見ようと思ったけど、USB Device Tree Viewer見られなかった。
Wiresharkで見ようと思ったら、URB_FUNCTION_ABORT_PIPE が出ている。なんだろう。
とりあえず劣後してレポートを見てみる。ふむ。1枚のレポートにUsageが3つ入ってるのね。なるほど。

Akihiro MATOBAAkihiro MATOBA

https://github.com/yswallow/CH552duinoKeyboardhttps://usevia.app/ でキー設定を流し込んでみようと、こんなjsonを書いてみた。
jsonの読み込みはできたんだけど、authorize Device で「Failed to write the report.」というエラーに阻まれる。

{
    "name": "CH55xduinokbd",
    "vendorId": "0x1209",
    "productId": "0xC55D",
    "lighting": "qmk_rgblight",
    "matrix": { "rows": 1, "cols": 5 },
    "layouts": { "keymap": [ ["0,0","0,1","0,2",{"x":0.5},"0,3","0,4"] ] }
  }

F12でChromeのDeveloper Console を開いて sendReport(0,data) にブレークポイントを置いて待ち構えたら、DOMException: Failed to write the report. というのを捕まえた。
Console.dir(r) してみたら、こんな感じ:

DOMException: Failed to write the report.
code: 0
message: "Failed to write the report."
name: "NotAllowedError"
[[Prototype]]: DOMException

なんで?
MDN https://developer.mozilla.org/ja/docs/Web/API/HIDDevice/sendReport
仕様 https://wicg.github.io/webhid/#dom-hiddevice-sendreport

If this report is a blocked report, ...... reject promise with a "NotAllowedError"

ブロックリスト https://github.com/WICG/webhid/blob/main/blocklist.txt
送信先は vendor:0x1209, product:0xc55d, usagePage:0xff60, usage: 0x61, reportId:0x08 なので、これに引っかかってるわけではないんだけどなぁ……。

ん、なんだこれ。
https://github.com/WICG/webhid/issues/12#issuecomment-645487885

WebHID blocks access to any input, output, or feature reports defined in a top-level HID collection with IDs that would allow the device to be treated as a mouse or keyboard input.

同じコレクション内に、ブロックリストに該当するレポートがあるとダメなのか?

Akihiro MATOBAAkihiro MATOBA

https://nondebug.github.io/webhid-explorer/ で、同じように Output Report で 00 01 を送ってみたら、同じように阻まれた。
デバイスログ: chrome://device-log/ には Invalid output report ID HID Transter Failed が出てる。via-appで失敗したときと同じメッセージだ。

ということは……?

  • オリジナルのFirmwareのときは webhid-explorer で送信できた。
  • Firmwareを CH552duinoKeyboard にしたら、webhid-explorer で送信できなくなった。

Collectionの木構造が異なっているのが気になる。

CH552duinoKeyboardのHID Descriptorを、
いまの「ひとつのCollectionに、キーボード/マウス/ベンダ準拠デバイスがぶら下がる」から、「Collectionが3つあり、キーボード/マウス/ベンダ準拠デバイスがそれぞれにぶら下がる」形に変えてみてやってみよう。

Akihiro MATOBAAkihiro MATOBA

ん? まてよ!? Output Report で 08 01 00{31}を送ってみたら、送れた。
TypeError DOMException じゃないのか……。

まあでもVIAでエラーが出るのは将来的には回避しないといけないから、まずはCollectionの形を変えてみることにしよう。