De techniek achter verzenden en ontvangen (Door: HvM)
Deze uiteenzetting is initieel uitgezocht door HvM (@HvMFH55) in de Telegram groep "Meshcore Nederland groep". Hartelijk dank hiervoor.
Inleiding
In de meshcore broncode zitten CAD en LBT samen in dezelfde functie (isReceiving()). Toch zijn het twee fundamenteel verschillende mechanismen. In dit document worden ze daarom los van elkaar toegelicht.
Wat is CAD en wat is LBT?
Ik zag wat berichten waarin CAD en LBT een beetje door elkaar heen gehaald worden. daarom eerst even wat toelichting over beiden en waar ze verschillen.
CAD (Channel Activity Detection):
Detecteert of er iemand aan het zenden is door te zoeken naar het LoRa chirp-patroon (preamble). Gebruikt de correlator in de lora-chip. Kan signalen detecteren die ver onder de noisefloor zitten → dat is de kracht van lora spread-spectrum.
LBT (Listen Before Talk):
Meet de totale signaalsterkte (RSSI) van alle RF signalen op het kanaal. Als die boven een drempel uitkomt, is het kanaal "bezet". → LBT kan alleen signalen detecteren die boven de noisefloor uitkomen.
Het verschil in gevoeligheid:
LBT (RSSI-based):
• Ziet: totale RF-energie op het kanaal
• Gevoeligheid: alleen signalen boven de noise floor
CAD (preamble detectie):
• Ziet: LoRa chirp-patronen via correlatie
• Gevoeligheid: signalen ver onder de noise floor
CAD gevoeligheid per spreading factor:
SF7 → detecteert tot -7.5 dB onder noise floor
SF8 → detecteert tot -10 dB onder noise floor
SF9 → detecteert tot -12.5 dB onder noise floor
SF10 → detecteert tot -15 dB onder noise floor
SF11 → detecteert tot -17.5 dB onder noise floor
SF12 → detecteert tot -20 dB onder noise floor
Dus bijvoorbeeld bij SF8 kan een signaal dat 10 dB onder de ruis zit nog gedetecteerd worden door CAD. LBT zou dat signaal compleet missen.
Software vs Hardware:
Zowel CAD als LBT bestaan in een hardwarematige en softwarematige variant. Bij hardwarematig voert de LoRa-chip zelf de detectie uit (bijv. een dedicated CAD-scan commando of hardware RSSI-drempel). Bij softwarematig leest de firmware registerwaarden uit (zoals RSSI of preamble-detect flags) en doet zelf de logica.
Meshcore gebruikt voor zowel CAD als LBT op basis van uitsluitend de softwarematige oplossing. Mijn indruk is, dat hiervoor gekozen is omdat het eenvoudiger te implementeren is en maakt de code minder afhankelijk van specifieke lora hardware. Meshcore ondersteunt namelijk 6+ verschillende radiochips (SX1262, SX1276, SX1278, LR1110, LR1121, STM32WL) en niet allemaal bieden dezelfde hardware CAD/LBT interface. De softwarematige aanpak werkt op al deze chips op dezelfde manier.
Note: CAD is hardware driven, dat gaat op basis van de interrupt die de hardware geeft als een lora preample wordt ontvangen (https://github.com/meshcore-dev/MeshCore/blob/main/src/helpers/radiolib/CustomLLCC68.h#L81 en verwante code). LBT is wel softwarematig op basis van gemeten rssi. Zie ook verderop.
Flow: Van ontvangst tot verzending
- Pakket ontvangen via radio
- Node beslist: doorsturen (retransmitten) of niet?
- TX delay berekend (optioneel, afhankelijk van tx_delay_factor > 0)
- Pakket gaat in wachtrij met "niet vóór [tijdstip]" label
- [WACHTTIJD] — pakket wacht in queue tot scheduled_for bereikt is
- Pakket wordt kandidaat → prioriteit-selectie (minste hops eerst)
- CAD check (altijd actief): is er nu een LoRa preamble?
- Ja → wacht 120-480ms, probeer opnieuw
- Na 4 sec continu bezet → geforceerd zenden (noodmaatregel)
- Nee → ZENDEN!
- (Optioneel) LBT check: is RSSI > noise floor + int.thresh? Dan wachten, anders zenden
- → Alleen als int.thresh > 0
Tx delay
Wat doet TX delay?
Wanneer meerdere repeaters tegelijk hetzelfde pakket ontvangen, willen ze allemaal tegelijk retransmitten. TX delay voegt een willekeurige wachttijd toe zodat ze niet precies tegelijk gaan zenden.
Formule: wachttijd = random(0 ... 5 × airtime × tx_delay_factor)
Dit is vooral nuttig wanneer repeaters dicht bij elkaar staan en dus vrijwel tegelijk pakketten binnenkrijgen. Op grotere afstanden tussen repeaters is het minder van belang (de pakketten komen toch op verschillende momenten aan).
Bij tx_delay_factor = 0 gaan pakketten direct de queue in met scheduled_for = now.
Rekenvoorbeeld met de volgende voorbeeldwaarden:
• Preamble: 50 ms
• Payload: 393 ms
• Totale airtime: 443 ms
Wat doet tx_delay_factor met die 443ms airtime?
| tx_delay_factor | t (airtime × factor) | Random delay bereik |
|---|---|---|
| 0 | 0 ms | 0 ms (geen delay) |
| 0.1 | 44 ms | 0 – 220 ms |
| 0.2 | 88 ms | 0 – 440 ms |
| 0.5 | 221 ms | 0 – 1105 ms ! |
| 1.0 | 443 ms | 0 – 2215 ms !!! |
Factor 1.0:
Random wachttijd 0 tot 2.2 seconden
Gemiddeld 1.1 sec voordat CAD start. dan heeft CAD nog maar 2.9 sec over van het 4-sec window voordat het geforceerd wordt te versturen.
Note: dit is niet correct: de tx_delay gaat niet af van de CAD window. Aanpassing met onderbouwing en referenties naar code zijn hier nodig
Factor 0:
Geen wachttijd, CAD start direct
Maar als 3 repeaters tegelijk hetzelfde pakket ontvangen, willen ze ook alledrie direct zenden → gegarandeerde collision
Wachtrij (Queue)
De outbound queue bepaalt welk pakket als eerste verzonden wordt:
• Prioriteit = aantal hops. Minder hops = hogere prioriteit = eerder verzonden
• Bij gelijke prioriteit: het oudste pakket gaat eerst (FIFO)
• Vaste grootte: typisch 8-16 pakketten (afhankelijk van de hardware)
• Queue vol? → het nieuwe ontvangen pakket worden weggegooid (niet het oude) !!!
• Pakketten hebben geen vervaltijd — ze blijven in de queue tot ze verzonden zijn
Bij high-traffic: de queue raakt vol met wachtende pakketten → nieuwe berichten worden gedumpt. Er is geen mechanisme om oude, minder relevante pakketten te vervangen door nieuwere.
Note: aantal hops is niet het enige dat prioriteit bepaald. Ook het packet type en route type tellen mee. Maar allereerst wordt de volgorde van de queue bepaald door de geschedulde time, dat op basis van ontvangst en de tx delay is.
- direct trace packets hebben priority 5: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#L61
- direct routed packets hebben priority 0: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#L101
- flooded retransmits gaan op basis van hop count: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#L338
- acks hebben priority 0: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#L375-L385
- eigen adverts hebben priority 3: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#643
- eigen path messages hebben priority 2: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#L641
- andere eigen flood messages hebben priority 1: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#L645
- zero hop messages hebben priority 0: https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp#L711
CAD implementatie
Hoe werkt de software-CAD concreet?
De radio staat continu in luistermodus (RX). Terwijl de radio luistert, detecteert de lora-chip automatisch preambles en headers. Als er iets gedetecteerd wordt, zet de chip een vlaggetje (IRQ flag). Op het moment dat de node wilt gaan zenden, leest het die flag uit, dat kost microseconden. Het is dus geen actieve scan, maar een momentopname: "is er nu iemand bezig?"
Nadelen van deze aanpak:
- Het is een snapshot: als een andere node net begint met zenden (eerste 1-2 symbolen), kan de flag nog niet gezet zijn → je denkt dat het kanaal vrij is, maar dat is het niet.
- Bij hardware-CAD kun je instellen hoeveel symbolen (bijv. 2) de chip moet afwachten voordat hij oordeelt. Bij software-CAD is dat niet instelbaar.
LBT implementatie (int.thresh)
De software-LBT in meshcore werkt als volgt:
- Meet de huidige RSSI op het kanaal
- Vergelijkt met een berekende noisefloor + int.thresh waarde
- Als RSSI > noise floor + threshold → kanaal = bezet
Hoe wordt de noisefloor bepaald?
- 64 RSSI-metingen worden gemiddeld genomen op momenten dat er geen pakket actief is
- Dit is geen echte fysieke noisefloor, het is een benadering op basis van recente metingen
Problemen met deze LBT, vooral bij onze high-traffic mesh:
- Bij druk netwerkverkeer zijn er nauwelijks stille momenten → de 64 samples worden moeilijk of niet verzameld
- De meetdata kan sterk verouderd zijn. pas na 64 geldige samples wordt de floor ververst
- Op sommige lora-chips is de realtime RSSI-uitlezing onbetrouwbaar
- Het voordeel van LBT (kan ook andere SF's en niet-lora signalen detecteren) valt weg door deze onbetrouwbaarheid
Note: de resampling van de noisefloor is vrij snel. Als geen pakket ontvangen wordt, wordt de rssi in elke loop gemeten en meegenomen als sample als de waarde ervan in de buurt van de bekende noisefloor zit (om outliers eruit te halen, zie https://github.com/meshcore-dev/MeshCore/blob/main/src/helpers/radiolib/RadioLibWrappers.cpp#L81) Als er dus ergens een gaatje van een paar 100ms in het verkeer zit, is de noisefloor al bepaald. Het is erg onwaarschijnlijk dat het netwerkverkeer zo druk is dat 64 samples niet of moeilijk kunnen worden verzameld
Referentie
De noise floor sampling vind plaats in RadioLibWrapper::loop https://github.com/meshcore-dev/MeshCore/blob/main/src/helpers/radiolib/RadioLibWrappers.cpp#L76-L94
Deze wordt getriggered door het resetten van de _num_floor_samples in RadioLibWrapper::triggerNoiseFloorCalibrate (https://github.com/meshcore-dev/MeshCore/blob/main/src/helpers/radiolib/RadioLibWrappers.cpp#L48-L54)void RadioLibWrapper::loop() {
if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
if (!isReceivingPacket()) {
int rssi = getCurrentRSSI();
if (rssi < _noise_floor + SAMPLING_THRESHOLD) { // only consider samples below current floor + sampling THRESHOLD
_num_floor_samples++;
_floor_sample_sum += rssi;
}
}
} else if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES && _floor_sample_sum != 0) {
_noise_floor = _floor_sample_sum / NUM_NOISE_FLOOR_SAMPLES;
if (_noise_floor < -120) {
_noise_floor = -120; // clamp to lower bound of -120dBi
}
_floor_sample_sum = 0;
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
}
}
Conclusie
CAD (preamble detectie)
Doet vanuit de code altijd een check voordat er wordt gezonden. Werkt betrouwbaar en is zeer gevoelig, kan signalen ver onder de noise floor detecteren. Dit is het mechanisme waar je op kunt vertrouwen.
In onze drukke mesh kan het wel voorkomen dat de CAD-check door de voortdurende stroom van binnenkomende pakketten geen vrij moment vindt. Na 4 seconden continu "bezet" wordt er geforceerd gezonden — met risico op collision.
LBT (int.thresh)
Niet betrouwbaar. De noisefloor is geen echte noisefloor waarde maar een gemiddelde van 64 RSSI-metingen die bij druk verkeer verouderd of onvolledig zijn. LBT heeft in theorie wel een aanvulling op CAD: het kijkt naar alle RF-energie op het kanaal, dus ook niet-lora storingen en signalen met een andere SF. CAD kijkt alleen naar lora-preambles met dezelfde SF.
Maar door de huidige implementatie (trage sampling, onbetrouwbare RSSI op sommige chips, verouderde meetdata) is dit voordeel in de praktijk onbruikbaar.
TX delay kort houden
Een hoge tx_delay_factor heeft geen zin bij veel verkeer. Het verlengt de wachttijd in de queue, waardoor meerdere pakketten tegelijk hun wachttijd bereiken en dan allemaal tegelijk CAD gaan proberen, dat verhoogt de drukte op het kanaal nog verder. Bovendien hoe drukker het kanaal, hoe groter de kans dat CAD binnen zijn 4-seconden window geen vrij moment vindt en geforceerd moet zenden (= grotere kan op collisions).
Aanbeveling voor repeaters
- Zet tx_delay_factor op 0.1 of 0.2 (niet op 0.5, 1.0 of hoger).
Dit geeft net genoeg spreiding voor repeaters die dicht bij elkaar staan, maar laat CAD veel vaker proberen om een vrij moment te vinden.
- Zet int.thresh op 0 want LBT is onbetrouwbaar (geen realtime meting) !!!
Aanbeveling voor bridge d.m.v. 2 repeaters
(Serieel/RS-232 of ESPNOW)
- Gebruikt geen overlappende frequenties tussen beide repeaters. Wanneer beide repeater van dezelfde frequentie zitten kan CAD van een repeater alleen preambles van eigen SF herkennen.
- Gebruikt tx.delay 0.1 op de ene en 0.2 op de andere, om nog collisions nog zoveel mogelijk te voorkomen!