秩父
Chrome App で USB デバイス (AVR V-USB) にアクセスする
Chrome App は、chrome.usb という API を通じて低レベルなUSBデバイスドライバをJavaScriptで書くことが可能になっている。chrome.usb は libusb 相当の API を提供している。
つまり、自分で作った USB デバイスと通信して、ブラウザ(といっても App だけど)からそれのI/Oを操作でき、LED を HTML+CSS+JS で UI を作って操作したり、ミサイルも JavaScript で飛ばせる。
そこで、AVR (200円ぐらいのワンチップマイコン) とその上で動くソフトウェアUSB実装である V-USB でカスタムデバイスを作成し、Chrome App から呼んでみた。
デバイス側
だいぶ前に実験した結果、Mac OS においては HID デバイスとしてUSBデバイスを作ると OS の管理下におかれ、libusb などからアクセスする手段が殆どなくなってしまうことがわかっていた。libhid 的なものがあればいいのだけれど、すくなくともまともにメンテされているのは当時見当らなかったので諦めた。
今回は教訓をいかして最初からHIDではないカスタムクラスのUSBデバイスにすることとした。
デバイス側の実装及び V-USB の設定ファイルは以下の通り
- https://github.com/cho45/AVR-V-USB-Vendor-Skelton/blob/master/main.c
- https://github.com/cho45/AVR-V-USB-Vendor-Skelton/blob/master/usbconfig.h
AVR ATmega168、16MHz で動かして試している。
HID にしなくともデバイス側の実装はあまり大差ない。
USB_CFG_DEVICE_CLASS と USB_CFG_INTERFACE_CLASS を 0xff (vendor) にしておかないと claim_interface に失敗したので忘れずに変えておく必要がありそう。
ホスト側
ホスト側では、まず libusb + ruby における実装を書いて試した。
interrupt_transfer はブロックをとれるみたいなのだが、うまく呼ばれなかったので深追いせず普通に呼んでいる。
libusb を使うにしても USB のプロトコルの知識が必要になるのが面倒
そして Chrome App の実装も書いた。主な実装箇所は以下
最初どうしても openDevice が成功せず途方にくれていたが、Chrome 自体を再起動することで成功するようになった。諦めなくてよかった。
ちなみに公式?にある usb のサンプルは API が最新に追従していないので参考にしないほうがいい。
USB プロトコルまわり
知っておかなければいけないこと
- USB は基本的に片方向の通信
- CONTROL 転送は1度のトランザクションが双方向で Stop-and-wait ARQ みたいな感じになってる
- 必ずホストから先に通信が始まる (つまりデバイス側からデータを push することはできない)
- USB には転送方法がいくつかある・それぞれ再送があったりなかったりするので必要に応じて使う
- CONTROL 転送
- ISOCHRONOUS 転送
- BULK 転送
- INTERRUPT 転送
参照
このエントリを参照するエントリ
ドッグフーディング以上の「ユーザ視点」はない
✖
Chrome App をテストする
Chrome App という、Chrome Extension の延長上にあるスタンドアロンアプリを作れる仕組みがある。これは、しばしば出てくる HTML + JavaScript でスタンドアロンアプリを作れるやつの Chrome 版にあたる。
結構いろいろとAPIが整備されていて面白いんだけど、いまいちテストを書く方法がわからなくていろいろ試した。Chrome の API を使っている場合、どうしても Chrome のコンテキストで実行する必要があり、なかなか面倒くさい。
protractor (webdriver-js ラッパ) を使う
protractor は Angular.JS 用のエンドトゥーエンドテストフレームワークで、いろいろ環境を一発でつくるのが便利なので使ってる。Angular.JS も使ってるから丁度いい。
コマンドラインから Chrome App を起動すると、まず1つ普通のChromeのウィンドウが開き、次にアプリケーションのウィンドウが開くという挙動をする。なので、アプリケーションのウィンドウが起動するのを待って、そっちにスイッチする必要がある。
403 Forbidden このあたりに書いてあるのをなおして、以下のようなのを書いた。
function switchToAppWindow (nth) { function _switchToAppWindow (n) { if (n <= 0) throw "failed to switch to app window"; browser.driver.getAllWindowHandles().then(function (handles) { if (handles.length <= nth) { _switchToAppWindow(n--); } else { browser.driver.switchTo().window(handles[nth]); } }); } _switchToAppWindow(1000); }
switchToAppWindow(1) で2番目に生成されたウィンドウにスイッチできる。
これで、Angular.JS レベルでの E2E テストは書けるようになる
Unit テストを書く
しかし、E2E テストだと Chrome 向けに Chrome 専用の API を使ったライブラリのテストが書きにくいので、普通の Unit Test みたいなのも書きたくなる。
いろいろ試したけど、結局以下のようにした。
- テスト用の .html を用意する
- E2E テスト用のスクリプトから、executeScript でテスト用の .html を開き、テストを実行する
Jasmine を読みこませてテストを実行させ、自力でテスト結果を取得して表示してる。
ただ、罠があって、Jasmine が eval を使っててそのままだと Content Security Policy にひっかかって実行できない。これはmanifest.json に sandbox 指定することでも解決するけど、これだと本来の目的の API がたぶん使えないので、jasmine を書きかえて治す必要がある。global オブジェクトを取得しようとして eval を使っているので (なんで?) そこだけ自分で変えたら動く (Edge バージョンだと eval するコードがないので普通に動きそう)。
スケルトンプロジェクト
をつくった