Hierarchical region gating via TX duty cycle (simple_repeater)#2960
Open
khagele wants to merge 1 commit into
Open
Hierarchical region gating via TX duty cycle (simple_repeater)#2960khagele wants to merge 1 commit into
khagele wants to merge 1 commit into
Conversation
Autonomously shed inter-region traffic when a repeater's own TX duty cycle is high, protecting the local cluster during high-traffic events (issue meshcore-dev#2747). Opt-in, off by default. When TX duty cycle exceeds a configurable threshold, regions are gated from the outermost layer inward: wildcard '*' first, then the broadest named regions, always keeping the innermost cluster. Recovery re-enables regions inside-out once duty cycle drops below (threshold - hysteresis), with per-step random jitter to desync recovery across repeaters. Design notes: - Gating is transient: a new non-persisted rt_flags overlay on RegionEntry is OR'd with config flags in the forward path, so a 'region save' or reboot mid-event can never make a deny permanent. - The gate level is re-asserted every check interval, so region edits or a bulk 'region load' commit (which rebuild entries with cleared rt_flags) cannot desync the gate state; disabling the feature while gated lifts the gate immediately. - Dispatcher::getTxDutyCyclePercent() refreshes the airtime budget before reading, so recovery still fires when traffic (and TX) stops. Config (persisted, appended for upgrade compat): set dc.gate <0|1>, set dc.gate.thresh <1-100>, set dc.gate.hyst <0-50> get dc.gate[.thresh|.hyst], get dc.gate.status (live duty% + level) Adds a native gtest suite for the gating logic (test/test_region_gating) and extends the native test mocks (Arduino/Stream/SHA256) so RegionMap builds off-device. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
titusn
reviewed
Jul 15, 2026
|
|
||
| rm.applyDutyGate(0); | ||
| EXPECT_FALSE(gated(rm, "*")); | ||
| EXPECT_FALSE(gated(rm, "eu")); |
There was a problem hiding this comment.
/nitpick: should this not expect all four regions to not be gated and would splitting this test up into multiple tests make it more readable, especially for later debugging if the test ever fails? If this is according to the current coding guidelines, no problem of course. However a basic rule is: a test should contain one expect. Same can be said about the tests below.
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.
Implements the proposal in #2747: autonomous, per-repeater gating that sheds inter-region traffic when a repeater's own TX duty cycle is high, so the local cluster keeps working during high-traffic/disruption events without needing admin access.
This is a reference implementation of the proposal, opened to move the discussion to concrete code. Credit to @fizzyfuzzle, whose POC in the issue thread this tracks and generalizes, and to the NL/CZ operators (@LennertVA, @DreitTheDragon, @onnozweers, @titusn) whose feedback shaped the design.
How it works
Reuses the existing
RegionMapparent hierarchy andDispatcherairtime budget. When TX duty cycle exceeds a threshold, regions are gated from the outermost layer inward — wildcard*first, then the broadest named regions — always protecting the innermost cluster. Recovery re-enables regions inside-out once duty cycle falls belowthreshold − hysteresis, with per-step random jitter to desync recovery across repeaters. Multiple regions on the same hierarchy level gate together; their leaves stay protected.Design / safety
rt_flagsfield onRegionEntryis OR'd with configflagsin the forward path, so aregion saveor reboot mid-event can never make a deny permanent.region loadcommit (which rebuild entries with clearedrt_flags) cannot desync the gate; disabling the feature while gated lifts the gate immediately.Dispatcher::getTxDutyCyclePercent()refreshes the airtime budget before reading, so recovery still fires when traffic (and thus TX) stops.Config (opt-in, off by default; persisted with upgrade-safe append)
set dc.gate <0|1>·set dc.gate.thresh <1-100>(default 70) ·set dc.gate.hyst <0-50>(default 10) ·get dc.gate[.thresh|.hyst]·get dc.gate.status(live duty % + gate level).Gate state is intentionally not added to the
regiontree dump for now — that format is parsed and round-tripped by deployed apps, so surfacing per-region gate state in "Manage Regions" is left as an open question. Live state is available viaget dc.gate.status.Verification
Heltec_E290_repeaterfirmware builds & links.test/test_region_gating/) covering wildcard-first ordering, leaf protection, inside-out recovery, config/runtime separation, same-level regions, and edge cases; native mocks (Arduino.h/Stream.h/SHA256.h) extended soRegionMapbuilds off-device.Open questions / out of scope
getDutyCycleWindowMs(), mirroringgetAirtimeBudgetFactor()).simple_repeater;simple_room_serverwould be a trivial follow-up.🤖 Generated with Claude Code