プログラミング
プリンターの作り方
How to Build a Printer (nishantjosh.dev)
要約
筆者は、e-inkディスプレイであるXteink X3にMacBookから直接印刷できるように、インターネット印刷プロトコル(IPP)を実装しました。メモリ容量の制約から、受信したデータをディスプレイバッファに直接書き込むことで、ディスプレイ自体をストレージとして活用する巧妙な方法を見つけました。これにより、MacBookからXteink X3への印刷が可能になり、独自の「プリンター」が完成しました。
全文翻訳
e-inkディスプレイを自作しようと考えていたところ、オンラインでXteink X3を見つけました。その仕様の細かい部分に、このデバイスが完全にプログラマブルであることが書かれていました。私はすぐに飛びつきました。届いたデバイスは、その薄さに惹かれました。手に持つとしっかりとした感触がありました。
CrossPointファームウェアに機能を追加し始めました。異なる起動アニメーション、本体を振るとルーレットが回る機能、SFネットワーキングイベント用のLinkedIn QRコードなどです。しかし、デバイスに何かを転送するのは手間がかかりました。デバイスのホットスポットに参加し、ブラウザで小さなアップロード用ウェブサイトを開く必要がありました。うんざりしました。
もっと良い方法でデバイスにデータを送れないか探しているうちに、ある考えが浮かびました。紙のように見えるなら、紙のように振る舞うべきだ。それに印刷できるはずだ。プリンターとは何だろうか?MacBookで何かを開き、「プリント」を押してXteinkを選択できるようにしたかったのです。そのためには、コンピューターが通信相手に何を期待しているのかを知る必要がありました。私はインターネット印刷プロトコル、すなわちIPPにたどり着きました。これは、コンピューターがプリンターの対応機能を問い合わせたり、ドキュメントを送信したり、ジョブの状況を尋ねたりすることを可能にします。メッセージはHTTP経由でやり取りされます。「Get-Printer-Attributes」のような操作で機能性を問い合わせ、「Print-Job」で作業を送信します。
私はモノクロ出力、300 dpi、1部、片面印刷を広告しました。ドキュメントフォーマットとしては、Apple rasterとPWG rasterを受け入れました。これは、Macがドキュメントを送信前にピクセルに変換する必要があることを意味します。私のデバイス(ペンギンと名付けました)は、結果を画面に収まるように縮小しました。私はA5とレターサイズの用紙、メディアタイプをステーショナリー、出力トレイを「face-up」と名付けました。私はそれをペンギンと呼びました。その色合いが気に入ったからです。Bonjourを使って、その名前で「_ipp._tcp」サービスをアナウンスしました。アナウンスには、受け入れたフォーマットと、印刷ジョブを送信すべきアドレスが含まれていました。macOSでドライバーレス検出を有効にするには、「_universal」サブタイプも追加する必要がありました。これは、Arduinoラッパーが公開していなかったため、ESP-IDFのmDNS APIを直接呼び出すことを意味しました。
コンピューターにページを送信させることは、仕事の一部に過ぎませんでした。まだこのデバイスでそれを受信する必要がありました。ページをどこに置くのか?300 dpiのレターサイズのページは、2,550 × 3,300ピクセルです。グレースケールピクセルあたり1バイトとすると、圧縮されていない状態でおよそ8.4 MBになります。X3には400 KBのRAMがあり、16 KBはキャッシュ用に予約されています。Wi-Fiを動作させ、プリンターサーバーを実行し、どうにかして丸ごと1ページを受信する必要がありました。Wi-Fiを動作させ、プリンターのページイメージを確保すると、ヒープには6.8 KBしか残りませんでした。
Linuxのmmapを思い出しました。SDカードでそれに似たことをして、もっとRAMがあるかのように見せかけることはできないだろうか?C3のメモリマッピングサポートはフラッシュ用であり、SDカード上のファイル用ではありませんでした。しかし、待てよ。ディスプレイをストレージにできないだろうか?受信したページを変換パイプラインに通し、結果を直接ディスプレイに書き込むとしたらどうなるだろうか?ピクセルをデコードし、画面に収まるように縮小し、白黒にディザリングする。行が準備でき次第、ディスプレイ上のその場所に配置し、作業スペースを再利用する。ページができるまで続ける。ディスプレイにはすでに画面イメージ用のRAMが予約されていました。到着次第、そこにページを構築できるのです。それまで、私はコピーするためだけに完全なセカンドイメージを構築していました。私のデコーダーはすでに1行ずつ動作していました。スケーラーを変更して、完成した行も引き渡すようにし、それらをディスプレイの画面イメージに接続しました。
最初は、紙がプリンターから送り出されるように、ページが帯状に表示されるようにしていました。各中間リフレッシュには約0.5秒かかったため、完成したページを一度に表示するように変更しました。完成したページは、既存のスクリーンショットライターを使用してSDカードにBMPとして保存しました。
画像バッファRAM
チップの400 KBのうち
~113 KB (変更前)
~62 KB (変更後)
これにより、ネットワークスタックはソケットバッファ用のスペースを確保できました。
プレビューにペンギンが表示されました。
なぜかMacBookに「無職転生」のマンガのサンプル画像がありました。プレビューで開き、印刷に進みました。プリンターリストに「penguin」が表示されました。マジかよ。
それを選択して印刷しました。マンガのページはXteink上で非常に良く見えました。記憶では、表示されるまで約1秒かかりました。それは今もそこにあります。
最初の印刷ができるようになるまで、一晩かかりました。プリンターサーバーはリーダー自体で動作します。Wi-Fiネットワークに参加させることも、独自のホットスポット「literate-penguin」を開始させることもできます。私のペンギンはもう読めるのです。コードは私のCrossPointフォークにあり、プリンターの実装も含まれています。保存された印刷物はSDカードに残っており、リーダー上で閲覧できます。私のプリンターには、結局のところ出力トレイがあります。それはフォルダです。