Analizzando il codice sorgente di un programma opensource, ho trovato una interessante tecnica per effettuare il parsing dei dati ricevuti in maniera molto efficente, utilizzando una struct.
Questa tecnica funziona se i dati ricevuti hanno lunghezza e struttura fissa, ad esempio si applica molto bene a pacchetti dati.
In fase di ricezione, normalmente il programma memorizza i dati ricevuti in un buffer all’interno della memoria RAM. Ipotizziamo che il pacchetto abbia una lunghezza di 14 bytes:
char rx_buffer[14]; |
Le specifiche prevedono che tale pacchetto sia formato dai seguenti campi:
[checklist]
- indirizzo, 5 bytes
- valore di temperatura, 3 bytes
- valore di umidità, 3 bytes
- contatore di trasmissione (TX), 1 bytes
- checksum, 2 bytes
[/checklist]
Possiamo definire una struct che rappresenta la struttura del pacchetto:
typedef struct { uint8_t address[5]; uint8_t temperature[3]; uint8_t humidity[3]; uint8_t tx; uint8_t checksum[2]; } my_packet; |
La tecnica consiste nell’applicare, come se fosse un template, la struct al buffer:
in tal modo i bytes ricevuti vengono automaticamente suddivisi nei vari campi:
Per effettuare il parsing è quindi sufficiente effettuare un cast del buffer:
my_packet* parsed_data = (my_packet*)rx_buffer; |
e accedere ai dati come campi di tale struct:
parsed_data->address; parsed_data->temperature; [...] |
Padding e packed
Abbiamo però un problema: per rendere più efficiente l’accesso alla memoria, i compilatori normalmente allineano le variabili in base alla loro dimensione. Questo significa che – quando viene allocato in memoria spazio per la struct – è possibile che tra le variabili vi sia dello spazio “libero” (chiamato padding):
La presenza del padding causa un disallineamento se applichiamo la struct al buffer con i dati ricevuti (che invece sono continui). Fortunatamente è possibile richiedere al compilatore di non inserire padding utilizzando l’attibuto packed:
typedef struct { uint8_t address[5]; uint8_t temperature[3]; uint8_t humidity[3]; uint8_t tx; uint8_t checksum[2]; }__attribute__((packed)) my_packet; |
In questo modo la tecnica mostrata ad inizio articolo funziona perfettamente!
Isn’t parsed_data a pointer? To access the struct field, shouldn’t you use
parsed_data->address
instead of
parsed_data.address
you’re absolutely right! thanks
mi sembra una valida alternativa a json
Veramente interessante, grazie!