Bridge: enhance CLI configuration options

This commit is contained in:
João Brázio
2025-10-03 00:20:09 +01:00
parent aa946bbe36
commit 8edcb46a28
11 changed files with 162 additions and 118 deletions

View File

@@ -231,6 +231,12 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
}
void MyMesh::logRx(mesh::Packet *pkt, int len, float score) {
#ifdef WITH_BRIDGE
if (_prefs.bridge_pkt_src == 1) {
bridge.sendPacket(pkt);
}
#endif
if (_logging) {
File f = openAppend(PACKET_LOG_FILE);
if (f) {
@@ -252,8 +258,11 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) {
void MyMesh::logTx(mesh::Packet *pkt, int len) {
#ifdef WITH_BRIDGE
bridge.onPacketTransmitted(pkt);
if (_prefs.bridge_pkt_src == 0) {
bridge.sendPacket(pkt);
}
#endif
if (_logging) {
File f = openAppend(PACKET_LOG_FILE);
if (f) {
@@ -481,9 +490,10 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
#if defined(WITH_RS232_BRIDGE)
, bridge(WITH_RS232_BRIDGE, _mgr, &rtc)
#elif defined(WITH_ESPNOW_BRIDGE)
, bridge(_mgr, &rtc)
, bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
#endif
#if defined(WITH_ESPNOW_BRIDGE)
, bridge(&_prefs, _mgr, &rtc)
#endif
{
next_local_advert = next_flood_advert = 0;
@@ -513,8 +523,14 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.flood_advert_interval = 12; // 12 hours
_prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled
_prefs.bridge_enabled = 1; // enabled
_prefs.bridge_channel = 0; // auto
// bridge defaults
_prefs.bridge_enabled = 1; // enabled
_prefs.bridge_delay = 500; // milliseconds
_prefs.bridge_pkt_src = 0; // logTx
_prefs.bridge_baud = 115200; // baud rate
_prefs.bridge_channel = 1; // channel 1
StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret));
}
void MyMesh::begin(FILESYSTEM *fs) {
@@ -525,9 +541,6 @@ void MyMesh::begin(FILESYSTEM *fs) {
acl.load(_fs);
#if defined(WITH_ESPNOW_BRIDGE)
bridge.setChannel(_prefs.bridge_channel);
#endif
#if defined(WITH_BRIDGE)
if (_prefs.bridge_enabled) {
bridge.begin();

View File

@@ -165,23 +165,6 @@ public:
void updateAdvertTimer() override;
void updateFloodAdvertTimer() override;
#if defined(WITH_BRIDGE)
void setBridgeState(bool enable) {
if (enable == bridge.getState()) return;
enable ? bridge.begin() : bridge.end();
}
#if defined(WITH_ESPNOW_BRIDGE)
void updateBridgeChannel(int ch) override {
bridge.setChannel(ch);
if (bridge.getState()) {
bridge.end();
bridge.begin();
}
}
#endif
#endif
void setLoggingOn(bool enable) override { _logging = enable; }
void eraseLogFile() override {
@@ -199,4 +182,17 @@ public:
void clearStats() override;
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
void loop();
#if defined(WITH_BRIDGE)
void setBridgeState(bool enable) override {
if (enable == bridge.getState()) return;
enable ? bridge.begin() : bridge.end();
}
void restartBridge() override {
if (!bridge.getState()) return;
bridge.end();
bridge.begin();
}
#endif
};

View File

@@ -35,7 +35,7 @@ public:
*
* @param packet The packet that was transmitted.
*/
virtual void onPacketTransmitted(mesh::Packet* packet) = 0;
virtual void sendPacket(mesh::Packet* packet) = 0;
/**
* @brief Processes a received packet from the bridge's medium.

View File

@@ -59,7 +59,11 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
file.read((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 128
file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132
file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -72,7 +76,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
_prefs->cr = constrain(_prefs->cr, 5, 8);
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
// sanitise bad bridge pref values
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
_prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000);
_prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1);
_prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200);
_prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
file.close();
@@ -119,7 +128,11 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 128
file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133
file.close();
}
@@ -205,6 +218,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else if (memcmp(command, "clear stats", 11) == 0) {
_callbacks->clearStats();
strcpy(reply, "(OK - stats reset)");
/*
* GET commands
*/
} else if (memcmp(command, "get ", 4) == 0) {
const char* config = &command[4];
if (memcmp(config, "af", 2) == 0) {
@@ -261,38 +277,29 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
#ifdef WITH_BRIDGE
} else if (memcmp(config, "bridge.enabled", 14) == 0) {
sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
} else if (memcmp(config, "bridge.delay", 12) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
} else if (memcmp(config, "bridge.source", 13) == 0) {
sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
#endif
#ifdef WITH_RS232_BRIDGE
} else if (memcmp(config, "bridge.baud", 11) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud);
#endif
#ifdef WITH_ESPNOW_BRIDGE
} else if (memcmp(config, "bridge.channel", 14) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
#endif
} else if (memcmp(config, "bridge.secret", 13) == 0) {
sprintf(reply, "> %s", _prefs->bridge_secret);
#endif
} else {
sprintf(reply, "??: %s", config);
}
/*
* SET commands
*/
} else if (memcmp(command, "set ", 4) == 0) {
const char* config = &command[4];
#ifdef WITH_BRIDGE
if (memcmp(config, "bridge.enabled ", 15) == 0) {
_prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0;
_callbacks->setBridgeState(_prefs->bridge_enabled);
savePrefs();
strcpy(reply, "OK");
}
else
#ifdef WITH_ESPNOW_BRIDGE
if (memcmp(config, "bridge.channel ", 15) == 0) {
int ch = atoi(&config[15]);
if (ch > 0 && ch < 15) {
_prefs->bridge_channel = (uint8_t)ch;
_callbacks->updateBridgeChannel(ch);
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: channel must be 0 (AUTO) or 1-14");
}
} else
#endif
#endif
if (memcmp(config, "af ", 3) == 0) {
_prefs->airtime_factor = atof(&config[3]);
savePrefs();
@@ -428,6 +435,55 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_prefs->freq = atof(&config[5]);
savePrefs();
strcpy(reply, "OK - reboot to apply");
#ifdef WITH_BRIDGE
} else if (memcmp(config, "bridge.enabled ", 15) == 0) {
_prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0;
_callbacks->setBridgeState(_prefs->bridge_enabled);
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "bridge.delay ", 13) == 0) {
int delay = _atoi(&config[13]);
if (delay >= 0 && delay <= 10000) {
_prefs->bridge_delay = (uint16_t)delay;
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: delay must be between 0-10000 ms");
}
} else if (memcmp(config, "bridge.source ", 14) == 0) {
_prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0;
savePrefs();
strcpy(reply, "OK");
#endif
#ifdef WITH_RS232_BRIDGE
} else if (memcmp(config, "bridge.baud ", 12) == 0) {
uint32_t baud = atoi(&config[12]);
if (baud >= 9600 && baud <= 115200) {
_prefs->bridge_baud = (uint32_t)baud;
_callbacks->restartBridge();
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: baud rate must be between 9600-115200");
}
#endif
#ifdef WITH_ESPNOW_BRIDGE
} else if (memcmp(config, "bridge.channel ", 15) == 0) {
int ch = atoi(&config[15]);
if (ch > 0 && ch < 15) {
_prefs->bridge_channel = (uint8_t)ch;
_callbacks->restartBridge();
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: channel must be between 1-14");
}
} else if (memcmp(config, "bridge.secret ", 14) == 0) {
StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret));
_callbacks->restartBridge();
savePrefs();
strcpy(reply, "OK");
#endif
} else {
sprintf(reply, "unknown config: %s", config);
}

View File

@@ -30,8 +30,13 @@ struct NodePrefs { // persisted to file
uint8_t flood_max;
uint8_t interference_threshold;
uint8_t agc_reset_interval; // secs / 4
uint8_t bridge_enabled; // boolean
uint8_t bridge_channel; // 1-14
// Bridge settings
uint8_t bridge_enabled; // boolean
uint16_t bridge_delay; // milliseconds (default 500 ms)
uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logTx)
uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200)
uint8_t bridge_channel; // 1-14 (ESP-NOW only)
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
};
class CommonCLICallbacks {
@@ -57,12 +62,13 @@ public:
virtual void clearStats() = 0;
virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
#ifdef WITH_BRIDGE
virtual void setBridgeState(bool enable) = 0;
#ifdef WITH_ESPNOW_BRIDGE
virtual void updateBridgeChannel(int ch) = 0;
#endif
#endif
virtual void setBridgeState(bool enable) {
// no op by default
};
virtual void restartBridge() {
// no op by default
};
};
class CommonCLI {

View File

@@ -40,7 +40,8 @@ void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
}
if (!_seen_packets.hasSeen(packet)) {
_mgr->queueInbound(packet, millis() + BRIDGE_DELAY);
// bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network.
_mgr->queueInbound(packet, millis() + _prefs->bridge_delay);
} else {
_mgr->free(packet);
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "helpers/AbstractBridge.h"
#include "helpers/CommonCLI.h"
#include "helpers/SimpleMeshTables.h"
#include <RTClib.h>
@@ -48,34 +49,31 @@ public:
static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t);
static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t);
/**
* @brief Default delay in milliseconds for scheduling inbound packet processing
*
* It provides a buffer to prevent immediate processing conflicts in the mesh network.
* Used in handleReceivedPacket() as: millis() + BRIDGE_DELAY
*/
static constexpr uint16_t BRIDGE_DELAY = 500; // TODO: maybe too high ?
protected:
/** Tracks bridge state */
bool _initialized = false;
/** 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 mgr PacketManager for allocating and queuing packets
* @param rtc RTCClock for timestamping debug messages
*/
BridgeBase(mesh::PacketManager *mgr, mesh::RTCClock *rtc) : _mgr(mgr), _rtc(rtc) {}
BridgeBase(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: _prefs(prefs), _mgr(mgr), _rtc(rtc) {}
/**
* @brief Gets formatted date/time string for logging

View File

@@ -21,8 +21,8 @@ void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) {
}
}
ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: BridgeBase(mgr, rtc), _rx_buffer_pos(0) {
ESPNowBridge::ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: BridgeBase(prefs, mgr, rtc), _rx_buffer_pos(0) {
_instance = this;
}
@@ -33,8 +33,8 @@ void ESPNowBridge::begin() {
WiFi.mode(WIFI_STA);
// Set wifi channel
if (esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
Serial.printf("%s: ESPNOW BRIDGE: Error setting WIFI channel to %d\n", getLogDateTime(), _channel);
if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
Serial.printf("%s: ESPNOW BRIDGE: Error setting WIFI channel to %d\n", getLogDateTime(), _prefs->bridge_channel);
return;
}
@@ -52,7 +52,7 @@ void ESPNowBridge::begin() {
esp_now_peer_info_t peerInfo = {};
memset(&peerInfo, 0, sizeof(peerInfo));
memset(peerInfo.peer_addr, 0xFF, ESP_NOW_ETH_ALEN); // Broadcast address
peerInfo.channel = _channel;
peerInfo.channel = _prefs->bridge_channel;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
@@ -94,9 +94,9 @@ void ESPNowBridge::loop() {
}
void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) {
size_t keyLen = strlen(_secret);
size_t keyLen = strlen(_prefs->bridge_secret);
for (size_t i = 0; i < len; i++) {
data[i] ^= _secret[i % keyLen];
data[i] ^= _prefs->bridge_secret[i % keyLen];
}
}
@@ -166,10 +166,9 @@ void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sta
// Could add transmission error handling here if needed
}
void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
void ESPNowBridge::sendPacket(mesh::Packet *packet) {
// Guard against uninitialized state
if (_initialized == false) {
Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime());
return;
}

View File

@@ -73,22 +73,10 @@ private:
/** Current position in receive buffer */
size_t _rx_buffer_pos;
/**
* Network encryption key from build define
* Must be defined with WITH_ESPNOW_BRIDGE_SECRET
* Used for XOR encryption to isolate different mesh networks
*/
const char *_secret = WITH_ESPNOW_BRIDGE_SECRET;
/**
* Channel for ESP-NOW communication
* Valid 2.4GHz channels: 1-14
*/
int _channel = 0;
/**
* Performs XOR encryption/decryption of data
*
* Used to isolate different mesh networks
*
* Uses WITH_ESPNOW_BRIDGE_SECRET as the key in a simple XOR operation.
* The same operation is used for both encryption and decryption.
* While not cryptographically secure, it provides basic network isolation.
@@ -121,10 +109,11 @@ public:
/**
* Constructs an ESPNowBridge instance
*
* @param prefs Node preferences for configuration settings
* @param mgr PacketManager for allocating and queuing packets
* @param rtc RTCClock for timestamping debug messages
*/
ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc);
ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
/**
* Initializes the ESP-NOW bridge
@@ -166,21 +155,7 @@ public:
*
* @param packet The mesh packet to transmit
*/
void onPacketTransmitted(mesh::Packet *packet) override;
/**
* Gets the current channel
*
* @return The current channel (0 = AUTO, 1-14 = valid channel)
*/
int getChannel() const { return _channel; }
/**
* Sets the channel for ESP-NOW communication
*
* @param ch The channel to set (0 = AUTO, 1-14 = valid channel)
*/
void setChannel(int ch) { _channel = ch; }
void sendPacket(mesh::Packet *packet) override;
};
#endif

View File

@@ -4,11 +4,11 @@
#ifdef WITH_RS232_BRIDGE
RS232Bridge::RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: BridgeBase(mgr, rtc), _serial(&serial) {}
RS232Bridge::RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
: BridgeBase(prefs, mgr, rtc), _serial(&serial) {}
void RS232Bridge::begin() {
Serial.printf("%s: RS232 BRIDGE: Initializing...\n", getLogDateTime());
Serial.printf("%s: RS232 BRIDGE: Initializing at %d baud...\n", getLogDateTime(), _prefs->bridge_baud);
#if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX)
#error "WITH_RS232_BRIDGE_RX and WITH_RS232_BRIDGE_TX must be defined"
#endif
@@ -26,7 +26,7 @@ void RS232Bridge::begin() {
#else
#error RS232Bridge was not tested on the current platform
#endif
((HardwareSerial *)_serial)->begin(115200);
((HardwareSerial *)_serial)->begin(_prefs->bridge_baud);
// Update bridge state
_initialized = true;
@@ -114,10 +114,9 @@ void RS232Bridge::loop() {
}
}
void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) {
void RS232Bridge::sendPacket(mesh::Packet *packet) {
// Guard against uninitialized state
if (_initialized == false) {
Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime());
return;
}

View File

@@ -49,11 +49,12 @@ public:
/**
* @brief Constructs an RS232Bridge instance
*
* @param prefs Node preferences for configuration settings
* @param serial The hardware serial port to use
* @param mgr PacketManager for allocating and queuing packets
* @param rtc RTCClock for timestamping debug messages
*/
RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
/**
* Initializes the RS232 bridge
@@ -96,7 +97,7 @@ public:
*
* @param packet The mesh packet to transmit
*/
void onPacketTransmitted(mesh::Packet *packet) override;
void sendPacket(mesh::Packet *packet) override;
/**
* @brief Called when a complete valid packet has been received from serial