Skip to content

MeshCore bearer: neighbour routing + adaptive airtime/channel monitoring + review fixes (#155, #157) - #162

Merged
M0LTE merged 1 commit into
masterfrom
meshcore-adaptive
Jul 1, 2026
Merged

MeshCore bearer: neighbour routing + adaptive airtime/channel monitoring + review fixes (#155, #157)#162
M0LTE merged 1 commit into
masterfrom
meshcore-adaptive

Conversation

@M0LTE

@M0LTE M0LTE commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Follow-ups to the MeshCore bearer (#154), plus fixes from an adversarial code review.

#155 — route a configured neighbour over MeshCore

Additive DbNeighbour.MeshCoreChannel copied through RouteBuilder, so a neighbour with a channel hint is selected by the bearer (positive selection). Makes outbound usable from operator config.

#157 — adaptive airtime + channel monitoring

ChannelMonitor estimates channel occupancy from the radio's LOG_RX_DATA (0x88) overheard-packet events; the bearer does listen-before-talk and refuses sends when the channel is congested — a dynamic good-citizen control on top of the static airtime budget. Per-node threshold jitter for fairness. Configurable via DAPPS_MESHCORE_CONGESTION_BACKOFF_FRACTION / _LBT_GUARD_MS. Validated on air: backoff triggered when a node measured the channel ~21% busy.

Review fixes (adversarial multi-agent review)

  • Critical: SystemOptionsStore.Parse/SaveAsync never read/wrote the MeshCore* keys → the bearer could not be enabled or configured via the persisted table. Fixed both directions + round-trip regression test.
  • Inbound wake-waiter leak; SerialPort/read-loop leak on config failure; dispose-race on send.
  • Frame parsers length-guarded; drain skips a malformed frame instead of dropping the whole batch.
  • TxBudget.Refund returns airtime reserved for a send that didn't go on air.
  • MeshCore forwards audit-labelled meshcore.

Validation

  • 17 MeshCore unit tests (config round-trip, budget refund, frame guard, occupancy, transport); full solution builds clean.
  • On-air soak between two Heltec V3s: healthy incl. watchdog reset+recovery and adaptive backoff.

Builds on #161. Inert unless MeshCoreEnabled=true; DbNeighbour/BackhaulRoute changes are additive.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KLbwvhE2cKCe8WPZNg8k17

…155, #157)

#155 - route a configured neighbour over MeshCore: additive DbNeighbour.MeshCoreChannel
copied through RouteBuilder, so a neighbour with a channel hint is selected by the bearer.

#157 - adaptive airtime + channel monitoring: ChannelMonitor estimates channel occupancy
from LOG_RX_DATA (0x88) overheard-packet events; the bearer does listen-before-talk and
refuses sends when the channel is congested (a dynamic good-citizen control on top of the
static budget), with per-node threshold jitter for fairness. Configurable via
DAPPS_MESHCORE_CONGESTION_BACKOFF_FRACTION / _LBT_GUARD_MS. Validated on air (backoff
triggered at ~21% occupancy).

Fixes from an adversarial review of the bearer:
- CRITICAL: SystemOptionsStore.Parse/SaveAsync never read/wrote the MeshCore* keys, so the
  bearer could not be enabled or configured via the persisted table. Both directions fixed;
  round-trip regression test added.
- MeshCoreInbound: cancel the losing wait so idle iterations don't leak semaphore waiters.
- MeshCoreLink: dispose the client if configuration fails after Open (SerialPort/read-loop
  leak); swallow a dispose-race on send.
- MeshCoreFrames: length-guard every parser; DrainAsync skips a malformed frame instead of
  dropping the whole batch.
- TxBudget.Refund: return airtime reserved for a send that didn't go on air.
- OutboundMessageManager: audit-label MeshCore forwards as "meshcore".

Tests: 17 MeshCore unit tests; full solution builds clean. On-air soak (two Heltec V3s)
healthy incl. watchdog reset+recovery and adaptive backoff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLbwvhE2cKCe8WPZNg8k17
@M0LTE
M0LTE merged commit d8c794e into master Jul 1, 2026
4 checks passed
@M0LTE
M0LTE deleted the meshcore-adaptive branch July 1, 2026 00:23
M0LTE added a commit that referenced this pull request Jul 1, 2026
Make the MeshCore bearer operable from the dashboard, not just DAPPS_MESHCORE_* env vars:
- Settings page: a "MeshCore bearer" panel with every MeshCore option (enable, port,
  region preset, TX power, channel/PSK, node name, airtime budget, compression,
  congestion/LBT, reliability), rendered from and saved back through /Config.
- A live status line (link state, freq/SF/CR, occupancy, delivered, reliability
  confirmed/pending, resets) polling /MeshCore/status, plus a "Reset radio" button
  that drives the watchdog recovery via /MeshCore/reset.
- MeshCoreController: GET /MeshCore/status, POST /MeshCore/reset. MeshCoreBearer
  exposes GetStatus() + ResetRadioAsync().

Also closes a latent trap: /Config model-binds and SaveAsync upserts the WHOLE
SystemOptions, so once SaveAsync learned the MeshCore keys (#162), a save from the
form (which didn't render them) would have written MeshCore defaults and clobbered
the config. Rendering them in the form fixes that.

uitest: the Settings panel-groups spot-check now includes MeshCoreEnabled/Region.


Claude-Session: https://claude.ai/code/session_01KLbwvhE2cKCe8WPZNg8k17

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant