機種の追加方法
ドライバは、sub-screen-player に1つの機種ファミリーの扱い方を教えるものです。それ以外(デバイスの検出、
フレームの送信ペース、エンコード、キープアライブ、API、CLI)はすべて共通なので、ドライバは多くの場合
数百行で済みます。お手本は crates/drivers/d92 です。
1. デバイスの通信方法を調べる
Section titled “1. デバイスの通信方法を調べる”次のことを調べ、docs/devices/<model>.md(と日本語版の docs/devices/<model>.ja.md)に書き残してください。
- USB ID(ベンダー:プロダクト)とインターフェースの種類。macOS は
ioreg -p IOUSB -l、Linux はlsusb -v、 Windows はデバイスマネージャー →「詳細」→「ハードウェア ID」で確認できます。 - パネルのサイズと向き: 呼び出し側が描くサイズ(横長)と、送る前に画像を回転する必要があるか。
- 画像形式: JPEG、生の RGB565 など。サイズの上限も。
- コマンド: フレームを表示する方法。あれば明るさ、電源、消去、キープアライブも。
- タイミング: どこまで速くフレームを送れるか、キープアライブが必要か。
ドキュメントがない場合は、メーカー製アプリの通信をキャプチャします。x86 の Windows なら Wireshark と USBPcap が
使えます。それができない環境(Apple Silicon など)では、VMware の仮想マシンでメーカー製アプリを動かし、
usb.analyzer.enable = "TRUE" を設定すると、USB の通信が vmware.log に記録されます。
リバースエンジニアリングの作法も読んでおいてください。
現在 ssp-core が対応しているのは HID デバイスです。USB バルク転送やシリアルポートを使うディスプレイの場合は、
先に Issue を立ててください。crates/core に新しい Transport が必要になり、それはほかのドライバとも共有されます。
2. クレートを作る
Section titled “2. クレートを作る”crates/drivers/<model>/├── Cargo.toml└── src/ ├── lib.rs Driver と Display の実装 └── protocol.rs レポートを組み立てる純粋関数Cargo.toml:
[package]name = "ssp-driver-<model>"description = "sub-screen-player driver for the <Vendor Model>"version.workspace = trueedition.workspace = truerust-version.workspace = truelicense.workspace = trueauthors.workspace = true
[dependencies]ssp-core.workspace = truetracing.workspace = true
[lints]workspace = true3. プロトコルを書く
Section titled “3. プロトコルを書く”protocol.rs には定数(USB ID、パネルサイズ、レポート長)と、コマンドや画像をバイト列に変換する関数を置きます。
I/O を含めないことで、網羅的にテストできるようにします。
テストはキャプチャしたバイト列と照合します。たとえば D92 ドライバは、14547 バイトの JPEG に対して、 メーカー製アプリが送るのとまったく同じヘッダーが付くことを確認しています。
#[test]fn live_frame_header_matches_capture() { let out = live_frame(&vec![0xAB; 14547]); assert_eq!(&out[..13], &[0x43, 0x52, 0x54, 0, 0, 0x44, 0x52, 0x41, 0, 0, 0x38, 0xf3, 0xb1]);}デバイスを壊れた状態にするコマンドが分かっている場合は、ドライバがそれを決して送らないことを確かめるテストも書いてください。
4. Display を実装する
Section titled “4. Display を実装する”pub struct MyDisplay { transport: Box<dyn Transport>, info: DisplayInfo,}
impl MyDisplay { /// HID のハンドルではなく `Transport` を受け取るので、テストでは `RecordingTransport` を渡せます。 pub fn open(transport: Box<dyn Transport>, serial: &str) -> Result<Self> { ... }}
impl Display for MyDisplay { fn info(&self) -> &DisplayInfo { &self.info } fn show(&mut self, image: &EncodedImage) -> Result<()> { ... } // デバイスが対応しているものだけを実装し、残りは既定の実装のままにします。}DisplayInfo は実態どおりに埋めてください。
driver: 短く、変わらない ID("d92"など)。ディスプレイ ID やログに使われます。panel: 横長のサイズ、送信時に必要なRotation、ImageFormat。capabilities:max_fpsは実測した値に、キープアライブが必要ならkeep_alive_intervalを、max_image_bytesはデバイスが受け付けるサイズにします。
デバイスがなくなったときは Error::Transport か Error::Disconnected を返してください。デーモンが自動的に開き直します。
ドライバの中でリトライしたり、ループでスリープしたりしないでください。
5. Driver を実装する
Section titled “5. Driver を実装する”pub struct MyDriver;
impl Driver for MyDriver { fn id(&self) -> &'static str { "mymodel" } fn name(&self) -> &'static str { "Vendor Model" } fn usb_matches(&self) -> &'static [UsbMatch] { &[UsbMatch { vendor_id: 0x1234, product_id: 0x5678, usage_page: Some(0xFF00) }] } fn open(&self, candidate: &Candidate) -> Result<Box<dyn Display>> { let transport = ssp_core::hid::open(candidate)?; Ok(Box::new(MyDisplay::open(Box::new(transport), &candidate.serial)?)) }}マッチ条件はできるだけ狭くしてください(デバイスに HID インターフェースが複数あるなら usage page も指定します)。 そうすれば、ドライバが誰かのキーボードを掴んでしまうことはありません。
開発中は、ドライバに「試験中(experimental)」の印を付けてください。名前を指定したとき(--driver mymodel、
または設定ファイルの [drivers] の enable)だけ使われるようになるので、作りかけのドライバがリリースに入ってしまっても、
勝手にディスプレイを取ることはありません。
fn experimental(&self) -> bool { true // 実機で確認できたら外す }6. 登録する
Section titled “6. 登録する”- ルートの
Cargo.toml:membersと[workspace.dependencies]にクレートを追加します。 crates/server/Cargo.toml:ssp-driver-<model>.workspace = trueを追加します。crates/server/src/drivers.rs:registry.register(ssp_driver_<model>::MyDriver);を追加します。contrib/linux/70-sub-screen-player.rules: ベンダー ID とプロダクト ID の行を追加します。README.mdとREADME.ja.md: 「対応ディスプレイ」の表に追加します。docs/devices/<model>.mdとdocs/devices/<model>.ja.md: 手順 1 で調べたプロトコルのメモを置きます。
7. 実機で試す
Section titled “7. 実機で試す”mise run ci が通ることが前提です。そのうえで実機で自動検査を実行し、出力をプルリクエストに貼ってください。
ssp selftest --driver mymodel # すべて PASS になるはずデーモンが動いていても、検査の妨げになるのはデーモンがそのドライバを使っている場合だけです。ほかのディスプレイを
表示したまま検査したいときは、そのディスプレイのドライバだけでデーモンを起動してください(例: ssp serve --driver d92)。
続けて手でも試し、画面を確認してください。
mise run serve # 時計が表示されるはずssp devices # ディスプレイが一覧に出るssp brightness 30 && ssp brightness 100ssp off && ssp onssp show some-photo.jpg --fit cover # 向きを確認ssp status # 表示数・間引き数と送信時間を確認フレームレートを測るには、WebSocket API(api.ja.md)でフレームを流しながら ssp status を見てください。
時計の表示中にディスプレイを抜き差しして、自動で復帰することも確認してください。