kumpei.ikuta.me

WebUSB でブラウザから ESC/POS プリンタを制御

今回実装したもの

以下のリンクより遊べます。エプソン製の TM シリーズのプリンタであれば大体動くとは思いますが、動作確認は TM-T90 でしかとれていません。横幅がプリンタの印字可能ドット数を超える画像を印刷しようとすると、数メートルのゴミを印刷してしまうので注意してください。 USB ケーブルかプリンタの電源ケーブルを抜くことで中断できます。また、事前に udev ルールの適切な設定が必要です。

WebUSB ESC/POS demo

プリンタ購入〜印字テスト

ふとレシートを作りたくなったので、ヤフオクで TM-T90 というのを買ってみました。

なぜか 6 台届いたプリンタ
なぜか 6 台届いたプリンタ

USB 接続モデルだったので USB 2.0 Type-B (要は、現代的なプリンタ用の USB ケーブル)でサクっと PC に繋ぎます。そのままでは権限の問題で一般ユーザはアクセスできないので、 udev ルールを書いて対応します(Windows や macOS でのやりかたについてはよくわかりませんでした)。

/etc/udev/rules.d/99-escpos.rules に以下の設定を書きました。

SUBSYSTEM=="usb",
ATTRS{idVendor}=="04b8",
ATTRS{idProduct}=="0202",
MODE="0664",
GROUP="wheel"

ざっくり説明すると、この VID/PID を持つデバイスには wheel グループに属するユーザがアクセスできるよ、という設定です。

このようなプリンタは ESC/POS というコマンドを用いて制御するのが一般的なようです。PyPI にパッケージ があったので、まずはこれでテストしてみます。

from escpos.printer import Usb
p = Usb(idVendor=0x04b8, idProduct=0x0202)

text = "Lorem ipsum dolor (中略) id est laborum."

p.text(text)
p.cut()

Pythonで印刷テスト
Pythonで印刷テスト

めちゃくちゃ簡単に印刷できました…が、このライブラリは日本語が出せなかったり、紙幅の設定がいじれなかったりと、できないことがわりと多いです。結局、プリンタに実装されている機能をフルに使うには、本家の EPSON が公開している ESC/POS リファレンスを読みながら生の制御コマンドを読み書きしていくことになります。これがなかなか曲者で、たとえば、日本語を出力するには

  1. FS C コマンドで文字コードを Shift-JIS に設定
  2. ESC R コマンドで国際文字セットを日本語に設定
  3. FS & コマンドで漢字モードを有効化
  4. ESC M コマンドで内蔵フォントを選択
  5. テキストを Shift-JIS でエンコードして送信

という難解なステップを経る必要があります(そして、これらの制御は機種・ベンダー依存です)。この辺を踏まえつつ、JavaScript & WebUSB に乗せていきます。

WebUSB で実装

WebUSB において、USB デバイスとの通信は以下のような手順で行われます。

  1. navigator.usb.requestDevice() で USB デバイスのリストを取得
    • ペアリング済のデバイスが存在する場合は navigator.usb.gerDevice() でも可
  2. open() でデバイスを開く
  3. selectConfiguration() で構成を選択
  4. claimInterface() で排他ロックを要求
  5. selectAlternateInterface() でインターフェースを選択
  6. transferOut() および transferIn() でデバイスと通信
  7. releaseInterface() で排他ロックを解除
  8. close() でデバイスを閉じる

また、claimInterface()DOMException: Unable to claim interface というエラーが生じることがあります。これは、Linux カーネルが usblpusbfs などのドライバを勝手にロードし、デバイスがビジーになってしまうことが原因です。libusb には libusb_detach_kernel_driver という便利なメソッドがあり、これでカーネルが自動でロードしたドライバを unbind することができるのですが、Linux でしか使えないので WebUSB には実装されていないようです。参考:Chromium の Issue

残念ながら JavaScript からはどうにもならないようなので、それらのドライバを自動で unbind するスクリプトを udev ルールに追記します。

RUN+="/bin/sh -c 'echo -n $id:1.0 > /sys/bus/usb/drivers/usblp/unbind && echo -n $id:1.0 > /sys/bus/usb/drivers/usbfs/unbind'"

今回作ったデモページ WebUSB ESC/POS demo では、(日本語を含む)テキストの印刷に加え、画像の印刷と紙幅の変更を行えるようにしています。

日本語と画像の印刷
日本語と画像の印刷

できていないことメモ

  • プリンタからの情報取得
    • 紙幅やフォントなど、内蔵メモリの情報を USB で送ってくれるはずなんだけど受信できない
  • 印刷濃度の設定

おまけ

バグって大量に印刷されたゴミ
バグって大量に印刷されたゴミ

back to index