Skip to content

Extra repeats#10962

Open
NomDeTom wants to merge 10 commits into
meshtastic:developfrom
NomDeTom:extra-repeats
Open

Extra repeats#10962
NomDeTom wants to merge 10 commits into
meshtastic:developfrom
NomDeTom:extra-repeats

Conversation

@NomDeTom

@NomDeTom NomDeTom commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

This pull request introduces the new RepeatScalingModule, which refines how the mesh network decides when to cancel scheduled rebroadcasts of duplicate packets. Instead of always cancelling on the first duplicate (the historical behavior), the module allows for more nuanced, per-packet-type tolerance, improving delivery odds without wasting airtime on busy networks. The change is integrated across the codebase, with supporting logic and hooks in several routers and modules.

Repeat scaling logic and integration:

  • Added the new RepeatScalingModule (src/modules/RepeatScalingModule.cpp, src/modules/RepeatScalingModule.h) to track and decide how many duplicate rebroadcasts to tolerate before cancelling, with per-portnum thresholds and busy-mesh detection. [1] [2]
  • Integrated RepeatScalingModule into FloodingRouter, NextHopRouter, and RadioLibInterface to delegate dupe-cancel logic and to log rebroadcast decisions. [1] [2] [3] [4] [5] [6]
  • Registered and instantiated RepeatScalingModule in the module setup routine, with proper conditional compilation support. [1] [2]

Testing and developer support:

  • Added a test-only method to HopScalingModule to allow direct injection of per-hop counts, supporting tests for the new repeat scaling logic.
  • Increased the native test suite count to reflect new tests for repeat scaling.

These changes make rebroadcast cancellation more adaptive and robust, especially in varying mesh network conditions.

🤝 Attestations

  • I have tested that my proposed changes behave as described.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (please specify below)
      Pro-micro

Summary by CodeRabbit

  • New Features
    • Added adaptive repeat scaling that decides how long duplicate packets are tolerated before canceling queued rebroadcasts, using packet type/port and mesh “busy” conditions.
  • Bug Fixes
    • Improved duplicate-cancellation consistency across transports, including better handling for encrypted duplicates and scheduled rebroadcasts.
    • Ensured cancel behavior is correctly gated by device role and only applies to relevant packet transports.
  • Tests
    • Added unit and end-to-end test coverage for duplicate tracking, threshold logic, and encrypted duplicate cancellation.

NomDeTom and others added 8 commits July 8, 2026 23:36
… own rebroadcast

FloodingRouter previously cancelled its own scheduled rebroadcast the instant it
heard any duplicate of a packet from another node. Add a small per-(sender, id)
ring buffer that counts duplicates heard so far, and a compile-time per-portnum
threshold (getDupeCancelThreshold) controlling how many repeats to tolerate
before giving up - default of 1 preserves existing behavior everywhere.

Enable it for TEXT_MESSAGE_APP/TEXT_MESSAGE_COMPRESSED_APP (threshold 2): no
ACK/retry safety net for broadcast chat, and this applies to DM text messages
too since NextHopRouter::shouldFilterReceived calls the same inherited
perhapsCancelDupe/getDupeCancelThreshold with no addressing-based branching.

Adds test/test_flooding_router covering the ring buffer, the default/text-
message thresholds, and the perhapsCancelDupe threshold-gating integration.

clod helped too
Add meshTooBusyForExtraRepeats() to FloodingRouter: forces
getDupeCancelThreshold() back down to 1 (cancel on first duplicate heard)
whenever channel utilization is above 10%, TX air utilization is above 4%,
or HopScalingModule estimates more than 10 active direct neighbors -
regardless of what the per-portnum table says. Spending extra airtime on
repeat-tolerant rebroadcasts isn't worth it once the mesh is already
congested or dense enough that the packet is probably getting through
some other way.

Adds a test-only HopScalingModule::setLastPerHopCountsForTest() setter
(mirrors the existing PIO_UNIT_TESTING accessors) so tests can inject a
specific direct-neighbor count without driving the full histogram/rollover
machinery.

Extends test/test_flooding_router with the busy-channel-util,
busy-air-util-tx, and busy-direct-node-count gate cases (including
at-threshold boundary checks), plus a regression test confirming the gate
has no effect on portnums that were never extended past 1.

clod helped too
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (27)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Meshnology W10 meshnology_w10 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-08. Updated for e4b04f8.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d271dc14-641b-411f-a222-ccc14f2a0cf6

📥 Commits

Reviewing files that changed from the base of the PR and between 82c9e54 and e4b04f8.

📒 Files selected for processing (3)
  • src/modules/RepeatScalingModule.cpp
  • src/modules/RepeatScalingModule.h
  • test/test_repeat_scaling_module/test_main.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/modules/RepeatScalingModule.cpp
  • src/modules/RepeatScalingModule.h
  • test/test_repeat_scaling_module/test_main.cpp

📝 Walkthrough

Walkthrough

Introduces RepeatScalingModule, which tracks heard duplicate rebroadcasts per (sender, id) and computes portnum/next-hop/busy-mesh-based thresholds to decide when to cancel a node’s scheduled rebroadcast. Wires it into FloodingRouter, NextHopRouter, RadioLibInterface, module setup, and adds unit tests plus a HopScalingModule test helper.

Changes

Repeat Scaling Module

Layer / File(s) Summary
RepeatScalingModule contract and threshold storage
src/modules/RepeatScalingModule.h
Declares RepeatScalingModule, its public API, protected helpers, ring-buffer storage, and the exported global pointer.
RepeatScalingModule implementation
src/modules/RepeatScalingModule.cpp
Implements busy-mesh detection, threshold selection, duplicate tracking, scheduled-portnum caching, and cancel-or-keep decision logic.
Integration into routers, radio interface, and module setup
src/mesh/FloodingRouter.cpp, src/mesh/FloodingRouter.h, src/mesh/NextHopRouter.cpp, src/mesh/RadioLibInterface.cpp, src/modules/Modules.cpp, src/modules/HopScalingModule.h
FloodingRouter now consults repeatScalingModule, NextHopRouter records scheduled portnums, RadioLibInterface logs tolerated duplicates, Modules.cpp instantiates the module, and HopScalingModule gains a test-only setter.
FloodingRouter dupe-cancel tests
test/test_flooding_router/test_main.cpp
Adds test doubles and Unity cases for role and transport gating around perhapsCancelDupe.
RepeatScalingModule unit tests
test/test_repeat_scaling_module/test_main.cpp, test/native-suite-count
Adds Unity coverage for duplicate counting, thresholds, busy-mesh behavior, encrypted duplicate handling, and updates the native suite count file.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague to identify the main change in the PR. Use a descriptive title like 'Add repeat-scaling duplicate cancellation for rebroadcasts'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description summarizes the change, testing, and attestations, and covers the main objectives well.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
test/test_repeat_scaling_module/test_main.cpp (1)

1-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Comment blocks exceed the one-to-two-line guideline.

Several explanatory blocks in this file (file header, ScopedChannelUtil/ScopedAirUtilTx helper intro, Group 2b header, Group 5 header) run 4–11 lines each. The project's C++ style guideline restricts comments to one or two lines except when the reason truly isn't obvious.

As per coding guidelines: "Keep code comments minimal: one or two lines maximum, only when the reason is not obvious, and do not restate the next line."

Also applies to: 55-60, 241-246, 436-446

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test_repeat_scaling_module/test_main.cpp` around lines 1 - 10, Shorten
the oversized comment blocks in test_main.cpp to meet the one- or two-line
guideline by trimming the file header and the explanatory headers around
ScopedChannelUtil, ScopedAirUtilTx, Group 2b, and Group 5. Keep only the minimal
non-obvious context, remove restatements of the code/tests that follow, and
preserve the intent in concise comments near the relevant symbols.

Source: Coding guidelines

src/modules/RepeatScalingModule.h (1)

6-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc comments exceed the repo's comment-length guideline.

The class-level comment (Lines 6-16) and several method comments (shouldCancelDupe Lines 23-30, noteScheduled Lines 33-38, getToleratedDupeCount Lines 41-44, getDupeCancelThreshold Lines 48-51, registerDupeHeard Lines 54-57) run well past the repo's cap. As per coding guidelines, **/*.{c,cc,cpp,cxx,h,hpp,hh}: "Keep code comments minimal: one or two lines... only when the reason is not obvious, and do not restate the next line."

✂️ Example trim (class-level comment)
-/**
- * RepeatScalingModule owns the "how many duplicate rebroadcasts of a packet we ourselves have
- * queued to rebroadcast should we tolerate before giving up" decision (see
- * FloodingRouter::perhapsCancelDupe, its sole caller).
- *
- * The historical behavior is to cancel our own queued rebroadcast as soon as we hear the very
- * first duplicate from another node. This module allows some packet types (see
- * getDupeCancelThreshold in RepeatScalingModule.cpp) to instead tolerate a configurable number of
- * heard duplicates first, trading a little extra airtime for better delivery odds - unless the
- * mesh is already busy/dense, in which case it always falls back to the historical behavior.
- */
+// Decides how many heard duplicate rebroadcasts to tolerate before cancelling our own queued
+// rebroadcast (see FloodingRouter::perhapsCancelDupe and getDupeCancelThreshold).

The same trimming applies to the other flagged comments in this range.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/RepeatScalingModule.h` around lines 6 - 66, The doc comments in
RepeatScalingModule are too verbose and exceed the repo’s comment-length
guideline. Trim the class comment and the method comments for shouldCancelDupe,
noteScheduled, getToleratedDupeCount, getDupeCancelThreshold, and
registerDupeHeard so each is only one or two short lines, keeping only the
essential intent and removing any restatement of the obvious behavior.

Source: Coding guidelines

src/modules/RepeatScalingModule.cpp (2)

9-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Verbose comments here also exceed the repo's comment-length guideline.

The meshTooBusyForExtraRepeats doc (Lines 19-21) and especially the block comment above getDupeCancelThreshold (Lines 48-75, ~28 lines) go well past the cap. As per coding guidelines, **/*.{c,cc,cpp,cxx,h,hpp,hh}: "Keep code comments minimal: one or two lines... only when the reason is not obvious, and do not restate the next line." Consider moving the detailed design rationale into a design doc/ADR and leaving only a terse pointer comment in-line.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/RepeatScalingModule.cpp` around lines 9 - 75, The comments in
RepeatScalingModule are too verbose and exceed the repository’s comment-length
guideline. Trim the doc block around meshTooBusyForExtraRepeats and the
explanatory comment near getDupeCancelThreshold to one or two short lines that
only state the essential intent, and remove the repeated design rationale. If
the detailed threshold behavior matters, move that explanation into a design doc
or ADR and leave a brief inline pointer comment in RepeatScalingModule.cpp.

Source: Coding guidelines


78-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate portnum-resolution logic.

getDupeCancelThreshold (Lines 78-90) and shouldCancelDupe (Lines 206-207) both independently implement decoded ? p->decoded.portnum : lookupNotedPortnum(p->from, p->id). Extracting a small private helper avoids the two copies drifting apart later.

♻️ Suggested extraction
+int32_t RepeatScalingModule::resolvePortnum(const meshtastic_MeshPacket *p) const
+{
+    return (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) ? p->decoded.portnum
+                                                                            : lookupNotedPortnum(p->from, p->id);
+}
+
 uint8_t RepeatScalingModule::getDupeCancelThreshold(const meshtastic_MeshPacket *p)
 {
-    int32_t portnum;
-    if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
-        portnum = p->decoded.portnum;
-    } else {
-        portnum = lookupNotedPortnum(p->from, p->id);
-    }
+    int32_t portnum = resolvePortnum(p);

And in shouldCancelDupe:

-    const int32_t portnum =
-        (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) ? p->decoded.portnum : lookupNotedPortnum(p->from, p->id);
+    const int32_t portnum = resolvePortnum(p);

Also applies to: 202-220

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/RepeatScalingModule.cpp` around lines 78 - 90, The portnum
resolution logic is duplicated between getDupeCancelThreshold and
shouldCancelDupe, so extract the shared decoded-or-lookup behavior into a small
private helper in RepeatScalingModule and have both call it. Use the helper to
centralize the meshtastic_MeshPacket_decoded_tag check and
lookupNotedPortnum(p->from, p->id) fallback so the two paths stay consistent and
don’t drift apart.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/modules/RepeatScalingModule.cpp`:
- Around line 9-75: The comments in RepeatScalingModule are too verbose and
exceed the repository’s comment-length guideline. Trim the doc block around
meshTooBusyForExtraRepeats and the explanatory comment near
getDupeCancelThreshold to one or two short lines that only state the essential
intent, and remove the repeated design rationale. If the detailed threshold
behavior matters, move that explanation into a design doc or ADR and leave a
brief inline pointer comment in RepeatScalingModule.cpp.
- Around line 78-90: The portnum resolution logic is duplicated between
getDupeCancelThreshold and shouldCancelDupe, so extract the shared
decoded-or-lookup behavior into a small private helper in RepeatScalingModule
and have both call it. Use the helper to centralize the
meshtastic_MeshPacket_decoded_tag check and lookupNotedPortnum(p->from, p->id)
fallback so the two paths stay consistent and don’t drift apart.

In `@src/modules/RepeatScalingModule.h`:
- Around line 6-66: The doc comments in RepeatScalingModule are too verbose and
exceed the repo’s comment-length guideline. Trim the class comment and the
method comments for shouldCancelDupe, noteScheduled, getToleratedDupeCount,
getDupeCancelThreshold, and registerDupeHeard so each is only one or two short
lines, keeping only the essential intent and removing any restatement of the
obvious behavior.

In `@test/test_repeat_scaling_module/test_main.cpp`:
- Around line 1-10: Shorten the oversized comment blocks in test_main.cpp to
meet the one- or two-line guideline by trimming the file header and the
explanatory headers around ScopedChannelUtil, ScopedAirUtilTx, Group 2b, and
Group 5. Keep only the minimal non-obvious context, remove restatements of the
code/tests that follow, and preserve the intent in concise comments near the
relevant symbols.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d84ed1d-f11d-416f-b2cd-9f712644e09a

📥 Commits

Reviewing files that changed from the base of the PR and between b3ddeca and 82c9e54.

📒 Files selected for processing (11)
  • src/mesh/FloodingRouter.cpp
  • src/mesh/FloodingRouter.h
  • src/mesh/NextHopRouter.cpp
  • src/mesh/RadioLibInterface.cpp
  • src/modules/HopScalingModule.h
  • src/modules/Modules.cpp
  • src/modules/RepeatScalingModule.cpp
  • src/modules/RepeatScalingModule.h
  • test/native-suite-count
  • test/test_flooding_router/test_main.cpp
  • test/test_repeat_scaling_module/test_main.cpp

@NomDeTom NomDeTom added module Modules not otherwise mentioned - rangetest, serial, dropzone, neighbourinfo, storeforward mesh Mesh subsystem: algorithms, radios enhancement New feature or request labels Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request mesh Mesh subsystem: algorithms, radios module Modules not otherwise mentioned - rangetest, serial, dropzone, neighbourinfo, storeforward

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant