12.2. Die vier Ebenen¶
Die Protokollbibliothek ist als Stapel von vier Ebenen aufgebaut, von denen jede ein einziges Problem löst und auf der darunterliegenden Ebene aufbaut. Der Rest des Kapitels durchläuft den Stapel von unten nach oben.
12.2.1. Transport¶
Ganz unten befindet sich die Byte-Pipe zwischen der Cam und dem Host. Der Protokollbibliothek ist es egal, welche die Bytes transportiert:
USB-CDC über den USB-Port, an den die Cam angeschlossen ist. Die Standardoption mit der höchsten Bandbreite für jede Cam.
UART über ein Paar GPIO-Pins an der Cam, die mit einem seriellen Adapter am Host verbunden sind. Nützlich für Headless-Bereitstellungen, bei denen der USB-Port belegt oder physisch nicht zugänglich ist.
Die einzige Aufgabe des Transports ist „Bytes gehen rein, Bytes kommen raus, in der richtigen Reihenfolge“. Alles oberhalb dieser Ebene geht davon aus, dass der Transport Bytes in der Reihenfolge zustellt, in der sie geschrieben wurden, lässt aber zu, dass die Bytes selbst beschädigt werden oder die Verbindung ganz abbricht. Verlustreiche Bursts (einige fehlende Bytes) und saubere Abbrüche (die gesamte Verbindung ist für eine Weile weg und dann wieder da) werden beide weiter oben behandelt.
12.2.2. Framing¶
Die nächsthöhere Ebene erlegt dem Byte-Strom eine Struktur auf. Jede Nachricht wird zu einem Paket – ein 10-Byte-Header, gefolgt von einer Nutzlast, gefolgt von einem 4-Byte-Trailer. Der Header trägt:
Ein 2-Byte-Sync-Word (
0xD5AA), das es einem Empfänger ermöglicht, den Anfang eines Pakets nach einer Desynchronisation wiederzufinden.Eine 1-Byte-Sequenznummer, die von der Zuverlässigkeitsebene verwendet wird.
Eine 1-Byte-Channel-ID, die angibt, zu welchem logischen Stream das Paket gehört.
Ein 1-Byte-Flags-Feld für ACK- / NAK- / Fragment- / Event-Bits.
Ein 1-Byte-Opcode, der zwischen Protokollbefehlen, Systembefehlen und Channel-Befehlen unterscheidet.
Eine 2-Byte-Nutzlastlänge.
Ein 2-Byte-CRC über die vorhergehenden acht Header-Bytes.
Die Nutzlast folgt, dann ein 4-Byte-CRC über die Nutzlast selbst. Die beiden CRCs erkennen Beschädigungen unabhängig voneinander: Ein gekipptes Bit im Header macht den Header-CRC ungültig, und der Empfänger kann das Paket verwerfen, ohne jemals die Nutzlast lesen zu müssen.
12.2.3. Zuverlässigkeit¶
Die Zuverlässigkeitsebene verwandelt „Pakete, die vielleicht ankommen“ in „Pakete, die angekommen sind“. Sie verfolgt die Sequenznummern im Header, bittet die Gegenseite, Bestätigungen für jedes Paket zu senden, das eine erfordert, und überträgt erneut, wenn eine Bestätigung nicht innerhalb eines Timeouts eintrifft. Standardmäßig beginnt das Timeout für erneute Übertragungen bei 500 ms und verdoppelt sich bei jedem Versuch, mit drei Versuchen, bevor aufgegeben wird.
Jedes dieser Verhalten ist im Aufruf von protocol.init() konfigurierbar: ACK kann für Einweg-Streams abgeschaltet werden, die CRC-Validierung kann bei vollkommen sauberen Transporten übersprungen werden, und die Parameter für die erneute Übertragung können für langsame oder Verbindungen mit hoher Latenz angepasst werden.
12.2.4. Channels¶
Die oberste Ebene ist das, was der Anwendungscode sieht. Ein Channel ist ein benannter logischer Stream, der durch eine Channel-ID von 0 bis 31 identifiziert wird. Bis zu 32 Channels können auf einem Transport koexistieren; jeder ist unabhängig von den anderen und wird im Header jedes Pakets über seine ID adressiert. Die Cam bootet mit vier eingebauten Channels – stdin, stdout, stream und profile – und der Anwendungscode registriert darauf weitere, indem er protocol.register() mit einer Python-Klasse aufruft.
Die vier Ebenen vermischen keine Belange. Framing weiß nichts über Channels; Zuverlässigkeit weiß nichts über Paketinhalte; die Channel-Ebene weiß nicht, wie die Bytes ankommen. Diese Trennung ist der Grund, warum ein Transport-Wechsel (zum Beispiel USB zu UART) sich nicht bis in den Channel-Code fortpflanzt, und sie ist es, die den Rest des Kapitels Ebene für Ebene durchgehbar macht.