Skip to content

Dynamic CAD sensitivity#2916

Open
weebl2000 wants to merge 2 commits into
meshcore-dev:devfrom
weebl2000:dynamic-cad
Open

Dynamic CAD sensitivity#2916
weebl2000 wants to merge 2 commits into
meshcore-dev:devfrom
weebl2000:dynamic-cad

Conversation

@weebl2000

Copy link
Copy Markdown
Contributor

Probe every 4 seconds to measure actual channel activity. If channel seems busy we adjust CAD to be less sensitive. If channel seems empty maximum CAD sensitivity.

Build here: https://mcimages.weebl.me/?commitId=dynamic-cad

Based on conversation in #1727 - this should make a busier node be less senitive to preambles. Tagging @recrof and @syssi would be interesting to see how this changes behavior.

You can use get cad and it will show stats for actual preambles detected during idle periods.

@usrflo

usrflo commented Jul 20, 2026

Copy link
Copy Markdown

@weebl2000 : I tested in my setup with 5 weakly connected nodes, 50 direct messages in 10s intervals, interference by a LoRa node sending local adverts every 6 seconds and a successful delivery counted if the last node ACKs.

Looks good: the problems I saw with 7c8e092 disappeared.

My results:

  • cad off with this branch: 37/38 ACKs for 50 messages
  • cad on with this branch: 39/40 ACKs for 50 messages
  • cad on with this branch plus the latest dev commits: 42/36 ACKs for 50 messages

Attached you can find the monitoring logs.

dev-50-wo-cad.txt
dev-50-with-cad.txt
dev-50-wo-cad-2.txt
dev-50-with-cad-2.txt
dev-50-with-cad-latest-dev.txt
dev-50-with-cad-2-latest-dev.txt

@usrflo

usrflo commented Jul 20, 2026

Copy link
Copy Markdown

I wonder why using dynamic CAD together with repeated sending #2670 decreases deliverability significantly. Resending happens relatively often, but following nodes seem not to react. May the CAD analysis create too much deafness? I wonder why I didn't see this effect when running the same test without the sending repetition.

dyn-cad-on-rep.txt

Probe every 4 seconds to measure actual channel activity. If channel
seems busy we adjust CAD to be less sensitive. If channel seems empty
maximum CAD sensitivity.
@usrflo

usrflo commented Jul 23, 2026

Copy link
Copy Markdown

@weebl2000 Agreed on the motivation: at a busy node, sub-decode remote traffic and interference trip CAD-based carrier sense into unnecessary TX deferral, so auto-lowering CAD sensitivity (detPeak) when the local CAD-hit rate is high is sensible. I've dug into this (with AI-assisted research) alongside the repeated-sending work (#2670) and want to raise three points: (a) effect on standard MeshCore, (b) interaction with repeated-sending, (c) an alternative #2933 opens up.

(a) Effect on standard MeshCore

When CAD is enabled (opt-in set cad on) it's the carrier sense for sends - checkSend → isReceiving() → isChannelActive() → scanChannel(). This PR makes it self-tuning: performChannelScan() runs at detPeak = SF+13 + _cad_peak_offset, incremented once the ambient probe sees ≥10/32 detections. Is this read correct?

The benefit is real - fewer false-busy deferrals under overload - but two costs trade against it:

  • RX deafness. Every CAD, including the new 4 s ambient probe in loop(), leaves STATE_RX and re-arms via state = STATE_IDLE; startRecv(). A preamble arriving during the scan + transition is lost - the usual "momentary deafness for collision avoidance" trade, plus one extra deaf window every 4 s on every idle receiver, uncoordinated.
  • False-busy traded for collisions, right under load. Raising detPeak makes CAD miss real preambles, not just distant ones; per-attempt detection is already <100% near sensitivity. So under high ambient activity more sends fire mid-packet - a latency cost (deferral) becomes a loss cost (collision) exactly when the channel is busiest. get cad only shows the false-busy side; I'd also log collisions / preambles-lost-during-CAD to judge the net.

(b) Interaction with repeated-sending (#2670)

Standard MeshCore doesn't imply repeated-sending, so (a) stands on its own - but the repeated-sending demonstrates the limits of (a).

#2670's resend path deliberately avoids CAD: it uses a non-invasive RSSI/noise-floor gate (isResendChannelActive()) so the radio stays in RX and can still overhear the downstream forward that cancels redundant resends. So this PR doesn't gate resends directly. My hardware regression with CAD on comes through:

  1. Desensitized first-send CAD-LBT. Repeated-sending targets weak / near-sensitivity links - exactly where CAD false-negatives are worst; raised detPeak makes first-sends collide on the very links redundancy should rescue.
  2. Positive feedback loop. More occupancy → ambient probe sees high hits → _cad_peak_offset ratchets up → CAD misses more preambles → more collisions → more resends → more occupancy. The probe runs only while idle, window 32×4 s ≈ 128 s, so this builds over minutes - matching a "delivery collapses under sustained load" symptom, not an instant drop.

Net: the two pull opposite ways - CAD desensitizes under load, repeated-sending needs a collision-free, RX-receptive channel. I saw exactly this in the A/B I posted two days ago (CAD on vs off under repeated-sending load, past the 128 s window - see above).

(c) The RSSI-margin path

#1727 moved away from RSSI carrier sense ("Using RSSI isn't very reliable"), so interference_threshold is disabled.
Now #2933 fixes the part that was actually broken: the floor. The old one-way ratchet walked _noise_floor to the -120 clamp and stuck, making every margin over-sensitive; the median estimator accepts all idle samples and recovers both ways, so currentRSSI - noiseFloor is usable again.

The "RSSI unreliable" residual is smaller than it looks for a margin gate. getRSSI(false) is a raw instantaneous read with no validity guard, so spikes happen - but (1) the floor is sampled through the same getCurrentRSSI path, so persistent bias cancels in the subtraction; (2) the median rejects transient spikes in the floor, and a false-busy in the instantaneous read only costs latency (defer-and-retry), not a packet. The one mode that wouldn't self-heal - an AGC sticking low - doesn't apply under MeshCore's default rx_boosted_gain=on (fixed max gain, AGC loop off); it'd only matter in power-saving/AGC mode. #1743's sx26xResetAGC is a fine on-demand hedge if a sustained stuck-low is ever seen, but it's not load-bearing for the margin, and as a periodic reset it adds a deafness window - so I wouldn't make RSSI-margin's viability depend on it.

The real argument for keeping an RSSI-margin path isn't accuracy, it's that CAD is a different sensing modality: a preamble detector, immune to RSSI calibration, but structurally unable to run without leaving RXCONTINUOUS - so it deafens the receiver. A non-invasive RSSI-margin gate stays in RX, which is exactly what repeated-sending's cancel-overhearing (#2670) relies on. The trade is calibration-immunity + deafness (CAD) vs calibration-noisy + RX-receptive (RSSI-margin).

So for discussion: with #2933 making the floor reliable and the hardware residual largely self-neutralizing (especially under boosted gain), is the non-invasive RSSI-margin path worth revisiting?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants