12.5. Niezawodność – sekwencje, ACK, retransmisje#

Warstwa ramkowania wykrywa uszkodzenia za pomocą swoich sum CRC. Warstwa niezawodności zamienia „wykryte uszkodzenie” w „aplikacja nigdy nie widzi uszkodzonych danych”, negocjując retransmisje za każdym razem, gdy pakiet nie dotrze w całości.

12.5.1. Numery sekwencyjne#

Każdy nagłówek pakietu zawiera jednobajtowy numer sekwencyjny, osobny dla każdego kierunku przesyłania. Nadawca zwiększa licznik przed transmisją; odbiorca sprawdza, czy numer sekwencyjny każdego odebranego pakietu jest równy poprzedniemu plus jeden (modulo 256).

Zamiast czystego pakietu w prawidłowej kolejności, u odbiorcy mogą pojawić się trzy rzeczy:

  • Oczekiwany numer sekwencyjny z prawidłowym CRC. Pakiet jest przekazywany do następnej warstwy.

  • Oczekiwany numer sekwencyjny z błędnym CRC. Odbiorca odrzuca pakiet i (jeśli wynegocjowano ACK) wysyła NAK z prośbą o retransmisję.

  • Numer sekwencyjny o jeden wyższy niż oczekiwany, z prawidłowym CRC. Odbiorca wie, że poprzedni pakiet zaginął; wysyła NAK odwołujący się do brakującego numeru sekwencyjnego i odkłada na bok nowy pakiet.

Przypadek duplikatu (retransmisja docierająca po tym, jak oryginał w końcu się przedostał) jest obsługiwany przez porównanie z oczekiwanym licznikiem: jeśli numer sekwencyjny jest za oczekiwanym, pakiet jest duplikatem i odbiorca odrzuca go po wysłaniu ACK, którego nadawca najwyraźniej nie otrzymał za pierwszym razem.

12.5.2. ACK i NAK#

Dwa bity flag w nagłówku pakietu przenoszą sam ruch związany z niezawodnością:

  • ACK_REQ ustawiony na wychodzącym pakiecie oznacza „chcę otrzymać potwierdzenie”. Pakiety danych zwykle to ustawiają; pingi statusu i jednorazowe zdarzenia mogą tego nie robić.

  • ACK ustawiony na pakiecie oznacza „ten pakiet jest potwierdzeniem dla numeru sekwencyjnego z nagłówka”. Nie przenosi własnego ładunku.

  • NAK ustawiony oznacza „ten pakiet odrzuca poprzedni” – zwykle z powodu błędnego CRC lub luki w numerach sekwencyjnych. Nagłówek wskazuje nadawcy, który numer sekwencyjny należy retransmitować.

Nadawca działa w pętli stop-and-wait: transmituje jeden pakiet wymagający potwierdzenia, a następnie czeka na pasujący ACK (lub NAK) przed wysłaniem kolejnego. Model z jednym pakietem w locie utrzymuje stan nadawcy w ograniczonych ramach – kilkaset bajtów na najmniejszych kamerach – i odpowiada roli protokołu jako kanału sterującego między dwoma punktami końcowymi, a nie potoku zoptymalizowanego pod kątem przepustowości. Po otrzymaniu NAK nadawca retransmituje ten sam pakiet z ustawioną flagą RTX, aby odbiorca wiedział, że to ponowna próba.

12.5.3. Czas retransmisji#

Jeśli ani ACK, ani NAK nie dotrze w czasie retransmit timeout, nadawca samodzielnie retransmituje pakiet znajdujący się w locie. Limit czasu domyślnie wynosi 500 ms i podwaja się przy każdej kolejnej próbie (1 s, 2 s, …). Po skonfigurowanej liczbie prób – domyślnie trzech – nadawca poddaje się i zgłasza aplikacji błąd transportu.

Podwajanie limitu czasu to standardowy wzorzec exponential backoff. Krótki pierwszy limit czasu szybko wychwytuje utracone pakiety; podwajanie sprawia, że host zajęty przez kilkaset milisekund nie wywołuje lawiny duplikatów potęgujących obciążenie.

12.5.4. Konfigurowanie niezawodności#

Oba końce mogą za zgodą wyłączyć fragmenty warstwy niezawodności, gdy aplikacja może sobie pozwolić na utratę danych:

  • protocol.init(ack=False) wyłącza ACK dla poszczególnych pakietów. Nadawca wysyła i zapomina; odbiorca dostarcza to, co dotrze. Dobre do strumieniowego przesyłania danych z sensora, gdzie nieaktualna próbka jest akceptowalna.

  • protocol.init(seq=False) wyłącza śledzenie numerów sekwencyjnych, co implikuje również wyłączenie ACK. Przydatne tylko na całkowicie niezawodnych transportach.

  • protocol.init(crc=False) wyłącza walidację CRC, ale pozostawia resztę ramkowania nienaruszoną. Warto to robić tylko wtedy, gdy sam transport jest na tyle solidny, że błędy CRC nie występują.

Domyślne ustawienia – wszystko włączone – są właściwym punktem wyjścia dla każdej sesji debugowania host-kamera. Gdy aplikacja trafi do produkcji, kompromisy stają się specyficzne dla jej danych i transportu.

12.5.5. Kody statusu#

Gdy błąd transportu propaguje się do kodu aplikacji, dociera jako kod statusu. Biblioteka protokołu definiuje ich dziesięć:

  • SUCCESS – operacja zakończona pomyślnie.

  • FAILED – polecenie nie powiodło się z nieokreślonego powodu.

  • INVALID – odbiorca odrzucił polecenie lub jeden z jego argumentów.

  • TIMEOUT – upłynął czas licznika ponownych prób.

  • BUSY – kamera jest zajęta (zwykle zablokowany kanał).

  • CHECKSUM – CRC nagłówka lub ładunku nie pasowało.

  • SEQUENCE – numer sekwencyjny był poza kolejnością w stopniu, z którego warstwa nie może się odzyskać.

  • OVERFLOW – ładunek przekroczył wynegocjowane maksimum.

  • FRAGMENT – wieloczęściowa wiadomość dotarła z brakującymi fragmentami.

  • UNKNOWN – defensywny przypadek ogólny dla naprawdę nieoczekiwanych sytuacji.

Kod hosta wywołujący channel_read() widzi je jako wyjątki Pythona; kod aplikacji po stronie kamery, który zdecydował się na niestandardową obsługę błędów, widzi je jako wartości zwracane z wywołań zwrotnych backendu. Większość aplikacji kamery w ogóle nie musi patrzeć na kody statusu – biblioteka obsługuje ponawianie prób, a do aplikacji docierają tylko naprawdę nieodwracalne awarie (np. sam transport zniknął).

Mając ramkowanie wykrywające uszkodzenia i niezawodność umożliwiającą odzyskanie sprawności, praca na poziomie łącza jest zakończona. Kod aplikacji widzi pakiety w ramkach, uporządkowane i nienaruszone; bajty wewnątrz nich mogą oznaczać cokolwiek, czego zażąda kanał znajdujący się wyżej.