12.2. I quattro livelli#
La libreria del protocollo è costruita come uno stack di quattro livelli, ciascuno dei quali risolve un singolo problema e si basa sul livello sottostante. Il resto del capitolo percorre lo stack dal basso verso l’alto.
12.2.1. Trasporto#
In fondo c’è il tubo di byte tra la cam e l’host. La libreria del protocollo non si preoccupa di quale dei due trasporti i byte:
USB-CDC sulla porta USB a cui è collegata la cam. L’opzione predefinita e con la banda più ampia per ogni cam.
UART su una coppia di pin GPIO sulla cam collegati a un adattatore seriale sull’host. Utile per deployment headless dove la porta USB è occupata o non è fisicamente accessibile.
L’unico compito del trasporto è «i byte entrano, i byte escono, in ordine». Tutto ciò che sta sopra questo livello presuppone che il trasporto consegni i byte nell’ordine in cui sono stati scritti, ma ammette che i byte stessi possano essere corrotti o che il collegamento cada del tutto. Le raffiche con perdite (alcuni byte mancanti) e le interruzioni nette (l’intero collegamento perso per un po”, poi ripristinato) sono entrambe gestite ai livelli superiori.
12.2.2. Framing#
Il livello successivo impone una struttura al flusso di byte. Ogni messaggio diventa un pacchetto: un header di 10 byte seguito da un payload seguito da un trailer di 4 byte. L’header contiene:
Una parola di sincronizzazione di 2 byte (
0xD5AA) che consente a un ricevitore di ritrovare l’inizio di un pacchetto dopo una desincronizzazione.Un numero di sequenza di 1 byte usato dal livello di affidabilità.
Un ID di canale di 1 byte che indica a quale flusso logico appartiene il pacchetto.
Un campo flag di 1 byte per i bit ACK / NAK / fragment / event.
Un opcode di 1 byte che distingue i comandi del protocollo, i comandi di sistema e i comandi dei canali.
Una lunghezza del payload di 2 byte.
Un CRC di 2 byte sui precedenti otto byte dell’header.
Segue il payload, poi un CRC di 4 byte sul payload stesso. I due CRC rilevano la corruzione in modo indipendente: un bit invertito nell’header invalida il CRC dell’header, e il ricevitore può scartare il pacchetto senza dover mai leggere il payload.
12.2.3. Affidabilità#
Il livello di affidabilità trasforma i «pacchetti che potrebbero arrivare» in «pacchetti che sono arrivati.» Tiene traccia dei numeri di sequenza nell’header, chiede all’altra parte di inviare conferme per ogni pacchetto che ne richiede una e ritrasmette quando una conferma non arriva entro un timeout. Per impostazione predefinita il timeout di ritrasmissione parte da 500 ms e raddoppia a ogni nuovo tentativo, con tre tentativi prima di arrendersi.
Ciascuno di questi comportamenti è configurabile nella chiamata protocol.init(): l’ACK può essere disattivato per i flussi unidirezionali, la validazione CRC può essere saltata su trasporti perfettamente puliti e i parametri di ritrasmissione possono essere regolati per collegamenti lenti o ad alta latenza.
12.2.4. Canali#
Il livello superiore è ciò che vede il codice applicativo. Un canale è un flusso logico con nome identificato da un ID di canale da 0 a 31. Fino a 32 canali possono coesistere su un trasporto; ciascuno è indipendente dagli altri, indirizzato dal suo ID nell’header di ogni pacchetto. La cam si avvia con quattro canali integrati – stdin, stdout, stream e profile – e il codice applicativo ne registra altri sopra di essi chiamando protocol.register() con una classe Python.
I quattro livelli non mescolano le competenze. Il framing non sa nulla dei canali; l’affidabilità non sa nulla del contenuto dei pacchetti; il livello dei canali non sa come arrivano i byte. Questa separazione è il motivo per cui uno scambio di trasporto (da USB a UART, ad esempio) non si ripercuote verso l’alto nel codice dei canali, ed è ciò che rende il resto del capitolo percorribile un livello alla volta.