技術 約10分で読めます

HP Sprocket 200のBLEプロトコルを解析してPCから印刷した

いけさん目次

HP Sprocket 200を中古で手に入れた。
ZINKペーパーを使うモバイルフォトプリンターで、スマホから専用アプリ経由で印刷するタイプ。
前にサーマルプリンターをPCから操作したのと同じく、今回もPCから印刷できるか試す。

なぜこれにしたか

ZINKペーパーはHP SprocketでもCanon iNSPiCでも使える互換性がある。
ただHP用の純正ペーパーは流通が少なく、探すのが大変。
Canon iNSPiC用のほうが圧倒的に手に入りやすい。

じゃあ安い中古のSprocketを買って、紙はCanon用を使えばいいかというと、そう単純でもない。
ZINKペーパーは互換性があっても、純正品パックの一番下に入っているスマートシート(青い紙)がないと大抵印刷できない。
このシートがプリンターのキャリブレーションに使われるらしく、他社の紙だけ突っ込んでも動かないケースが多い。

つまり狙い目は「純正ペーパーが付属している中古品」。
この事前情報をもとにSofmapのジャンクコーナーを漁っていたら、約2,000円で純正ZINKペーパー付きのSprocket 200があったので即決した。
なぜかこの個体だけ純正品の紙がまるまる付いていた。

開封

HP SprocketとCanon iNSPiC用ペーパーのパッケージ

買ったのはHP Sprocketのほうだけ。
Canon iNSPiC用のZINKフォトペーパー20枚入りは、HP用の紙が手に入らなくなったときの保険。

HP Sprocket 200の箱の中身

Sprocket 200の箱を開けたところ。
本体、USBケーブル、ZINKフォトペーパーのサンプル、安全情報シートが入っていた。
本体は手のひらサイズで、表面にスペックル模様が入っている。

ZINKペーパーのセット

HP純正とCanon用ZINKペーパーのパッケージ

HP純正(10枚入り)とCanon iNSPiC用(10枚入り)のZINKペーパー。
中身は同じZINK方式だがパッケージと付属のスマートシートが異なる。

スマートシートの比較

上がHP純正のスマートシート、下がCanon用。
スマートシートはパック内の一番下に1枚入っている青い紙で、紙をセットしたときに最初に通してキャリブレーションに使われる。

ZINKペーパーとスマートシートを取り出したところ

左がZINKフォトペーパー、右がスマートシート。
今回はスマートシートだけHP純正を使い、紙本体はCanon iNSPiC用を入れた。
キャリブレーションさえHP純正のスマートシートで通れば、紙自体は他社製でも動くはず。

ZINKペーパーの表裏

白い面が印刷面で、ブランドロゴが入っている面が裏。
裏面を下にしてプリンターにセットする。
よく見るとHP用とCanon用で紙の色味が微妙に違う。同じZINK方式でも製造ロットや仕様が少し異なるのかもしれない。

スマートシートが排出されたSprocket

HP純正のスマートシートとCanon用の紙をセットして電源を入れると、最初にスマートシート(青い紙)が自動で排出された。
キャリブレーション処理は問題なく完了。他社製の紙でも受け付けてくれた。

PCから印刷できるか

HP SprocketにはPC/Mac用のドライバーもデスクトップアプリも存在しない。
HP Smart(HP汎用プリンターアプリ)もSprocket非対応。
公式にはスマホアプリ一択ということになっている。

ただし非公式な方法の報告がある。

方法1: Bluetooth OBEXファイル送信(Windows/Mac)

一番手軽な方法。
WindowsのBluetoothメニューから「ファイルの送信」で画像を直接送る。

  1. Sprocket 200の電源を入れてPCとペアリング
  2. タスクバーのBluetoothアイコン → 「ファイルの送信」
  3. 送信先としてSprocketを選択
  4. 画像ファイルを指定して送信

アプリのフィルターや余白調整は使えず、画像をそのまま送るだけ。
MacでもBluetooth設定から同様にファイル送信できるとの報告がある。

ただしSprocket 200はBluetooth 5.0搭載で、ファームウェアによってはOBEX Push Profile(OPP)が無効化されている可能性がある。
初代Sprocket(Bluetooth 3.0)では比較的安定して動いたらしいが、200では試してみないとわからない。

方法2: obexftpコマンド(Linux/Raspberry Pi)

Raspberry Piのフォトブースプロジェクト「pibooth」で動作実績がある方法。

sudo apt-get install bluetooth libbluetooth-dev obexftp
obexftp --nopath --noconn --uuid none --bluetooth [MACアドレス] --channel 4 -p image.png

チャンネル4がHP Sprocket固有のようで、CUPSプリンターとしては認識されないためobexftpで直接叩く形になる。

方法3: Androidエミュレーター経由(動かない)

BlueStacksなどでHP Sprocketアプリを動かすという情報がネットに多いが、主要なAndroidエミュレーターはBluetoothパススルーに対応していないので動作しない。

方針

方法1のOBEX送信をまず試す。
ダメなら方法2のobexftpをMac/Linuxから試す。
前回のサーマルプリンターはBLEのリバースエンジニアリングで制御できたが、Sprocketについては専用プロトコルのリバエン情報がほぼ見つからなかったので、OBEXで通らなければ厳しいかもしれない。

BLE接続を試す

OBEXの前に、まずBLEで直接つないでみることにした。
紙は入れずに接続だけ試す。

なおこの個体、バッテリーが死んでいるらしくmicroUSB給電なしでは起動しない。
ジャンク品なので仕方ない。USB挿しっぱなしで進める。

デバイス検出

bleakでBLEスキャンすると HP Sprocket 200 (8C:2D) として検出された。
Advertisementデータに独自サービスUUID 6822d239-7b61-4718-bdc1-189221946209 が含まれており、ペイロードは 62 49 06 00 06 00 の6バイト。

サービス構造

接続してGATTサービスを列挙した結果、カスタムサービスが1つだけ見えた。

UUID(末尾)プロパティ用途(推測)
...051ewrite, write-without-responseデータ送信(画像・コマンド)
...3658read, notify, indicateレスポンス受信
...2eeereadステータス?

前回のサーマルプリンター(Mini Printer Sugar)はISSSC Transparent UARTサービスでESC/POSコマンドを流せたが、Sprocket 200は完全に独自プロトコル。
サービスUUIDもキャラクタリスティクスUUIDも既知のBLEプロファイルに一致しない。

読み取り結果

Readableなキャラクタリスティクス2つを読んだが、どちらも空データが返ってきた。
Notifyを5秒間購読しても何も飛んでこない。

Char ...3658 (read/notify): 空
Char ...2eee (read):        読み取りエラー
Notifications:              5秒間なし

おそらく初期化コマンド(ハンドシェイク)をWrite側に送らないとプリンターが応答を返さないプロトコル設計になっている。

コマンドプローブ

紙を消費せずにプロトコルを探るため、Writeキャラクタリスティクスに色々なバイト列を送ってNotifyの反応を見た。

結果:

  • 0x000x7F(MSBが0): 何も返さない。コマンドとして認識されていない
  • 0x800xFF(MSBが1): 全部同じ 81 01 00 02 を返す
  • 2バイト以上の構造化コマンド(ヘッダー+長さ+ペイロード形式)も全部 81 01 00 02
  • ESC/POSシーケンス、HP のASCIIマジックバイトなども同様
送信: 0xFF       → 応答: 81 01 00 02
送信: 0x80 0x01  → 応答: 81 01 00 02
送信: 0x80 0x0A 0x00 0x00 (FW ver?) → 応答: 81 01 00 02
送信: 0x1B 0x40 (ESC @)             → 応答: なし

81 01 00 02 はおそらく汎用エラーレスポンス。
コマンドの内容に関わらず同じ応答ということは、デバイスが認証かセッション確立を要求していて、それが通るまで個別のコマンドを処理しない設計と考えられる。

BLE単体でのブラインドプローブではこれ以上の情報が得られない。

Bluetooth Classic(RFCOMM)接続

GitHubで見つけた tylerhahn/ivy2-sprocket2(Canon IVY 2ドライバーのHP Sprocket 2フォーク)がRFCOMM接続を使っていたので、BT Classicでも試した。

SDP(サービス検出)

blueutil でペアリング後、IOBluetooth経由でSDPレコードを取得。

サービス名RFCOMMチャンネルUUID
(カスタム)100000000-DECA-FADE-DECA-DEAFDECACAFF
SPP SERVER20x1101(SerialPort)

RFCOMM接続結果

Channel 1(カスタムUUID): 接続成功。MTU 1006。
接続した瞬間から約1秒間隔で FF 55 02 00 EE 10 の6バイトが送られてくる。

<< FF 55 02 00 EE 10  (約1秒おき、永続的に送信)

これはステータスビーコン。何を送っても変化しない。
ivy2プロトコルのスタートコード 0x430F を含むコマンドを送っても無反応で、ビーコンの内容は変わらなかった。

FF 55 がメッセージヘッダーで、残りの 02 00 EE 10 はステータス情報と思われる。
紙なしの状態なので、EE10 がエラーコード(紙なし等)を示している可能性がある。

Channel 2(SPP SERVER): 接続成功。MTU 1006。
何を送ってもデータ受信なし。

ivy2プロトコルとの互換性

ivy2-sprocket2のREADMEには「May need protocol adjustments」とあり、実機テストされた形跡がない。
実際にSprocket 200は 0x430F スタートコードに反応せず、独自の FF 55 プロトコルで通信している。
Canon IVY 2とはプロトコルレベルで互換性がないと考えられる。

プロトコル解析

BLEパケットキャプチャと既存のリバースエンジニアリング情報から、HP Sprocket 200のBLE通信プロトコルを解析した。

BLEフレーミング層

GATTの上にBLE独自のフレーミング層がある。
各BLEパケットの先頭1バイトがフレームヘッダーで、残りがペイロード。

byte[0] = [last_flag:1][sequence:7]
byte[1:] = HPLPPメッセージの断片
  • last_flag(bit 7): 1なら最終パケット
  • sequence(bit 0-6): 1から始まるシーケンス番号
  • MTU内に収まるメッセージは単一パケットで 0x81(seq=1, last=true)始まり

これで先ほどの 81 01 00 02 の謎が解ける。

81       → BLEフレーム: seq=1, last=true
01 00 02 → HPLPPメッセージ: コマンド=ERROR(0x01), ペイロード=00 02

デバイスはセッション確立前のコマンドにすべてERRORを返していた。

HPLPPプロトコル

HP Sprocket 200は内部コードネーム「IBIZA」で、HPLPPプロトコルを使用する。
旧機種(初代Sprocket等)が使うMantaパケット(スタートコード 0x1B2A)やivy2プロトコル(0x430F)とは別物。

HPLPPメッセージの先頭1バイトがコマンドコード。主要なコマンドは以下の通り。

コマンドコード方向用途
IF_CONFIG_REQ0x0ABLEインターフェース設定要求
IF_CONFIG_RSP0x0BMTU・ACK周期を返す
RD_STATUS_REQ0x08ステータス読み取り
RD_STATUS_RSP0x09バッテリー・印刷状態等
RD_SYS_ATT_REQ0x04デバイス属性読み取り
RD_SYS_ATT_RSP0x05FW版・シリアル等
PRINT_START_REQ0x0C印刷開始(ファイル長を送信)
PRINT_START_RSP0x0DジョブID・ファイルハンドルを返す
FILE_WRITE_REQ0x0E画像データ送信(チャンク)
FILE_WRITE_RSP0x0F書き込み確認
ERROR0x01エラーレスポンス

セッション確立

BLE接続後、最初に IF_CONFIG_REQ を送る必要がある。
これがないとデバイスは全コマンドに ERROR を返す。

送信: 81 0A 01 00
      ^^          BLEフレーム (seq=1, last)
         ^^       IF_CONFIG_REQ (0x0A)
            ^^    BLEインターフェース (0x01)
               ^^ ACK周期 = 0

受信: 81 0B 01 02 02 00
      ^^                BLEフレーム
         ^^             IF_CONFIG_RSP (0x0B)
            ^^ ^^       MTU = 258
                  ^^    上り ACK周期 = 2
                     ^^ (パディング)

デバイス情報の読み取り

セッション確立後、ステータスとデバイス属性を読み取れる。

送信: 81 08 01 02 03 06 09   (RD_STATUS_REQ + フィールドID)
フィールド
SYSTEM_FLAGS0x05000000
PRINT_STATUS16(紙なし)
BATTERY_LEVEL100(USB給電)
BATTERY_STATUS1(充電中)
NUM_HOSTS0
送信: 81 04 01 02 03 05 09 0A 0B 0C   (RD_SYS_ATT_REQ + フィールドID)
フィールド
SW_VERSIONM1L1FA1839AR
PROTOCOL_VERSION256
NAMEHP Sprocket 200 (8C:2D)
SERIALTH8B93225J
SUPER_MODEL0x6249
SUB_MODEL0x0600
BT_MACF4:39:09:D2:8C:2D
HW_VERSION4138

PRINT_STATUS = 16 は紙なしのエラーコード。
紙を入れればこの値が変わり、印刷可能状態になるはず。

印刷フロー

通信キャプチャから推測される印刷シーケンスは以下の通り。

graph TD
    A[BLE接続] --> B[IF_CONFIG_REQ]
    B --> C[IF_CONFIG_RSP<br/>MTU=258]
    C --> D[RD_STATUS_REQ]
    D --> E{PRINT_STATUS<br/>確認}
    E -->|紙なし| F[エラー: 印刷不可]
    E -->|OK| G[PRINT_START_REQ<br/>ファイル長を送信]
    G --> H[PRINT_START_RSP<br/>ジョブID + ファイルハンドル]
    H --> I[FILE_WRITE_REQ<br/>JPEGチャンク送信]
    I --> J[FILE_WRITE_RSP]
    J -->|残りあり| I
    J -->|完了| K[印刷実行]

画像はJPEG形式、解像度668x1002ピクセル、quality=90で圧縮してチャンク分割で送る。

紙なしで確認できたこと

項目結果
BLE接続OK
セッション確立(IF_CONFIG)OK
デバイス情報読み取りOK(FW、シリアル、MAC等)
ステータス読み取りOK(PRINT_STATUS=16 = 紙なし)
プロトコル特定HPLPP(HP独自、コードネーム IBIZA)
印刷コマンド特定済み(PRINT_START → FILE_WRITE)

紙を入れてPRINT_STATUSが変わることを確認できれば、あとはJPEGを送るだけ。

印刷テスト

テスト用の画像を用意した。

テスト印刷用イラスト

Sprocketの印刷仕様に合わせてJPEG 668x1002px、quality=90に変換済み。

紙をセットしてステータス確認

Canon iNSPiC用のZINKペーパーをHP純正のスマートシートと一緒にセットした。
PRINT_STATUSが16(紙なし)から1(印刷可能)に変化。

PRINT_STATUS: 1(印刷可能)
BATTERY_LEVEL: 94
BATTERY_STATUS: 1(充電中)

バイトオーダーの罠

最初の送信は失敗した。
HPLPPプロトコルの整数値はすべてリトルエンディアンで、ファイル長をビッグエンディアンで送っていたのが原因。

# NG: ビッグエンディアン
struct.pack('>I', 146002)  # → 00 02 3A 52
# プリンターはLE読みで 0x523A0200 = 1.3GB と誤解

# OK: リトルエンディアン
struct.pack('<I', 146002)  # → 52 3A 02 00
# プリンターは正しく 146002 バイトと認識

ファイル長が間違っていたため、プリンターは全データ受信後もSTATUS_COMPLETEを返さず、永遠に続きのデータを待ち続けていた。

転送成功

バイトオーダーを修正した結果、FILE_WRITE_RSPで正しく進捗が返るようになった。

FILE_WRITE 146002 bytes (chunk=500B)
  15000/146002 (10%) recv=15000 total=146002
  30000/146002 (20%) recv=30000 total=146002
  ...
  135000/146002 (92%) recv=135000 total=146002
  COMPLETE! 146002/146002

Transfer: 293 chunks

STATUS_COMPLETE(ステータスコード2)が返り、プリンターが全データの受信完了を認識した。

印刷結果

Sprocketから印刷中

転送完了後、プリンターが自動で印刷を開始した。
紙が排出口からゆっくり出てくる。

印刷結果

PCからBLE経由でHP Sprocket 200への直接印刷に成功した。
Canon iNSPiC用の紙でも問題なく印刷できたが、色味が若干くすんでいる。
これは他社製ZINKペーパーを使った場合のあるあるらしく、スマートシートによるキャリブレーションが純正紙に最適化されているため、互換品だと発色がずれるとのこと。
印刷自体は問題ないので実用上は許容範囲だが、色にこだわるならHP純正紙を使ったほうがいい。

左: HP純正紙、右: Canon iNSPiC用紙

同じ画像を同じプリンターで印刷した比較。左がHP純正紙、右がCanon iNSPiC用紙。
並べてみるとなんとなく色味が違う。純正のほうが若干落ち着いた発色に見える。
劇的な差ではないが、スマートシートのキャリブレーションが純正紙向けに調整されている以上、互換品で多少ずれるのは仕方ない。

転送速度について

143KBのJPEGを500Bチャンク×293回で送信しているので、転送速度はかなり遅い。
チャンクごとにFILE_WRITE_RSPの応答待ちが入るため、体感で1分弱かかる。
チャンクサイズを大きくすれば改善するはずだが、BLEのMTU上限やACK周期との兼ね合いがあるのでチューニングが必要。
とはいえ2x3インチのZINKペーパーに印刷するような用途でスピードが問題になることはあまりない。


ちなみにこの個体、「バッテリーが死んでいるのでUSB給電必須」と思い込んで実験していたが、何枚か印刷したらバッテリー単独で動くようになった。
充電しながら使い続けたおかげでバッテリーが復活したらしい。ジャンク品の当たり個体だった。