Add adaptive congestion control for backbone repeaters#1938
Open
wbijen wants to merge 1 commit intomeshcore-dev:devfrom
Open
Add adaptive congestion control for backbone repeaters#1938wbijen wants to merge 1 commit intomeshcore-dev:devfrom
wbijen wants to merge 1 commit intomeshcore-dev:devfrom
Conversation
92fb4ca to
995dbed
Compare
robekl
reviewed
Mar 7, 2026
| @@ -415,10 +415,24 @@ bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[]) | |||
|
|
|||
| bool MyMesh::allowPacketForward(const mesh::Packet *packet) { | |||
Contributor
There was a problem hiding this comment.
could a max based on min(_prefs->flood_max, getEffectiveFloodMax(...)) be used, to adhere to a configured lower value?
Contributor
Author
There was a problem hiding this comment.
Lowest now wins, fixed and pushed! thanks.
It now has GROUP_FLOOD_MAX and ADVERT_FLOOD_MAX configured at 8. Do you think that value makes sense or you want me to set it to 64 so by default there is no change in behavior at quiet times?
robekl
reviewed
Mar 7, 2026
Introduces BusyTracker — a lightweight busy score tracker that computes a composite congestion metric from airtime ratio, queue pressure, and flood duplicate ratio. The score drives a piecewise-linear ramp that smoothly reduces flood hop limits for group messages and advertisements as congestion builds, while leaving direct/unicast traffic untouched. Includes counter wrap protection (~49 day uptime), misconfiguration guards for build flag overrides, stats-busy CLI command, OLED display integration showing effective hop limits, and traffic-aware retransmit delay scaling. All thresholds and weights are compile-time configurable via build_flags. Addresses meshcore-dev#1588, complements meshcore-dev#1502. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
995dbed to
aecd4f9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adaptive Congestion Control for Backbone Repeaters
Summary
This PR adds a lightweight, adaptive congestion control system to repeater nodes. When the network gets busy, repeaters automatically and gracefully reduce flood hop limits for group messages and advertisements, keeping the mesh healthy without manual intervention.
This addresses #1588 (repeater limit channel flooding) with a busy-adaptive approach rather than a static cap.
How it works
BusyTracker (
src/helpers/BusyTracker.h) computes a composite busy score (0.0–1.0) from three weighted signals over a 60-second tumbling window:All three weights are compile-time configurable (
BUSY_WEIGHT_AIRTIME,BUSY_WEIGHT_QUEUE,BUSY_WEIGHT_DUP).The busy score drives a piecewise-linear ramp that smoothly reduces effective hop limits as congestion builds:
Example: A repeater running at 40% busy will forward group messages up to ~6 hops (down from 8), while advertisements drop to ~5 hops. At 80% busy, groups are limited to ~3 hops and adverts to ~2. Direct/unicast traffic is never throttled.
Traffic-aware retransmit delay
getRetransmitDelay()scales the random jitter window by(1 + busy × 2):busy = 0(idle): delay unchanged — same as current behaviorbusy = 0.5(moderate): 2× wider jitter window, spreading retransmissionsbusy = 1.0(saturated): 3× wider jitter windowThis naturally reduces collision probability under load by spreading retransmissions over a wider time window.
getDirectRetransmitDelay()is intentionally not scaled — DM and ACK traffic should not be penalized during congestion, as these are high-value, low-volume packets that need reliable delivery.Defaults
All thresholds are compile-time
#defines, overridable viabuild_flagsinplatformio.ini:BUSY_ONSETBUSY_WINDOW_MSBUSY_WEIGHT_AIRTIMEBUSY_WEIGHT_QUEUEBUSY_WEIGHT_DUPGROUP_FLOOD_MAXGROUP_FLOOD_MIDGROUP_FLOOD_FLOORADVERT_FLOOD_MAXADVERT_FLOOD_MIDADVERT_FLOOD_FLOORPACKET_POOL_SIZEBUSY_TX_DELAY_SCALEOperators can tune per-deployment, for example a high-traffic urban repeater:
What's included
allowPacketForward()— per-type flood limiting for group, advert, and fallback to_prefs.flood_maxgetRetransmitDelay()scales jitter by(1 + busy × 2), spreading retransmissions when congested. Direct message delays are intentionally untouched to protect DM/ACK deliverygetEffectiveFloodMax()clampsmidandfloorif build flag overrides violateceiling >= mid >= floorstats-busyCLI command — prints busy score, component breakdown, and effective hop limits over serialGRP:5/8 ADV:4/8 B:32%variants/heltec_v4/platformio.iniDesign choices
PACKET_POOL_SIZEconstant shared between pool allocator and BusyTracker to prevent silent driftIs this overengineered?
Honest question — the core mechanism is simple (composite score → hop limit ramp), but there are a lot of configurable knobs and a linear ramp. The rationale is that different deployments face very different traffic patterns (urban dense mesh vs rural sparse backbone), and we won't know the right defaults until we have field data. The
#ifndefguards make these zero-cost to ignore if you don't need them.That said, if the consensus is that fewer knobs and hardcoded defaults would be better to start with, happy to strip the configurability down to just
GROUP_FLOOD_MAX/FLOORandADVERT_FLOOD_MAX/FLOORand bake the rest in. We can always add knobs back later if field testing shows they're needed. Would love to hear thoughts on this.Files changed
src/helpers/BusyTracker.hgetEffectiveFloodMax(), all compile-time definesexamples/simple_repeater/MyMesh.hPACKET_POOL_SIZEdefineexamples/simple_repeater/MyMesh.cppexamples/simple_repeater/UITask.hexamples/simple_repeater/UITask.cppexamples/simple_repeater/main.cppvariants/heltec_v4/platformio.iniRelated work
GROUP_FLOOD_MAXis this feature, but busy-adaptive rather than static.Looking forward to feedback! Happy to tune defaults or simplify if it feels like too much for a first pass.