Skip to content

Let companion radios use a bridge (ESP-NOW local lane), and give bridges their own prefs - #3410

Open
l33tdawg wants to merge 2 commits into
meshcore-dev:devfrom
l33tdawg:feat/companion-espnow-bridge
Open

l33tdawg wants to merge 2 commits into
meshcore-dev:devfrom
l33tdawg:feat/companion-espnow-bridge

Conversation

@l33tdawg

@l33tdawg l33tdawg commented Sep 14, 2026

Copy link
Copy Markdown

The problem

A companion radio cannot use a bridge, and it is not an oversight in the companion - it is a type conflict:

src/helpers/CommonCLI.h:25:7: error: redefinition of 'class NodePrefs'

The bridges take NodePrefs* (BridgeBase.h), and the NodePrefs they mean is the CLI one in helpers/CommonCLI.h. The companion has its own, unrelated NodePrefs in examples/companion_radio/NodePrefs.h. Both are class NodePrefs : public ConfigSerializer, so any translation unit that includes a bridge and the companion's prefs defines the class twice. That is why ESP-NOW today exists as a mesh radio (variants/generic_espnow) and as a repeater bridge (heltec_v4_repeater_bridge_espnow), but never as a companion: a host-connected node cannot be the cheap end of a 2.4 GHz link, even though it is exactly the node with a host behind it to use the bandwidth.

The fix, in two parts

1. Give the bridges their own settings type. BridgePrefs holds the six fields the bridges and their examples use. Both NodePrefs classes inherit it, so every existing _prefs.bridge_enabled, _prefs.bridge_secret, ... call site keeps working with no edit, and BridgeBase no longer includes CommonCLI.h at all. Only the constructor parameter types change.

2. Wire the bridge into the companion the way the repeater does it: logRx and logTx are already virtual on Dispatcher and the companion simply never overrode them. The companion gains a bridge member, those two hooks, bridge.loop() and bridge.isRunning() in hasPendingWork() - the same shape as examples/simple_repeater.

examples/companion_radio/NodePrefs.h persists the bridge settings under new keys, so an existing config file is unaffected; anything unset keeps the BridgePrefs defaults.

Outcome

A companion node keeps LoRa as its long-range transport and mirrors mesh packets onto ESP-NOW for nearby nodes - a fast local lane rather than a replacement radio. New environment:

pio run -e heltec_v4_companion_radio_usb_bridge_espnow
RAM:   [=         ]   6.1% (used 128860 bytes from 2097152)
Flash: [==        ]  16.2% (used 1063737 bytes from 6553600)
========================= [SUCCESS] ========================

Regression checks

environment result
heltec_v4_companion_radio_usb (no bridge) SUCCESS - 103,520 B RAM / 650,565 B flash
RAK_4631_companion_radio_ble (nRF52, no bridge) SUCCESS
RAK_4631_repeater (uses the CLI NodePrefs) SUCCESS
heltec_v4_companion_radio_usb_bridge_espnow (new) SUCCESS

I could not build heltec_v4_repeater_bridge_espnow here: fetching ESP32Async/ESPAsyncWebServer for that environment failed repeatedly on this machine ("RPC failed; curl 18 Transferred a partial file"). That is a download failure, not a compile one, but I want it stated rather than implied - the existing ESP-NOW bridge repeater is the one configuration I have not compiled. It should be checked in CI.

Not done

No hardware validation - I have no ESP32 boards reachable while writing this. It is compile-verified only. The bridge itself is unchanged apart from its prefs type, and the ESP-NOW bridge's shared-secret XOR is left exactly as it is (it is documented upstream as network isolation, not security; that is a separate conversation).

CI

pr-build-check.yml had no environment that compiles src/helpers/bridges at all, so nothing in that gate would have caught a mistake in this PR - or would catch one later in a bridge or in the shared prefs. The matrix now includes heltec_v4_companion_radio_usb_bridge_espnow for that reason.

The environment name follows the existing heltec_v4_repeater_bridge_espnow. Note that, like that one, it does not end in _companion_radio_usb, so build.sh get-companion-firmwares-to-build will not pick it up for releases. That is existing behaviour for bridge variants and this change does not alter it - raising it here in case releasing a bridge build is wanted.

The bridge implementations take NodePrefs*, so they can only be built into the
examples that use the CLI NodePrefs class. The companion has its own, unrelated
NodePrefs, so including a bridge from a companion fails outright:

  src/helpers/CommonCLI.h:25:7: error: redefinition of 'class NodePrefs'

That is why ESP-NOW exists as a mesh radio and as a repeater bridge, but never
as a companion: a host-connected node could not be the cheap end of a 2.4 GHz
link.

Extract the six bridge settings into BridgePrefs and have both NodePrefs classes
inherit it. Every existing _prefs.bridge_* call site keeps working unchanged, and
a bridge no longer has to know which prefs class it was handed.

Then wire the bridge into the companion the way the repeater does it: logRx and
logTx are already virtual on Dispatcher, and the companion simply never
overrode them. Adds a heltec_v4 companion environment with the ESP-NOW bridge
enabled.
…ke the other bridge

The bridge code had no CI coverage at all: no environment in pr-build-check.yml
compiles src/helpers/bridges, so a change to a bridge - or to the prefs both
NodePrefs classes now share - would not be built by the gate.

Also rename the new environment to heltec_v4_companion_radio_usb_bridge_espnow so
it reads like the existing heltec_v4_repeater_bridge_espnow. Note that neither
ends with _companion_radio_usb or _repeater, so build.sh's
get-*-firmwares-to-build does not pick either up for releases; that is existing
behaviour for bridge variants, not something this change alters.
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