前の記事で、Claude Code の利用枠を 3.5 インチの USB ディスプレイに出すツールを作りました。机に置くと画面が思っていたより小さかったので、TURZX の 5.2 インチ(1280×720)を注文し、2026-10-15 の朝に届きました。Mac で動いたので、利用枠の表示はこちらに移しました。

右に横置きの 5.2 インチ。Claude Code の 5 時間・週次・Fable 週次の利用枠が円形のメーターで並ぶ。左に黒く塗装した縦置きの 3.5 インチ。rikkama.jp の CPU・メモリ・ディスク・スワップの使用率が出ている

右が 5.2 インチの利用枠(実際の値)、左が 3.5 インチです。3.5 インチは分解して外装を黒く塗り、サーバー監視の卓上モニターにしました。コードは前の記事と同じ studioc-co-jp/claude-usage-display(MIT)です。

5 インチをやめて 5.2 インチにした

最初に考えたのは 5 インチ(800×480)でした。候補の商品は、商品画像の SD カードの差し込み口などから、turing-smart-screen-python が「rev C」と呼ぶ世代でした(同リポジトリの wiki「Hardware revisions」)。rev C は、つないだ直後は休止した状態で、シリアルポートを開くと起き、別の USB の ID でつながり直します(lcd_comm_rev_c.py)。前の記事のとおり、macOS ではシリアル経由の送信で画面が崩れます。そのうえ、ライブラリの issue・GitHub のコード検索・ウェブ検索のどれでも、macOS で rev C を動かした報告は見つかりませんでした(2026-10-09)。

そこで、TURZX の 5.2 インチに替えました。ライブラリが 3.10.0(2026-04-12)で対応した、シリアルではなく USB で通信する新しい世代です。ベンダー独自の USB のインターフェースに、暗号化した 512 バイトの見出しと、PNG か JPEG の画像を 1 枚ずつ送ります。1 枚ごとに完結するので、rev A のように画素の数え違いで同期が崩れることはありません。

ただ、この世代でも、macOS で画像を表示した報告は見つかりませんでした。AliExpress で 5,541 円、到着から 90 日以内なら返品できる条件を確かめてから注文しました(2026-10-09)。

送信部は、届く前に仕様から書いた

届くまでの間に、送信部(claude_usage_display/turzx_usb.py)を書きました。仕様は次の 3 つから得ています。

  • turing-smart-screen-python の lcd_comm_turing_usb.py(GPL-3.0。前の記事と同じく、コードは持ち込まず仕様だけを読む)
  • 前の記事で着想をいただいた Token Dashboard(MIT)の internal/turzx/。同じ方式の 9.2 インチを Windows で動かしている実装
  • phstudy/turing-smart-screen-cli(MIT)。テストの答え合わせの値に使った

1 回の書き込みは「512 バイトの見出し+中身」です。

位置中身
0〜503DES-CBC で暗号化した 504 バイト(鍵と IV は仕様で決まった固定の値)。平文は、0 バイト目が命令番号、2〜3 が 1A 6D、4〜7 がその日の 0 時からのミリ秒、8 バイト目からが命令ごとの値
504〜5090
510〜511A1 1A

命令は、同期 10・明るさ 14・JPEG 101・PNG 102 を使います。画像の命令では、中身の長さを 4 バイトで値に入れ、見出しの後に PNG や JPEG をそのまま続けます。送るたびに応答を 1 つ読み、先頭が「命令番号・C8・見出しに入れた時刻」になっているかを確かめます。パネルの本来の向きは縦長(720×1280)なので、横向きの画像は 90 度回して送ります。

画面は、480×320 の配置を高さの比(720 ÷ 320)の 2.25 倍にして作り、横幅で余る 200 ピクセルはカード 3 枚の幅に回しました。1280×720 の 5.2 インチは約 282 ppi で、3.5 インチ(約 165 ppi)より細かいため、2.25 倍の文字は物理的には約 1.3 倍の大きさになります。表示部の高さも 49 mm から 62 mm へ約 1.27 倍になるので、縦の配分は 3.5 インチと同じです。後から、カードの余白が大きかったので、カードの中身をさらに 1.2 倍にしました(1.3 倍では、カードの上下に 3 ピクセルずつしか残りませんでした)。

つなぐと、シリアルポートは作られなかった

付属のケーブルを USB ハブの USB-A 端子につなぐと、電源が入り、紫と青の背景の画像に「TURZX V2」と出ました。Mac からは、つないだだけで USB の機器として見えました。

項目5.2 インチ3.5 インチ(前の記事)
USB の ID1cbe:00501a86:5722
速さHigh Speed(480 Mb/s)Full Speed(12 Mb/s)
macOS のシリアルポート作られない作られる(/dev/cu.usbmodem…)
画像の送り先ベンダー独自のインターフェース 0 のバルク OUT 0x01(応答は IN 0x81)インターフェース 1 のバルク OUT 0x03

macOS はこのインターフェースにドライバーを当てないので、libusb(pyusb)でそのまま確保できました。同期の命令を送ると、応答は書いたときに想定した形のとおりで、turzx_0001_0015 という文字列も入っていました。

応答は正常なのに、画面が崩れた

確認画面(四隅を赤・緑・青・白に塗り、赤に「左上」、青に「青」、中央に「1280×720」)を PNG で送ると、応答は正常なのに、画面はこうなりました。

5.2 インチに既定の紫と青の背景が透けて見え、右上に「TURZX V2」。「左上」「青」「1280×720」の文字と白い面だけが、縦に 4 回繰り返して描かれている

既定の背景が透けたうえで、白い文字(「左上」「青」と「1280×720」の縁取り)と白の面だけが、縦に 4 回繰り返して描かれています。赤・緑・青の面は出ていません。

ライブラリの lcd_comm_turing_usb.py を読み直すと、画面の状態を RGBA の画像として持ち、それを PNG にして送っていました。消去の命令で送る固定の PNG も、透明度つき(PNG の色の型 6)です。このツールは、透明度の無い RGB の PNG(色の型 2)を送っていました。同じ確認画面を、全面を不透明にした RGBA の PNG にして送ると、正しく映りました。

なぜ 4 回繰り返すのかは、パネルの中の処理なので外からは分かりません。確かめられたのは、RGB の PNG では崩れ、RGBA の PNG では崩れないことです。

応答の形はテストで固めていましたが、応答が示すのは「受け取った」ことだけでした。正しく映ったかは、画面を見るまで分かりません。

画面は 2 つの層でできていた

PNG と JPEG の違いも確かめました。このツールは PNG で送り、1 回に送れる中身の上限(1 MiB)を超えたときだけ JPEG にします。次の順に送りました。

  1. RGBA の PNG(確認画面)→ 正しく映る
  2. JPEG(右下に「JPEG」と書いた確認画面)→ 応答は正常だが、画面は変わらない
  3. 全面が透明の RGBA の PNG → 2 の JPEG の画面が現れる

パネルの画面は 2 つの層でできていました。JPEG は下の層(電源を入れたときの既定の背景がある層)に描かれ、PNG は上の層に透明度つきで重なります。1 の不透明な PNG が、2 の JPEG を隠していました。RGB の PNG で既定の背景が見えたのも、上の層の向こうに下の層があるためです。

Token Dashboard は、9.2 インチに JPEG だけを送っています(internal/turzx/protocol.go)。PNG を送らないので、この重なりには当たりません。PNG と JPEG を使い分けるこのツールでは、JPEG にしたときに、続けて透明の PNG を送って上の層を消すようにしました(claude_usage_display/turzx_usb.py から抜粋。大きさの検査と縦置きの分岐は省いています)。

def encode_image(image):
    """PNG(RGBA)で送り、1 MiB を超えるときだけ JPEG にする。命令番号と中身を返す。"""
    rgb = image.convert("RGB")
    buffer = io.BytesIO()
    rgb.convert("RGBA").save(buffer, format="PNG")  # 全面が不透明の RGBA
    if buffer.tell() <= MAX_PAYLOAD:
        return CMD_PNG, buffer.getvalue()
    ...  # 品質を下げながら JPEG にする


class TurzxUsb:
    def show(self, image):
        command, data = encode_image(image.transpose(self.rotation))
        self._exchange(command, struct.pack(">I", len(data)), data)
        if command == CMD_JPEG:  # JPEG は下の層に描かれ、前の PNG に隠れる
            clear = transparent_png(self.native_size)
            self._exchange(CMD_PNG, struct.pack(">I", len(clear)), clear)

利用枠の画面は 142,450 バイトの PNG で 1 MiB に収まるので、いまは JPEG の経路を通りません。

1 画面 0.1 秒。明るさは同じ割合でも違って見えた

利用枠の画面(PNG、142,450 バイト)を送る時間は、PNG にする 20 ミリ秒を含めて 102〜114 ミリ秒でした(5 回)。3.5 インチは、RGB565 の画素 307,200 バイトを Full Speed で送り、約 1.87 秒かかっていました。圧縮した画像を High Speed で送るので、1/17 ほどになります。

明るさは、命令 14 に 0〜102 の値で送ります(割合 × 102 ÷ 100)。3.5 インチは 0 が最も明るく 255 が最も暗い逆向きの値で、範囲も向きも違います。3.5 インチと同じ 25% を送ると、5.2 インチのほうが少し暗く見えました。上部に「明るさ ○%」と書いた見本の画面で 30・35・40・50% を 6 秒ずつ切り替えて見比べ、いったん 35% にしました(冒頭の写真はこのときのものです)。その後、もう 5% 明るくして、いまは 40%(パネルの値 40)です。同じ割合を送っても、パネルが違えば同じ明るさにはなりません。

向きは、横置きでケーブルを左に出す置き方なら、上下を反転する指定(--flip)なしで正しく映りました。縦置きは試していません。

青がくすんで見えたのは、パネルの差だった

2 台を並べると、「青いリングが 3.5 インチよりくすんで見える」という指摘が出ました。送った値とコードで確かめました。

  • **使った分の弧は、同じ色です。**5.2 インチへの PNG は (0, 145, 255)、3.5 インチは RGB565 の 0x049f((0, 146, 255) 相当)で、どちらも Apple の Human Interface Guidelines の Blue です。見え方の違いは、パネルの差です
  • **まだ使っていない分の輪(地)は、コードの上でも違う色でした。**利用枠の画面は、青をカードの灰色へ 75% 寄せた (25, 60, 88)、サーバー監視の画面は、青を黒へ 78% 寄せた (0, 32, 56) で、画面ごとに作り方が違っていました。そのときの利用枠は 2%・13%・0% で、リングのほとんどが地の色でした

地をサーバー監視と同じ作り方にした画面と今の画面を、5.2 インチに 5 秒ずつ交互に出して見比べ、今の色のままにしました。「色が違って見える」は、送った値・コード・パネルに分けて確かめると、どこで違うのかを決められます。

やめたもの、回したもの

**3.5 インチは、サーバー監視に回しました。**黒のつもりで間違えて白を注文したものなので、分解して外装を黒く塗装し、当社のサービスのサーバーの状態を常に出す卓上モニターにしています。2 台を同じ Mac につなぎ、利用枠は 5.2 インチ、サーバーの状態は 3.5 インチに出しています。詳しくはサーバー監視の記事に書きました。

**Antigravity の枠も載せましたが、やめました。**5.2 インチは画面に余裕があるので、手元で使っている Antigravity の利用枠も出せるようにしました(2026-10-11)。見本の 2 案から、上の段に Claude Code の 3 枚、下の段に Antigravity の 5 時間と週の枠を横長のカードで並べる案を選びました。値は、Antigravity の CLI の /usage の出力を読み、内部の API は自分では呼びません。古い版の CLI は /usage を指示文として扱い、枠を使ってしまうので、版が 1.1.11 より古ければ呼ばず、応答に会話の形跡(num_turns が 1 以上、または conversation_id)があれば取得をやめます。ADE の Orca と同じ止め方です。1 回に約 6 秒かかり、実行のたびに作業用のフォルダに小さなファイルが 1 つ増えるので、取得は 15 分ごとにしました。

やめたのは、出しても役に立たなかったからです。Antigravity は画像の生成にしか使っておらず、実際に当たる上限は、画像の生成の週の上限(これまでの記録で週 45〜60 回ほど)です。ところが /usage が返すのは、モデルのグループごとの 5 時間と週の枠だけで、画像の生成の枠はありません。画像の生成が週の上限に届きかけていた時刻にも、/usage の週の枠は 6% でした。2026-10-13 にやめると決め、5.2 インチが届いた日に実際の値でもう一度出してみましたが、やはり出さないことにしました。

**見出しのアイコンも、作りましたが使っていません。**見出しの左にサービスのアイコンを出せるようにしたものの、アイコン付きの画面を見て「情報量が多くて見づらくなる」としてやめました。ロゴは、商標の指針(例: Anthropic の Trademark Guidelines)が使用に事前の承認を求め、個人で使う場合の例外も書いていないので、リポジトリには入れず、手元のフォルダから読む作りにしていました。この記事の写真にも、ロゴは写していません。

まとめ

  • シリアルを通らない新しい世代の TURZX は、macOS でもドライバーに邪魔されず、libusb でそのまま確保できた。1 枚ごとに完結した画像を送るので、rev A のような同期の崩れも起きない
  • 応答が正常でも、正しく映ったとは限らない。5.2 インチは RGB の PNG を受け取って正常に応答し、崩れた画面を描いた。PNG は RGBA で送る
  • パネルの画面は 2 つの層。JPEG は下、PNG は上に透明度つきで重なるので、不透明な PNG の後に送った JPEG は見えない
  • 明るさの「25%」は、パネルごとに意味が違う。値をそろえるのではなく、並べて見比べて決める
  • 「色が違って見える」は、送った値・コード・パネルに分けて確かめる

通信の仕様を公開している turing-smart-screen-python、Token Dashboard、turing-smart-screen-cli の作者の方々に感謝します。