Skip to content

Extra repeats#10962

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

Extra repeats#10962
NomDeTom wants to merge 9 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 to better determine when duplicate packets should be tolerated versus canceling queued rebroadcasts, using packet characteristics and mesh congestion signals.
  • Bug Fixes
    • Improved duplicate-cancellation behavior so queued rebroadcasts are stopped more reliably (including encrypted broadcasts and direct messages), with decisions correctly limited to the appropriate device role and transports.
  • Tests
    • Added comprehensive unit and end-to-end test coverage for duplicate tracking, threshold decisions, scheduling notes, and encrypted duplicate cancellation.

@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 (29)
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 Pocket V3 rak_wismesh_pocket nrf52840
RAK WisMesh Repeater Mini V2 rak_wismesh_repeater_mini nrf52840
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-17. Updated for 0aa53fd.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces RepeatScalingModule, which tracks duplicate rebroadcasts by (sender, id) and computes packet, next-hop, and mesh-busyness thresholds for cancellation. Integrates the module into routing, transmission logging, module setup, and adds comprehensive Unity tests.

Changes

Repeat Scaling Module

Layer / File(s) Summary
RepeatScalingModule contract and tracking storage
src/modules/RepeatScalingModule.h
Declares the module API, threshold hooks, duplicate tracking, ring-buffer storage, and global pointer.
Threshold and duplicate decision implementation
src/modules/RepeatScalingModule.cpp
Implements threshold selection, busy-mesh detection, duplicate tracking, scheduled portnum caching, and cancellation decisions.
Router, radio, and module integration
src/mesh/FloodingRouter.cpp, src/mesh/FloodingRouter.h, src/mesh/NextHopRouter.cpp, src/mesh/RadioLibInterface.cpp, src/modules/Modules.cpp, src/modules/HopScalingModule.h
Connects repeat scaling to cancellation, rebroadcast scheduling, transmission logging, module initialization, and test setup.
FloodingRouter cancellation tests
test/test_flooding_router/test_main.cpp
Tests role and transport gating, module delegation, and the cancellation path.
RepeatScalingModule behavior tests
test/test_repeat_scaling_module/test_main.cpp, test/native-suite-count
Tests duplicate tracking, thresholds, busy-mesh gates, encrypted duplicates, cancellation timing, and the test harness.

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

Suggested labels: enhancement

Suggested reviewers: caveman99

Sequence Diagram(s)

sequenceDiagram
  participant NextHopRouter
  participant RepeatScalingModule
  participant FloodingRouter
  participant RadioLibInterface

  NextHopRouter->>RepeatScalingModule: noteScheduled(sender, id, portnum)
  FloodingRouter->>RepeatScalingModule: shouldCancelDupe(packet)
  RepeatScalingModule-->>FloodingRouter: cancellation decision
  RadioLibInterface->>RepeatScalingModule: getToleratedDupeCount(sender, id)
  RepeatScalingModule-->>RadioLibInterface: tolerated duplicate count
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.75% 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 related to the repeat-scaling change, but it is too vague to describe the main update clearly. Use a more specific title like "Add RepeatScalingModule for duplicate rebroadcast cancellation".
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description covers the module, integration, testing, and attestations required by the template.
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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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
@phaseloop

Copy link
Copy Markdown
Contributor

I wonder if there would be value in using RSSI of a packet in making retransmission decision. For example allow duplicate if heard retransmission was from a node relatively far away (using threshold calculated from RSSI of nearest neighbours).

@NomDeTom

Copy link
Copy Markdown
Collaborator Author

I wonder if there would be value in using RSSI of a packet in making retransmission decision. For example allow duplicate if heard retransmission was from a node relatively far away (using threshold calculated from RSSI of nearest neighbours).

I think we tried something like that before. @RCGV1 didn't you have something?

NomDeTom added 9 commits July 18, 2026 05:55
… 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/mesh/FloodingRouter.cpp (1)

142-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Condense overly long comments.

As per coding guidelines, code comments should be kept minimal with a strict limit of one or two lines maximum.

  • src/mesh/FloodingRouter.cpp#L142-L145: Reduce the 4-line explanatory block to a brief summary (e.g., // Fall back to cancelling on the first duplicate if RepeatScalingModule is excluded.).
  • src/modules/HopScalingModule.h#L207-L209: Condense the 3-line comment to two lines (e.g., /// Directly inject the last-rollover per-hop counts for tests, bypassing the histogram/rollHour() machinery.).
🤖 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/mesh/FloodingRouter.cpp` around lines 142 - 145, The comments at
src/mesh/FloodingRouter.cpp lines 142-145 and src/modules/HopScalingModule.h
lines 207-209 are overly long. Condense the FloodingRouter comment to one brief
line describing first-duplicate cancellation when RepeatScalingModule is
excluded, and reduce the HopScalingModule comment to no more than two lines
while preserving its test-injection and bypass behavior.

Source: Coding guidelines

🤖 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/mesh/FloodingRouter.cpp`:
- Around line 142-145: The comments at src/mesh/FloodingRouter.cpp lines 142-145
and src/modules/HopScalingModule.h lines 207-209 are overly long. Condense the
FloodingRouter comment to one brief line describing first-duplicate cancellation
when RepeatScalingModule is excluded, and reduce the HopScalingModule comment to
no more than two lines while preserving its test-injection and bypass behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e43afe8c-a810-4bcd-a7bf-a85131ce2f0e

📥 Commits

Reviewing files that changed from the base of the PR and between e4b04f8 and 0aa53fd.

📒 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
🚧 Files skipped from review as they are similar to previous changes (8)
  • test/native-suite-count
  • src/modules/Modules.cpp
  • src/mesh/NextHopRouter.cpp
  • src/modules/RepeatScalingModule.h
  • src/mesh/FloodingRouter.h
  • test/test_flooding_router/test_main.cpp
  • src/modules/RepeatScalingModule.cpp
  • test/test_repeat_scaling_module/test_main.cpp

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.

2 participants