12.2. 4 つの層

プロトコルライブラリは 4 つの層からなるスタックとして構築されており、各層は 1 つの問題だけを解決し、その下の層の上に積み重なります。この章の残りでは、スタックを下から上へとたどっていきます。

ラベル付きの 4 つの層を縦に積み重ねた図: 一番下に トランスポート(USB または UART)、その上にフレーミング (CRC を伴うパケットヘッダー)、さらにその上に信頼性 (シーケンス番号、ACK / NAK、再送)、そして一番上に チャネル(名前付きの論理ストリーム)。

12.2.1. トランスポート

一番下にあるのは、cam とホストの間のバイトパイプです。プロトコルライブラリは、どちらがバイトを運ぶかを気にしません。

  • cam が接続されている USB ポート上の USB-CDC。すべての cam にとってデフォルトかつ最も帯域幅の高い選択肢です。

  • cam の 1 組の GPIO ピンを介した UART で、ホスト側のシリアルアダプタに接続します。USB ポートが使用中であったり物理的にアクセスできなかったりするヘッドレス構成で役立ちます。

トランスポートの唯一の仕事は「バイトが入り、バイトが出る、順序どおりに」です。この層より上のすべては、トランスポートが書き込まれた順序でバイトを配信することを前提としますが、バイト自体が破損したりリンク全体が完全に切断されたりすることは許容します。損失のあるバースト(数バイトの欠落)とクリーンな切断(リンク全体がしばらく失われ、その後復帰する)は、どちらも上位で処理されます。

12.2.2. フレーミング

次の層は、バイトストリームに構造を課します。すべてのメッセージはパケットになります。10 バイトのヘッダーに続いてペイロード、その後に 4 バイトのトレーラーが続きます。ヘッダーには次のものが含まれます。

  • 2 バイトの同期ワード(0xD5AA)。受信側が同期ずれの後にパケットの先頭を再発見できるようにします。

  • 信頼性層が使用する 1 バイトのシーケンス番号。

  • パケットがどの論理ストリームに属するかを示す 1 バイトのチャネル ID。

  • ACK / NAK / フラグメント / イベントのビットを表す 1 バイトのフラグフィールド。

  • プロトコルコマンド、システムコマンド、チャネルコマンドを区別する 1 バイトのオペコード。

  • 2 バイトのペイロード長。

  • 先行する 8 バイトのヘッダーに対する 2 バイトの CRC。

その後にペイロードが続き、ペイロード自体に対する 4 バイトの CRC が続きます。2 つの CRC は破損を独立して検出します。ヘッダー内のビット反転はヘッダー CRC を無効にし、受信側はペイロードを読み取る必要なしにパケットを破棄できます。

12.2.3. 信頼性

信頼性層は、「届くかもしれないパケット」を「届いたパケット」に変えます。ヘッダー内のシーケンス番号を追跡し、確認応答を必要とするすべてのパケットについて相手側に確認応答の送信を求め、確認応答がタイムアウト内に届かない場合は再送します。デフォルトでは、再送タイムアウトは 500 ms から始まり、再試行のたびに倍になり、あきらめるまでに 3 回再試行します。

これらの動作はそれぞれ protocol.init() の呼び出しで設定可能です。一方向ストリームでは ACK をオフにでき、完全にクリーンなトランスポートでは CRC 検証をスキップでき、再送パラメータは低速または高レイテンシのリンク向けに調整できます。

12.2.4. チャネル

一番上の層は、アプリケーションコードが目にするものです。チャネル は、0 から 31 までのチャネル ID で識別される名前付きの論理ストリームです。1 つのトランスポート上に最大 32 個のチャネルが共存でき、それぞれが他から独立しており、すべてのパケットのヘッダー内の ID で指定されます。cam は 4 つの組み込みチャネル(stdinstdoutstreamprofile)を持って起動し、アプリケーションコードは Python クラスを指定して protocol.register() を呼び出すことで、その上にさらに登録します。

4 つの層は関心を混在させません。フレーミングはチャネルについて知りません。信頼性はパケットの中身について知りません。チャネル層はバイトがどのように届くかを知りません。この分離こそが、トランスポートの差し替え(たとえば USB から UART へ)がチャネルコードにまで波及しない理由であり、この章の残りを 1 度に 1 層ずつたどれるようにしているものです。