12.5. Fiabilidad: secuencias, ACK y retransmisiones¶
La capa de tramado detecta la corrupción mediante sus CRC. La capa de fiabilidad convierte la «corrupción detectada» en «la aplicación nunca ve datos dañados» negociando retransmisiones cada vez que un paquete no llega intacto.
12.5.1. Números de secuencia¶
Cada cabecera de paquete lleva un número de secuencia de un byte, independiente para cada dirección de transmisión. El emisor incrementa el contador antes de transmitir; el receptor comprueba que la secuencia de cada paquete recibido sea la anterior más uno (módulo 256).
En el receptor pueden aparecer tres cosas en lugar de un paquete limpio y en orden:
El número de secuencia esperado, con un CRC válido. El paquete se entrega a la siguiente capa.
El número de secuencia esperado, con un CRC incorrecto. El receptor descarta el paquete y (si se han negociado los ACK) envía un NAK solicitando una retransmisión.
Un número de secuencia una unidad mayor que el esperado, con un CRC válido. El receptor sabe que el paquete anterior se perdió; envía un NAK que hace referencia a la secuencia perdida y guarda el nuevo paquete.
El caso de los duplicados (una retransmisión que llega después de que el original finalmente lograra pasar) se gestiona comparando con el contador esperado: si la secuencia está por detrás de la esperada, el paquete es un duplicado y el receptor lo descarta tras enviar el ACK que el emisor claramente no recibió la primera vez.
12.5.2. ACK y NAK¶
Dos bits de bandera en la cabecera del paquete transportan el propio tráfico de fiabilidad:
ACK_REQactivado en un paquete saliente significa «quiero un acuse de recibo de vuelta». Los paquetes de datos normalmente lo activan; los pings de estado y los eventos puntuales puede que no.ACKactivado en un paquete significa «este paquete es el acuse de recibo del número de secuencia indicado en la cabecera». No lleva carga útil propia.NAKactivado significa «este paquete rechaza uno anterior», normalmente por un CRC incorrecto o por un hueco en los números de secuencia. La cabecera indica al emisor qué secuencia debe retransmitir.
El emisor ejecuta un bucle de parada y espera (stop-and-wait): transmite un paquete que requiere acuse de recibo y luego espera el ACK (o NAK) correspondiente antes de enviar el siguiente. El modelo de un único paquete en tránsito mantiene acotado el estado del emisor (unos pocos cientos de bytes en las cámaras más pequeñas) y se ajusta al papel del protocolo como canal de control entre dos extremos, en lugar de una tubería optimizada para el rendimiento. Ante un NAK, el emisor retransmite el mismo paquete con la bandera RTX activada para que el receptor sepa que es un reintento.
12.5.3. Temporización de la retransmisión¶
Si no llega ni el ACK ni el NAK dentro del tiempo de espera de retransmisión, el emisor retransmite por su cuenta el paquete en tránsito. El tiempo de espera es de 500 ms por defecto y se duplica en cada reintento consecutivo (1 s, 2 s, …). Tras el número configurado de reintentos (tres por defecto), el emisor se rinde e informa de un error de transporte a la aplicación.
Duplicar el tiempo de espera es el patrón estándar de retroceso exponencial (exponential backoff). Un primer tiempo de espera corto detecta rápidamente los paquetes perdidos; la duplicación hace que un host ocupado durante unos cientos de milisegundos no provoque una avalancha de duplicados que agraven la carga.
12.5.4. Configurar la fiabilidad¶
Ambos extremos pueden desactivar de común acuerdo partes de la capa de fiabilidad, cuando la aplicación puede permitirse perder datos:
protocol.init(ack=False)desactiva los ACK por paquete. El emisor dispara y olvida; el receptor entrega lo que llegue. Resulta adecuado para transmitir datos de sensores en los que una muestra obsoleta es aceptable.protocol.init(seq=False)desactiva el seguimiento de los números de secuencia, lo que implica desactivar también los ACK. Solo es útil en transportes perfectamente fiables.protocol.init(crc=False)desactiva la validación CRC pero deja intacto el resto del tramado. Solo merece la pena cuando el propio transporte es lo bastante robusto como para que no se produzcan errores de CRC.
Los valores por defecto (todo activado) son el punto de partida adecuado para cualquier sesión de depuración entre host y cámara. Una vez que la aplicación está en producción, los compromisos se vuelven específicos de sus datos y de su transporte.
12.5.5. Los códigos de estado¶
Cuando un error de transporte sí se propaga hasta el código de la aplicación, llega como un código de estado. La biblioteca del protocolo define diez:
SUCCESS: la operación se completó.FAILED: el comando falló por un motivo no especificado.INVALID: el receptor rechazó el comando o uno de sus argumentos.TIMEOUT: se agotó un temporizador de reintento.BUSY: la cámara está ocupada (normalmente un canal bloqueado).CHECKSUM: el CRC de la cabecera o de la carga útil no coincidió.SEQUENCE: el número de secuencia estaba fuera de orden más allá de lo que la capa puede recuperar.OVERFLOW: una carga útil superó el máximo negociado.FRAGMENT: llegó un mensaje de varios fragmentos con piezas faltantes.UNKNOWN: un comodín defensivo para condiciones realmente inesperadas.
El código del host que llama a channel_read() los ve como excepciones de Python; el código de aplicación del lado de la cámara que ha optado por un manejo de errores personalizado los ve como valores de retorno de las funciones de retorno (callbacks) del backend. La mayoría de las aplicaciones de cámara no necesitan mirar los códigos de estado en absoluto: la biblioteca gestiona el reintento, y solo los fallos realmente irrecuperables (por ejemplo, que el propio transporte haya desaparecido) llegan a la aplicación.
Con el tramado en su sitio para detectar la corrupción y la fiabilidad en su sitio para recuperarse de ella, el trabajo a nivel de cable está hecho. El código de aplicación ve paquetes tramados, ordenados e intactos; los bytes de su interior son libres de significar lo que el canal superior quiera que signifiquen.