コンテンツにスキップ

アーキテクチャ

このドキュメントでは、sub-screen-player の構成、各部分の責務、新しいコードをどこに置くべきかを説明します。

ssp CLI ・ スクリプト / アプリ ・ (今後)Web 画面、プラグイン
│
HTTP / WebSocket (127.0.0.1:7920)
│
━━ ssp-server ━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
api/ ルーティング、認証、WebSocket ストリーム
manager ディスプレイの検出、抜き差し、各ディスプレイの表示内容 (Content)
sources/ フレームを描く組み込み画面(時計、静止画)
│
│ Frame(横長の RGB、パネルと同じサイズ)
│
━━ ssp-core ━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Presenter 最新フレーム優先のキュー、エンコード/デバイス用スレッド、
送信ペースの制御、同一フレームの省略、キープアライブ
Encoder パネルの向きへの回転、エンコード(JPEG)
Display すべてのドライバが実装するトレイト
Transport 通信路のトレイト(hid::HidTransport が実装)
│
│ EncodedImage
│
━━ ssp-driver-<model> ━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
protocol デバイス向けレポートを組み立てる純粋関数
Display 実装: 表示、保存、明るさ、電源、キープアライブ
Driver 対応する USB ID と、デバイスの開き方
│
│ レポート
│
USB (HID)

これらはすべて ssp という1つのバイナリに入っています。ssp serve がデーモンを動かし、それ以外のコマンドは すべて、そのデーモンに対する小さな HTTP クライアントです。

クレート パス 担当すること してはいけないこと
ssp-core crates/core Display/Driver/Transport トレイト、HID アクセス、フレームとエンコード、Presenter 特定の機種のプロトコルを知ること
ssp-driver-<model> crates/drivers/<model> 1つの機種ファミリー: そのプロトコル、機能、癖 ほかのドライバ、サーバ、非同期コードへの依存
ssp-server crates/server デーモン: デバイス管理、組み込みソース、設定、HTTP/WebSocket API 機種固有のバイト列を持つこと
sub-screen-player(バイナリ名 ssp) crates/cli コマンドライン、自動起動、ログ設定 デバイスと直接通信すること(デーモン未起動時の一覧表示を除く)

依存は下向きだけです: cli → server → drivers/* → core

  • Frame(core): 呼び出し側が描くもの。常に横長で、パネルとちょうど同じサイズの 8 ビット RGB です。 サイズの違う画像は Frame::fit(contain / cover / stretch)で合わせます。
  • PanelSpec(core): 呼び出し側から見たパネルのサイズ、送信時に必要な回転、画像形式。たとえば D92 は 1920x462、時計回りに 90° 回転、JPEG です。
  • EncodedImage(core): 回転とエンコードを済ませ、ドライバに渡せる状態のフレーム。
  • Display(core のトレイト、ドライバが実装): show(ライブ表示、保存しない)、save(デバイスに保存)、 set_brightness、wake、sleep、clear、keep_alive。機種にない操作は既定の実装のままにしておくと、 Error::Unsupported を返します。
  • Capabilities(core): ディスプレイが対応している操作、フレームレートの上限、キープアライブの間隔、 受け付ける画像の最大サイズ。ほかの部分は機種を判別する代わりにこれを参照します。
  • Driver(core のトレイト): ドライバが扱う USB インターフェース(UsbMatch)と open。 デーモンが使うドライバの一覧は crates/server/src/drivers.rs にあります。どのドライバを使うかは DriverSelection (--driver オプションや設定ファイルの [drivers])で決まり、Registry::select で反映します。experimental (試験中)のドライバは、名前を指定したときだけ使われます。
  • Content(server): ディスプレイに表示させる内容。なし、画像、時計、ストリームのいずれかです。 ディスプレイ ID ごとに保持されるので、挿し直したディスプレイは元の表示を続けます。
  • Source(server のトレイト): フレームを描き、次に絵が変わるタイミングを知っているもの(時計、静止画)。
  1. フレームが作られます。Source がディスプレイごとのプレイヤースレッドで描画するか、 WebSocket/HTTP のクライアントが送ってきたものをサーバがデコードします。
  2. Presenter::submit はフレームを1枠のスロットに入れ、すぐに戻ります。スロットに残っていた前のフレームは 捨てられます(dropped として数えます)。
  3. エンコードスレッドがフレームを取り出し、回転してエンコードします。結果がデバイスの受け付けるサイズを 超えた場合は、JPEG の品質を段階的に下げます。
  4. デバイススレッドは、フレーム間隔(1 / max_fps)が経過した時点で最新のエンコード済みフレームを送ります。 画面に出ているものと同じフレームは送りません(duplicates として数えます)。次のフレームのエンコードは 現在のフレームの送信と並行して進むため、フレームレートは両者の合計ではなく、遅い方で決まります。
  5. ドライバの Display::show が画像をレポートに変換し(protocol.rs)、Transport を通して書き込みます。

制御コマンド(明るさ、電源、消去、保存)も同じデバイススレッドを順番に通り、呼び出し側は結果を待ちます。 キープアライブも同じスレッドから keep_alive_interval ごとに送られます。

スレッド 数 役割
tokio ランタイム 数本 HTTP と WebSocket の処理。ブロックする処理は spawn_blocking に回します。
ssp-scan 1 2 秒ごとに USB を走査し、新しいディスプレイを開き、抜かれたものを検知します
ssp-encode-<id> ディスプレイごとに1 フレームの回転とエンコード
ssp-device-<id> ディスプレイごとに1 Display を所有し、フレーム・コマンド・キープアライブを送ります
ssp-player-<id> ディスプレイごとに0〜1 現在の組み込み Source を動かします(例: 時計を刻む)

Display を使うのは自分のデバイススレッドだけなので、ドライバ側でロックは不要です。

  • Error::Transport と Error::Disconnected は致命的です。Presenter は停止し、待ち行列のコマンドを失敗させます。 マネージャは次の走査でそのデバイスを外しますが Content は保持し、再び現れたら開き直します。
  • それ以外のエラー(InvalidArgument、Image、Unsupported)は呼び出し側に返され、ディスプレイは動き続けます。
  • デバイスを開けなかった場合(ほかのプログラムが使用中など)、マネージャは 10 秒ごとに再試行し、ログは最初の失敗だけ出します。
  • API は既定で 127.0.0.1 で待ち受けます。token を設定しない限り、ループバック以外の listen アドレスは 設定の検証で拒否されます。
  • トークンなしの場合、Host または Origin ヘッダーがループバックでないリクエストは拒否します。これにより、 ブラウザで開いた Web ページがディスプレイを操作すること(CSRF、DNS リバインディング)を防ぎます。
  • トークンありの場合、すべてのリクエストに Authorization: Bearer <token> または ?token=<token> が必要です。

現在あるものと、今後の機能がどこに入るか:

拡張 状況 入る場所
新しいディスプレイ機種 利用可能 crates/drivers/ 以下の新しいクレート(手順)
外部プログラムからの描画 利用可能 WebSocket ストリームまたは HTTP の画像エンドポイント(API)
組み込み画面 利用可能 crates/server/src/sources/ の Source と、Content のバリアント
HID 以外の通信方式(USB バルク、シリアル) 予定 crates/core に新しい Transport 実装
Web(HTML/CSS)画面 予定 ページを描画する Source、またはフレームを送る外部クライアント
WASM プラグイン 予定 サンドボックス化したプラグインを動かす Source
設定ファイルでのレイアウト合成 予定 ほかのソースを組み合わせる Source(config.rs で設定)

新しい種類のコンテンツが差し込まれる継ぎ目は、Source トレイトとストリーム API の2つです。どちらも小さく、 安定した形に保ってください。