Skip to content

← Tornar a la llista

Per què un protocol binari (i no JSON)

L'opció fàcil era JSON. També era la que feia impossible el target de 30 FPS. Un protocol binari de sis bytes d'overhead resol el coll d'ampolla.

3 min de lectura

L’opció més fàcil era enviar JSON. Era ràpid d’escriure, debuggable amb tail -f, llegible per humans. També era la que feia fisicament impossible arribar a 30 FPS.

Estructura del frame binari: SOF (0xA5), VERSION, TYPE, LEN big-endian, PAYLOAD i CRC8.

El context

La primera versió del host enviava hex ASCII: una línia per LED amb el format 0xRRGGBB\n. Vuit caràcters + salt de línia = 9 bytes per LED. Per a 299 LEDs: 2 691 bytes per frame (afegint el \n final).

A 230 400 baud reals — comptant 10 bits per byte un cop sumat el bit de start i el d’stop —, el canal entrega uns 23 040 bytes per segon. Un frame, per anar pel cable, vol ~117 mil·lisegons. Sostre teòric: menys de 9 FPS, abans de comptar res del host, res de l’Arduino, res del CRC, res de cap recuperació d’errors.

El PRD demana 30 FPS sostingut a 300+ LEDs. La diferència entre 9 i 30 no és optimitzable: el protocol no pot.

La decisió

Frame binari minimalista, sense ambició:

Offset  Mida  Camp
0       1     SOF       = 0xA5
1       1     VERSION   = 0x01
2       1     TYPE      (DATA=0x01, INIT=0x02, PING=0x03, ACK=0x04)
3       2     LEN       (big-endian, uint16)
5       LEN   PAYLOAD
5+LEN   1     CRC8      (cobreix VERSION..PAYLOAD-1)

Sis bytes d’overhead total per frame, independent del contingut. Quatre tipus de frame:

  • DATA: N × 3 bytes RGB. El pa de cada dia.
  • INIT: 4 bytes — pin (uint16) + npixels (uint16). El host configura la tira en arrencar.
  • PING: payload buit. Health-check.
  • ACK: 1 byte de status. Resposta del firmware.

El mateix frame de 299 LEDs ara són 5 + 897 + 1 = 903 bytes. A 500 000 baud — el nou setpoint —, ~50 000 bytes/segon. Sostre teòric: ~55 FPS. Marge real per al target de 30 FPS amb gestió d’errors, retransmissions i backpressure inclosa.

Comparativa abans/després: bytes per frame i FPS sostre — ASCII hex (2691 B, ~9 FPS) vs binari (903 B, ~55 FPS).

Per què aquestes decisions concretes

Per què SOF = 0xA5. Un patró de bits 10100101 — alterna prou perquè qualsevol byte stuck-low o stuck-high es noti, i és un valor poc probable d’aparèixer accidentalment a l’inici d’un payload RGB real.

Per què LEN big-endian. Cap raó d’eficiència; només convenció de wire (“network byte order”). Un firmware en C ho desempaqueta amb dues línies; el host Python igual.

Per què CRC8 i no CRC16 o cap. A 30 FPS i bytes curts, una probabilitat d’1 / 256 de no detectar un error és tolerable. El CRC16 dobla l’overhead per frame i no detecta significativament millor a aquesta mida. Cap CRC deixaria passar errors silenciosos — i amb la WS281x això es tradueix en un LED random al mig de la tira, no en un crash. Veure errors esporàdics és pitjor que detectar-los i poder retransmetre.

Per què generadors a la decode. El codec del host exposa decode(stream) -> Iterator[Frame]. Llegeix bytes fins que troba un SOF ancorable; si en troba un de qualsevol abans, el descarta silenciosament (resync). És el mateix comportament que té el firmware. Si el cable té soroll o queden bytes orfes entre frames concatenats, el codec recupera sol sense pràcticament retransmissió.

Errors tipats, no booleans. La jerarquia DecodeError té quatre subclasses: VersionMismatch, CRCMismatch, LengthError, UnknownFrameType. Importa perquè la resposta del host és diferent per a cadascuna. Un CRCMismatch es retransmet; un VersionMismatch aborta i renegocia (el firmware corre una build antiga, per exemple); un LengthError no es retransmet — el frame estava mal format. Tornar False o None perdria aquesta distinció i amagaria el bug del host darrere d’una retransmissió que mai funcionaria.

El que ve

Tenim un protocol que cap pel cable. Ara cal un host que sàpiga generar frames a 30 FPS, fer-ho mentre la UI web demana coses, i no congelar-se quan el camí ric (tick → encode → write → ack) es desincronitza una mil·lèsima. Pròxim post: l’arquitectura Python.