Het frame¶
OPCODE · PAYLOAD · 176 BYTES · GEEN FRAGMENTATIE · WAT NIET PAST
Elk frame begint met één byte die aangeeft wat voor frame het is. De overige bytes vormen de gegevensinhoud (payload). Binnen het frame staat geen extra kop met een eigen lengteveld, en het protocol kan één bericht niet over meerdere frames verdelen. Dat maakt het protocol eenvoudig te implementeren en legt tegelijk een harde grens op aan wat er in één keer overheen kan.
[!NOTE] Bron. Deze pagina is geverifieerd tegen de firmware zelf:
MeshCorev1.16.0, commit03b6ef4, 28 juli 2026 — bestandensrc/helpers/BaseSerialInterface.henexamples/companion_radio/MyMesh.cpp. De byte-indeling van de transportheader staat in USB-serieel en wordt hier niet herhaald.
Opcode en payload¶
De opbouw is overal gelijk:
[opcode: 1 byte][payload: 0 tot 175 bytes]
De eerste byte bepaalt hoe de rest gelezen wordt. Commando's lopen van 1
tot 65, antwoorden van 0 tot 28 en pushcodes van 0x80 tot 0x90. Omdat
commando's alleen naar de node gaan en antwoorden en pushcodes alleen
terugkomen, botsen die bereiken nergens in de praktijk.
Binnen de payload gelden drie afspraken:
- Bij getallen van meerdere bytes wordt de minst belangrijke byte eerst verstuurd (little-endian). De uitzondering is CayenneLPP in telemetriegegevens, waar juist de belangrijkste byte vooropgaat (big-endian); zie CayenneLPP.
- Tekst is UTF-8 en niet per se afgesloten met een nulbyte — de framelengte is de grens.
- Er is geen uitlijning of opvulling. Er staan geen ongebruikte tussenbytes tussen de velden: ze staan tegen elkaar aan, en een veld dat pas vanaf een bepaalde protocolversie bestaat, staat achteraan zodat oudere clients gewoon eerder ophouden met lezen.
Dat laatste is de reden dat de firmware zijn antwoorden opbouwt met een oplopende teller en aan het eind precies zoveel bytes wegschrijft als hij gevuld heeft, in plaats van een vaste structuur te versturen.
De grens van 176 bytes¶
Er is één framegrootte voor alle transporten:
src/helpers/BaseSerialInterface.h r.5
#define MAX_FRAME_SIZE 176 // +4 for transport codes (region scoping)
Het commentaar verwijst naar de vier bytes die een pakket extra kan dragen om het tot een verspreidingsgebied te beperken (region scoping); zie Regio's en Scopes.
Voor een client zijn er twee gevolgen. Bij verzenden moet je zelf controleren of het past — de firmware weigert wat te groot is, maar pas nadat het al is verstuurd. Bij ontvangen moet je erop rekenen dat een frame nooit langer is dan 176 bytes, en dat een langer frame door de firmware wordt afgekapt in plaats van geweigerd.
Wat er overblijft voor gegevens¶
De opcode neemt één byte in beslag, en de meeste commando's hebben nog een adres, een index of een tijdstempel nodig. Voor datagrammen op een kanaal — losse pakketten met gestructureerde gegevens in plaats van tekst — is die overhead vastgelegd:
examples/companion_radio/MyMesh.cpp r.101
#define MAX_CHANNEL_DATA_LENGTH (MAX_FRAME_SIZE - 9)
Negen bytes overhead, dus 167 bytes gegevens. Wie meer aanbiedt, krijgt een fout terug:
examples/companion_radio/MyMesh.cpp r.1169-1171
} else if (payload_len > MAX_CHANNEL_DATA_LENGTH) {
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_CHANNEL_DATA_LENGTH);
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
Voor tekstberichten geldt hetzelfde principe met een andere overhead. De praktische ondergrens om te onthouden: reken op ongeveer 150 bytes bruikbare tekst per bericht, en bedenk dat een LoRa-pakket daarna nóg een keer wordt opgedeeld door wat er over de lucht past — zie MeshCore Pakketstructuur.
Er is geen fragmentatie¶
Het protocol kan één groot logisch bericht niet automatisch over meerdere frames verdelen. Zo'n verdeling heet fragmentatie. Waar ze toch nodig is, is het per geval opgelost:
| Geval | Oplossing |
|---|---|
| Veel contacten ophalen | een reeks losse frames: RESP_CODE_CONTACTS_START, dan RESP_CODE_CONTACT per stuk, dan RESP_CODE_END_OF_CONTACTS |
| Berichten ophalen | één frame per bericht, herhaald tot RESP_CODE_NO_MORE_MESSAGES |
| Iets ondertekenen | een eigen deelprotocol van drie commando's: CMD_SIGN_START (33), CMD_SIGN_DATA (34), CMD_SIGN_FINISH (35) |
Dat derde geval is het enige waar de app zelf moet opdelen. De node meldt
bij CMD_SIGN_START hoeveel hij aankan:
examples/companion_radio/MyMesh.cpp r.1712-1717
} else if (cmd_frame[0] == CMD_SIGN_START) {
out_frame[0] = RESP_CODE_SIGN_START;
out_frame[1] = 0; // reserved
uint32_t len = MAX_SIGN_DATA_LEN;
memcpy(&out_frame[2], &len, 4);
_serial->writeFrame(out_frame, 6);
MAX_SIGN_DATA_LEN is 8 KiB (MyMesh.cpp r.137). De app stuurt dat in
brokken van maximaal 175 bytes met CMD_SIGN_DATA en sluit af met
CMD_SIGN_FINISH.
Wat een client moet controleren¶
| Bij verzenden | Bij ontvangen |
|---|---|
| past het commando binnen 176 bytes | is het frame lang genoeg voor de velden die je leest |
| ken je de lengtegrens van dít commando | is de eerste byte een antwoord of een push |
| is de klok gezet (voor alles met een tijdstempel) | hoort dit antwoord bij mijn openstaande verzoek |
| is er nog een verzoek open | is het veld dat je leest wel aanwezig in deze protocolversie |
De rechterkolom onderaan is de meest gemaakte fout. Velden die in een latere
FIRMWARE_VER_CODE zijn toegevoegd staan achteraan, en een oudere node
stuurt een korter frame. Wie de lengte niet controleert, kan bytes buiten het
ontvangen frame als geldige velden interpreteren of een leesfout
veroorzaken. Zie Het interactiemodel.
Bronnen¶
Firmware, commit 03b6ef4 (v1.16.0, 28 juli 2026):
src/helpers/BaseSerialInterface.h—MAX_FRAME_SIZEexamples/companion_radio/MyMesh.cpp—MAX_CHANNEL_DATA_LENGTH,MAX_SIGN_DATA_LEN, de lengtecontroles
Verwante hoofdstukken:
- USB-serieel — de header rond het frame
- De drie transporten — wat elk transport oplegt
- MeshCore Pakketstructuur — wat er daarna over de lucht gaat
- De commandogroepen — welk commando welke payload verwacht