Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/pr-build-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ jobs:
- Heltec_v3_companion_radio_ble
- Heltec_v3_repeater
- Heltec_v3_room_server
# ESP32-S3 companion with the ESP-NOW bridge: the only entry here that
# compiles src/helpers/bridges - without it a change to a bridge, or to
# the prefs both NodePrefs classes share, is not built by this check.
- heltec_v4_companion_radio_usb_bridge_espnow
# nRF52
- RAK_4631_companion_radio_ble
- RAK_4631_companion_radio_ethernet
Expand Down
33 changes: 32 additions & 1 deletion examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,24 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
}
}

#if defined(WITH_BRIDGE)
// The companion never overrode these hooks, so a companion could not mirror mesh
// traffic onto a second transport the way a repeater can. Which direction is
// mirrored is the same setting the repeater uses: bridge_pkt_src 0 = what this
// node transmits, 1 = what it receives.
void MyMesh::logRx(mesh::Packet* packet, int len, float score) {
if (_prefs.bridge_pkt_src == 1) {
bridge.sendPacket(packet);
}
}

void MyMesh::logTx(mesh::Packet* packet, int len) {
if (_prefs.bridge_pkt_src == 0) {
bridge.sendPacket(packet);
}
}
#endif

bool MyMesh::isAutoAddEnabled() const {
return (_prefs.manual_add_contacts & 1) == 0;
}
Expand Down Expand Up @@ -932,7 +950,11 @@ void MyMesh::onSendTimeout() {}

MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui)
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables),
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) {
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0)
#if defined(WITH_BRIDGE)
, bridge(&_prefs, _mgr, &rtc)
#endif
{
_iter_started = false;
_cli_rescue = false;
cli_command[0] = 0;
Expand Down Expand Up @@ -2444,6 +2466,10 @@ void MyMesh::checkSerialInterface() {
}

void MyMesh::loop() {
#if defined(WITH_BRIDGE)
bridge.loop();
#endif

BaseChatMesh::loop();

if (_cli_rescue) {
Expand Down Expand Up @@ -2485,5 +2511,10 @@ bool MyMesh::advert() {

// To check if there is pending work
bool MyMesh::hasPendingWork() const {
#if defined(WITH_BRIDGE)
// The bridge holds the 2.4 GHz radio and its queue, so a node running one
// cannot be considered idle. Same rule the repeater applies.
if (bridge.isRunning()) return true;
#endif
return _mgr->getOutboundTotal() > 0 || dirty_contacts_expiry != 0;
}
23 changes: 23 additions & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@
#include <helpers/StaticPoolPacketManager.h>
#include <target.h>

// A companion can be a bridge too. The repeater has always been able to mirror
// mesh packets onto a second transport (RS232, or ESP-NOW for a fast local
// lane); a companion could not, so a host-connected node could never be the
// cheap end of a high-speed link. The hooks below are the same ones the repeater
// uses - logRx/logTx are virtual on Dispatcher, the companion simply never
// overrode them.
#if defined(WITH_RS232_BRIDGE)
#include "helpers/bridges/RS232Bridge.h"
#define WITH_BRIDGE
#elif defined(WITH_ESPNOW_BRIDGE)
#include "helpers/bridges/ESPNowBridge.h"
#define WITH_BRIDGE
#endif

/* ---------------------------------- CONFIGURATION ------------------------------------- */

#ifndef LORA_FREQ
Expand Down Expand Up @@ -136,6 +150,10 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override;

void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
#if defined(WITH_BRIDGE)
void logRx(mesh::Packet* packet, int len, float score) override;
void logTx(mesh::Packet* packet, int len) override;
#endif
bool isAutoAddEnabled() const override;
bool shouldAutoAddContactType(uint8_t type) const override;
bool shouldOverwriteWhenFull() const override;
Expand Down Expand Up @@ -232,6 +250,11 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {

DataStore* _store;
NodePrefs _prefs;
#if defined(WITH_RS232_BRIDGE)
RS232Bridge bridge;
#elif defined(WITH_ESPNOW_BRIDGE)
ESPNowBridge bridge;
#endif
uint32_t pending_login;
uint32_t pending_status;
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
Expand Down
12 changes: 11 additions & 1 deletion examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <helpers/ConfigSerializer.h>
#include <helpers/CommonRadioPrefs.h>
#include <helpers/DynamicConfigSerializer.h>
#include <helpers/bridges/BridgePrefs.h>

#define TELEM_MODE_DENY 0
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
Expand All @@ -11,7 +12,7 @@
#define ADVERT_LOC_NONE 0
#define ADVERT_LOC_SHARE 1

class NodePrefs : public ConfigSerializer { // persisted to file
class NodePrefs : public ConfigSerializer, public BridgePrefs { // persisted to file
public:
float airtime_factor = 0;
char node_name[32];
Expand Down Expand Up @@ -166,6 +167,15 @@ class NodePrefs : public ConfigSerializer { // persisted to file
def("tel_loc", _parent->telemetry_mode_loc);
def("tel_env", _parent->telemetry_mode_env);
def("tz_offset", _parent->tz_offset);
// Bridge settings, so a companion can be a bridge (see WITH_BRIDGE). The
// defaults come from BridgePrefs; these keys only persist what the user
// changes, so an existing config file keeps working untouched.
def("br_en", _parent->bridge_enabled);
def("br_delay", _parent->bridge_delay);
def("br_src", _parent->bridge_pkt_src);
def("br_baud", _parent->bridge_baud);
def("br_ch", _parent->bridge_channel);
def("br_secret", _parent->bridge_secret, sizeof(_parent->bridge_secret));
}
public:
CompanionPrefs(NodePrefs* parent) : _parent(parent) { }
Expand Down
13 changes: 5 additions & 8 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <helpers/ConfigSerializer.h>
#include <helpers/CommonRadioPrefs.h>
#include <helpers/DynamicConfigSerializer.h>
#include <helpers/bridges/BridgePrefs.h>

#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
#define WITH_BRIDGE
Expand All @@ -22,7 +23,7 @@
#define LOOP_DETECT_MODERATE 2
#define LOOP_DETECT_STRICT 3

class NodePrefs : public ConfigSerializer {
class NodePrefs : public ConfigSerializer, public BridgePrefs {
public:
// in-memory backing data
float airtime_factor = 0;
Expand All @@ -49,13 +50,9 @@ class NodePrefs : public ConfigSerializer {
uint8_t flood_max_advert = 0;
uint8_t interference_threshold = 0;
uint8_t agc_reset_interval = 0; // secs / 4
// Bridge settings
uint8_t bridge_enabled = 0; // boolean
uint16_t bridge_delay = 0; // milliseconds (default 500 ms)
uint8_t bridge_pkt_src = 0; // 0 = logTx, 1 = logRx (default logTx)
uint32_t bridge_baud = 0; // 9600, 19200, 38400, 57600, 115200 (default 115200)
uint8_t bridge_channel = 0; // 1-14 (ESP-NOW only)
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
// Bridge settings (bridge_enabled, bridge_delay, bridge_pkt_src,
// bridge_baud, bridge_channel, bridge_secret) come from BridgePrefs, so the
// bridge implementations do not have to know which NodePrefs this is.
// Power setting
uint8_t powersaving_enabled = 0; // boolean
// Gps settings
Expand Down
12 changes: 6 additions & 6 deletions src/helpers/bridges/BridgeBase.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include "helpers/AbstractBridge.h"
#include "helpers/CommonCLI.h"
#include "helpers/bridges/BridgePrefs.h"
#include "helpers/SimpleMeshTables.h"

#include <RTClib.h>
Expand Down Expand Up @@ -53,26 +53,26 @@ class BridgeBase : public AbstractBridge {
/** Tracks bridge state */
bool _initialized = false;

/** Bridge settings, from whichever NodePrefs this build actually has. */
BridgePrefs *_prefs;

/** Packet manager for allocating and queuing mesh packets */
mesh::PacketManager *_mgr;

/** RTC clock for timestamping debug messages */
mesh::RTCClock *_rtc;

/** Node preferences for configuration settings */
NodePrefs *_prefs;

/** Tracks seen packets to prevent loops in broadcast communications */
SimpleMeshTables _seen_packets;

/**
* @brief Constructs a BridgeBase instance
*
* @param prefs Node preferences for configuration settings
* @param prefs Bridge settings
* @param mgr PacketManager for allocating and queuing packets
* @param rtc RTCClock for timestamping debug messages
*/
BridgeBase(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
BridgeBase(BridgePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: _prefs(prefs), _mgr(mgr), _rtc(rtc) {}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/helpers/bridges/BridgePrefs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include <stdint.h>

/**
* @brief The settings a bridge implementation needs, kept in their own type.
*
* A bridge must not depend on *which* NodePrefs it is handed. MeshCore has two
* unrelated ones - the CLI class the repeater examples build against, and the
* companion's - so a bridge that takes `NodePrefs*` can only ever be compiled
* into the first: including it from a companion redefines the class outright.
*
* Both NodePrefs classes inherit this, so `_prefs.bridge_enabled` and the other
* existing call sites keep working with no change.
*/
struct BridgePrefs {
uint8_t bridge_enabled = 0; // boolean
uint16_t bridge_delay = 0; // milliseconds (default 500 ms)
uint8_t bridge_pkt_src = 0; // 0 = logTx, 1 = logRx (default logTx)
uint32_t bridge_baud = 0; // 9600, 19200, 38400, 57600, 115200 (default 115200)
uint8_t bridge_channel = 0; // 1-14 (ESP-NOW only)
char bridge_secret[16] = {0}; // XOR key for bridge packets (ESP-NOW only)
};
2 changes: 1 addition & 1 deletion src/helpers/bridges/ESPNowBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) {
}
}

ESPNowBridge::ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
ESPNowBridge::ESPNowBridge(BridgePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: BridgeBase(prefs, mgr, rtc), _rx_buffer_pos(0) {
_instance = this;
}
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/bridges/ESPNowBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class ESPNowBridge : public BridgeBase {
* @param mgr PacketManager for allocating and queuing packets
* @param rtc RTCClock for timestamping debug messages
*/
ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
ESPNowBridge(BridgePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc);

/**
* Initializes the ESP-NOW bridge
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/bridges/RS232Bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#ifdef WITH_RS232_BRIDGE

RS232Bridge::RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
RS232Bridge::RS232Bridge(BridgePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: BridgeBase(prefs, mgr, rtc), _serial(&serial) {}

void RS232Bridge::begin() {
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/bridges/RS232Bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class RS232Bridge : public BridgeBase {
* @param mgr PacketManager for allocating and queuing packets
* @param rtc RTCClock for timestamping debug messages
*/
RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
RS232Bridge(BridgePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);

/**
* Initializes the RS232 bridge
Expand Down
24 changes: 24 additions & 0 deletions variants/heltec_v4/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,30 @@ lib_deps =
${heltec_v4_oled.lib_deps}
densaugeo/base64 @ ~1.4.0

; A companion that also bridges mesh packets onto ESP-NOW: the same host-facing
; radio as above, plus a 2.4 GHz local lane for nearby nodes. Not the same thing
; as running ESP-NOW *as* the mesh radio - here LoRa stays the long-range
; transport and ESP-NOW carries the same mesh packets locally.
[env:heltec_v4_companion_radio_usb_bridge_espnow]
extends = heltec_v4_oled
build_flags =
${heltec_v4_oled.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=SSD1306Display
-D ENABLE_USB_INTERFACE
-D WITH_ESPNOW_BRIDGE=1
build_src_filter = ${heltec_v4_oled.build_src_filter}
+<helpers/bridges/ESPNowBridge.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${heltec_v4_oled.lib_deps}
densaugeo/base64 @ ~1.4.0

[env:heltec_v4_companion_radio_ble]
extends = heltec_v4_oled
build_flags =
Expand Down