12.5. Fiabilidade – sequências, ACKs, retransmissões#

A camada de enquadramento deteta corrupção através dos seus CRCs. A camada de fiabilidade transforma «corrupção detetada» em «a aplicação nunca vê dados corrompidos», negociando retransmissões sempre que um pacote não chega intacto.

12.5.1. Números de sequência#

Cada cabeçalho de pacote contém um número de sequência de um byte, separado para cada direção de transmissão. O emissor incrementa o contador antes de transmitir; o recetor verifica que a sequência de cada pacote recebido é a anterior mais um (módulo 256).

Três situações podem surgir no recetor em vez de um pacote limpo e ordenado:

  • O número de sequência esperado, com um CRC válido. O pacote é entregue à camada seguinte.

  • O número de sequência esperado, com um CRC inválido. O recetor descarta o pacote e (se os ACKs estiverem negociados) envia um NAK a solicitar uma retransmissão.

  • Um número de sequência superior em um ao esperado, com um CRC válido. O recetor sabe que o pacote anterior se perdeu; envia um NAK referenciando a sequência em falta e guarda o novo.

O caso duplicado (uma retransmissão que chega depois do original ter finalmente sido entregue) é tratado comparando com o contador esperado: se a sequência estiver atrás do esperado, o pacote é um duplicado e o recetor descarta-o após enviar o ACK que o emissor claramente não recebeu na primeira vez.

12.5.2. ACK e NAK#

Dois bits de flag no cabeçalho do pacote transportam o próprio tráfego de fiabilidade:

  • ACK_REQ definido num pacote de saída significa «quero um reconhecimento de volta.» Os pacotes de dados normalmente definem este bit; os pings de estado e eventos pontuais podem não o fazer.

  • ACK definido num pacote significa «este pacote é o reconhecimento do número de sequência no cabeçalho.» Não transporta payload próprio.

  • NAK definido significa «este pacote rejeita um anterior» – normalmente por causa de um CRC inválido ou de uma falha no número de sequência. O cabeçalho indica ao emissor qual a sequência a retransmitir.

O emissor executa um ciclo de espera e avanço: transmite um pacote que requer reconhecimento e aguarda o ACK (ou NAK) correspondente antes de enviar o seguinte. O modelo de um único pacote em voo mantém o estado do emissor limitado – algumas centenas de bytes nas câmaras mais pequenas – e corresponde ao papel do protocolo como canal de controlo entre dois endpoints, em vez de um pipeline otimizado para débito. Em caso de NAK, o emissor retransmite o mesmo pacote com o flag RTX definido, para que o recetor saiba que é uma nova tentativa.

12.5.3. Temporização de retransmissão#

Se nem ACK nem NAK chegarem dentro do tempo limite de retransmissão, o emissor retransmite o pacote em voo por sua iniciativa. O tempo limite é predefinido para 500 ms e duplica em cada nova tentativa consecutiva (1 s, 2 s, …). Após o número de tentativas configurado – predefinição de três – o emissor desiste e comunica um erro de transporte à aplicação.

A duplicação do tempo limite é o padrão padrão de backoff exponencial. Um primeiro tempo limite curto apanha pacotes perdidos rapidamente; a duplicação significa que um host ocupado durante algumas centenas de milissegundos não desencadeia uma enxurrada de duplicados que aumenta a carga.

12.5.4. Configurar a fiabilidade#

Ambas as extremidades podem desativar partes da camada de fiabilidade, por acordo, quando a aplicação pode permitir-se perder dados:

  • protocol.init(ack=False) desativa os ACKs por pacote. O emissor envia e esquece; o recetor entrega o que chegar. Adequado para transmissão contínua de dados de sensor onde uma amostra desatualizada é aceitável.

  • protocol.init(seq=False) desativa o rastreamento do número de sequência, o que implica também desativar os ACKs. Útil apenas em transportes perfeitamente fiáveis.

  • protocol.init(crc=False) desativa a validação CRC mas mantém o resto do enquadramento intacto. Vale a pena fazer apenas quando o próprio transporte é suficientemente robusto para que erros de CRC não ocorram.

As predefinições – tudo ativo – são o ponto de partida correto para qualquer sessão de depuração entre host e câmara. Quando a aplicação entra em produção, as trocas tornam-se específicas aos seus dados e ao seu transporte.

12.5.5. Os códigos de estado#

Quando um erro de transporte se propaga até ao código de aplicação, chega como um código de estado. A biblioteca de protocolo define dez:

  • SUCCESS – operação concluída.

  • FAILED – o comando falhou por um motivo não especificado.

  • INVALID – o recetor rejeitou o comando ou um dos seus argumentos.

  • TIMEOUT – um temporizador de nova tentativa expirou.

  • BUSY – a câmara está ocupada (tipicamente um canal bloqueado).

  • CHECKSUM – o CRC do cabeçalho ou do payload não correspondeu.

  • SEQUENCE – o número de sequência estava fora de ordem além do que a camada consegue recuperar.

  • OVERFLOW – um payload excedeu o máximo negociado.

  • FRAGMENT – uma mensagem de múltiplos fragmentos chegou com peças em falta.

  • UNKNOWN – um apanhador defensivo para condições genuinamente inesperadas.

O código do host que chama channel_read() recebe estes como exceções Python; o código de aplicação do lado da câmara que optou pelo tratamento de erros personalizado recebe-os como valores de retorno dos callbacks do backend. A maioria das aplicações de câmara não precisa de examinar os códigos de estado – a biblioteca trata da nova tentativa, e apenas falhas genuinamente irrecuperáveis (p.ex., o próprio transporte desapareceu) chegam à aplicação.

Com o enquadramento em vigor para detetar corrupção e a fiabilidade em vigor para recuperar dela, o trabalho ao nível do fio está concluído. O código de aplicação vê pacotes enquadrados, ordenados e intactos; os bytes dentro deles são livres de significar o que o canal acima quiser.