João Brázio пре 10 месеци
родитељ
комит
1d45c7ec66

+ 9 - 2
examples/simple_repeater/MyMesh.cpp

@@ -513,6 +513,8 @@ 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
 }
 
 void MyMesh::begin(FILESYSTEM *fs) {
@@ -523,8 +525,13 @@ void MyMesh::begin(FILESYSTEM *fs) {
 
   acl.load(_fs);
 
-#ifdef WITH_BRIDGE
-  bridge.begin();
+#if defined(WITH_ESPNOW_BRIDGE)
+  bridge.setChannel(_prefs.bridge_channel);
+#endif
+#if defined(WITH_BRIDGE)
+  if (_prefs.bridge_enabled) {
+    bridge.begin();
+  }
 #endif
 
   radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);

+ 26 - 11
examples/simple_repeater/MyMesh.h

@@ -2,7 +2,8 @@
 
 #include <Arduino.h>
 #include <Mesh.h>
-#include <helpers/CommonCLI.h>
+#include <RTClib.h>
+#include <target.h>
 
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
   #include <InternalFileSystem.h>
@@ -12,16 +13,6 @@
   #include <SPIFFS.h>
 #endif
 
-#include <helpers/ArduinoHelpers.h>
-#include <helpers/StaticPoolPacketManager.h>
-#include <helpers/SimpleMeshTables.h>
-#include <helpers/IdentityStore.h>
-#include <helpers/AdvertDataHelpers.h>
-#include <helpers/TxtDataHelpers.h>
-#include <helpers/ClientACL.h>
-#include <RTClib.h>
-#include <target.h>
-
 #ifdef WITH_RS232_BRIDGE
 #include "helpers/bridges/RS232Bridge.h"
 #define WITH_BRIDGE
@@ -32,6 +23,15 @@
 #define WITH_BRIDGE
 #endif
 
+#include <helpers/AdvertDataHelpers.h>
+#include <helpers/ArduinoHelpers.h>
+#include <helpers/ClientACL.h>
+#include <helpers/CommonCLI.h>
+#include <helpers/IdentityStore.h>
+#include <helpers/SimpleMeshTables.h>
+#include <helpers/StaticPoolPacketManager.h>
+#include <helpers/TxtDataHelpers.h>
+
 #ifdef WITH_BRIDGE
 extern AbstractBridge* bridge;
 #endif
@@ -165,6 +165,21 @@ public:
   void updateAdvertTimer() override;
   void updateFloodAdvertTimer() override;
 
+#if defined(WITH_ESPNOW_BRIDGE)
+  void setBridgeState(bool enable) {
+    if (enable == bridge.getState()) return;
+    enable ? bridge.begin() : bridge.end();
+  }
+
+  void updateBridgeChannel(int ch) override {
+    bridge.setChannel(ch);
+    if (bridge.getState()) {
+      bridge.end();
+      bridge.begin();
+    }
+  }
+#endif
+
   void setLoggingOn(bool enable) override { _logging = enable; }
 
   void eraseLogFile() override {

+ 14 - 2
src/helpers/AbstractBridge.h

@@ -11,6 +11,18 @@ public:
    */
   virtual void begin() = 0;
 
+  /**
+   * @brief Stops the bridge.
+   */
+  virtual void end() = 0;
+
+  /**
+   * @brief Gets the current state of the bridge.
+   *
+   * @return true if the bridge is initialized and running, false otherwise.
+   */
+  virtual bool getState() const = 0;
+
   /**
    * @brief A method to be called on every main loop iteration.
    *        Used for tasks like checking for incoming data.
@@ -20,14 +32,14 @@ public:
   /**
    * @brief A callback that is triggered when the mesh transmits a packet.
    *        The bridge can use this to forward the packet.
-   * 
+   *
    * @param packet The packet that was transmitted.
    */
   virtual void onPacketTransmitted(mesh::Packet* packet) = 0;
 
   /**
    * @brief Processes a received packet from the bridge's medium.
-   * 
+   *
    * @param packet The packet that was received.
    */
   virtual void onPacketReceived(mesh::Packet* packet) = 0;

+ 90 - 53
src/helpers/CommonCLI.cpp

@@ -32,32 +32,34 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
   if (file) {
     uint8_t pad[8];
 
-    file.read((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor));  // 0
-    file.read((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name));  // 4
-    file.read(pad, 4);   // 36
-    file.read((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat));  // 40
-    file.read((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon));  // 48
-    file.read((uint8_t *) &_prefs->password[0], sizeof(_prefs->password));  // 56
-    file.read((uint8_t *) &_prefs->freq, sizeof(_prefs->freq));   // 72
-    file.read((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm));  // 76
-    file.read((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd));  // 77
-    file.read((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval));  // 78
-    file.read((uint8_t *) pad, 1);  // 79  was 'unused'
-    file.read((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base));  // 80
-    file.read((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor));  // 84
-    file.read((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password));  // 88
-    file.read((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor));  // 104
-    file.read(pad, 4);   // 108
-    file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf));  // 112
-    file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr));  // 113
-    file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only));  // 114
-    file.read((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks));  // 115
-    file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw));  // 116
-    file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval));  // 120
-    file.read(pad, 3);   // 121
-    file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max));   // 124
-    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->airtime_factor, sizeof(_prefs->airtime_factor));    // 0
+    file.read((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name));              // 4
+    file.read(pad, 4);                                                                // 36
+    file.read((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat));                // 40
+    file.read((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon));                // 48
+    file.read((uint8_t *)&_prefs->password[0], sizeof(_prefs->password));             // 56
+    file.read((uint8_t *)&_prefs->freq, sizeof(_prefs->freq));                        // 72
+    file.read((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm));        // 76
+    file.read((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd));          // 77
+    file.read((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval));  // 78
+    file.read((uint8_t *)pad, 1);                                                     // 79  was 'unused'
+    file.read((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base));      // 80
+    file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor));  // 84
+    file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
+    file.read((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
+    file.read(pad, 4);                                                                             // 108
+    file.read((uint8_t *)&_prefs->sf, sizeof(_prefs->sf));                                         // 112
+    file.read((uint8_t *)&_prefs->cr, sizeof(_prefs->cr));                                         // 113
+    file.read((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only));               // 114
+    file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks));                         // 115
+    file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw));                                         // 116
+    file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval));         // 120
+    file.read(pad, 3);                                                                             // 121
+    file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max));                           // 124
+    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
 
     // sanitise bad pref values
     _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -70,6 +72,8 @@ 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);
+    _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
+    _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
 
     file.close();
   }
@@ -88,32 +92,34 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
     uint8_t pad[8];
     memset(pad, 0, sizeof(pad));
 
-    file.write((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor));  // 0
-    file.write((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name));  // 4
-    file.write(pad, 4);   // 36
-    file.write((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat));  // 40
-    file.write((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon));  // 48
-    file.write((uint8_t *) &_prefs->password[0], sizeof(_prefs->password));  // 56
-    file.write((uint8_t *) &_prefs->freq, sizeof(_prefs->freq));   // 72
-    file.write((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm));  // 76
-    file.write((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd));  // 77
-    file.write((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval));  // 78
-    file.write((uint8_t *) pad, 1);  // 79  was 'unused'
-    file.write((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base));  // 80
-    file.write((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor));  // 84
-    file.write((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password));  // 88
-    file.write((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor));  // 104
-    file.write(pad, 4);   // 108
-    file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf));  // 112
-    file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr));  // 113
-    file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only));  // 114
-    file.write((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks));  // 115
-    file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw));  // 116
-    file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval));  // 120
-    file.write(pad, 3);   // 121
-    file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max));   // 124
-    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->airtime_factor, sizeof(_prefs->airtime_factor));    // 0
+    file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name));              // 4
+    file.write(pad, 4);                                                                // 36
+    file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat));                // 40
+    file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon));                // 48
+    file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password));             // 56
+    file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq));                        // 72
+    file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm));        // 76
+    file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd));          // 77
+    file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval));  // 78
+    file.write((uint8_t *)pad, 1);                                                     // 79  was 'unused'
+    file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base));      // 80
+    file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor));  // 84
+    file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
+    file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
+    file.write(pad, 4);                                                                             // 108
+    file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf));                                         // 112
+    file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr));                                         // 113
+    file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only));               // 114
+    file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks));                         // 115
+    file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw));                                         // 116
+    file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval));         // 120
+    file.write(pad, 3);                                                                             // 121
+    file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max));                           // 124
+    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.close();
   }
@@ -252,11 +258,41 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
         mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE);
       } else if (memcmp(config, "role", 4) == 0) {
         sprintf(reply, "> %s", _callbacks->getRole());
+#ifdef WITH_BRIDGE
+      } else if (memcmp(config, "bridge.enabled", 14) == 0) {
+        sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
+#ifdef WITH_ESPNOW_BRIDGE
+      } else if (memcmp(config, "bridge.channel", 14) == 0) {
+        sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
+#endif
+#endif
       } else {
         sprintf(reply, "??: %s", config);
       }
     } 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();
@@ -301,7 +337,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
         StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
         savePrefs();
         strcpy(reply, "OK");
-      } else if (sender_timestamp == 0 && memcmp(config, "prv.key ", 8) == 0) {  // from serial command line only
+      } else if (sender_timestamp == 0 &&
+                 memcmp(config, "prv.key ", 8) == 0) { // from serial command line only
         uint8_t prv_key[PRV_KEY_SIZE];
         bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
         if (success) {

+ 34 - 23
src/helpers/CommonCLI.h

@@ -3,29 +3,35 @@
 #include "Mesh.h"
 #include <helpers/IdentityStore.h>
 
-struct NodePrefs {  // persisted to file
-    float airtime_factor;
-    char node_name[32];
-    double node_lat, node_lon;
-    char password[16];
-    float freq;
-    uint8_t tx_power_dbm;
-    uint8_t disable_fwd;
-    uint8_t advert_interval;   // minutes / 2
-    uint8_t flood_advert_interval;   // hours
-    float rx_delay_base;
-    float tx_delay_factor;
-    char guest_password[16];
-    float direct_tx_delay_factor;
-    uint32_t guard;
-    uint8_t sf;
-    uint8_t cr;
-    uint8_t allow_read_only;
-    uint8_t multi_acks;
-    float bw;
-    uint8_t flood_max;
-    uint8_t interference_threshold;
-    uint8_t agc_reset_interval;   // secs / 4
+#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
+#define WITH_BRIDGE
+#endif
+
+struct NodePrefs { // persisted to file
+  float airtime_factor;
+  char node_name[32];
+  double node_lat, node_lon;
+  char password[16];
+  float freq;
+  uint8_t tx_power_dbm;
+  uint8_t disable_fwd;
+  uint8_t advert_interval;       // minutes / 2
+  uint8_t flood_advert_interval; // hours
+  float rx_delay_base;
+  float tx_delay_factor;
+  char guest_password[16];
+  float direct_tx_delay_factor;
+  uint32_t guard;
+  uint8_t sf;
+  uint8_t cr;
+  uint8_t allow_read_only;
+  uint8_t multi_acks;
+  float bw;
+  uint8_t flood_max;
+  uint8_t interference_threshold;
+  uint8_t agc_reset_interval; // secs / 4
+  uint8_t bridge_enabled;     // boolean
+  uint8_t bridge_channel;     // 0 = AUTO, 1-14 valid
 };
 
 class CommonCLICallbacks {
@@ -50,6 +56,11 @@ public:
   virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
   virtual void clearStats() = 0;
   virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
+  
+#ifdef WITH_ESPNOW_BRIDGE
+  virtual void setBridgeState(bool enable) = 0;
+  virtual void updateBridgeChannel(int ch) = 0;
+#endif
 };
 
 class CommonCLI {

+ 11 - 0
src/helpers/bridges/BridgeBase.cpp

@@ -2,6 +2,10 @@
 
 #include <Arduino.h>
 
+bool BridgeBase::getState() const {
+  return _initialized;
+}
+
 const char *BridgeBase::getLogDateTime() {
   static char tmp[32];
   uint32_t now = _rtc->getCurrentTime();
@@ -28,6 +32,13 @@ bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t rece
 }
 
 void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
+  // Guard against uninitialized state
+  if (_initialized == false) {
+    Serial.printf("%s: BRIDGE: RX packet received before initialization\n", getLogDateTime());
+    _mgr->free(packet);
+    return;
+  }
+
   if (!_seen_packets.hasSeen(packet)) {
     _mgr->queueInbound(packet, millis() + BRIDGE_DELAY);
   } else {

+ 10 - 0
src/helpers/bridges/BridgeBase.h

@@ -21,6 +21,13 @@ class BridgeBase : public AbstractBridge {
 public:
   virtual ~BridgeBase() = default;
 
+  /**
+   * @brief Gets the current state of the bridge.
+   *
+   * @return true if the bridge is initialized and running, false otherwise.
+   */
+  bool getState() const override;
+
   /**
    * @brief Common magic number used by all bridge implementations for packet identification
    *
@@ -50,6 +57,9 @@ public:
   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;
 

+ 47 - 6
src/helpers/bridges/ESPNowBridge.cpp

@@ -27,8 +27,16 @@ ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc)
 }
 
 void ESPNowBridge::begin() {
+  Serial.printf("%s: ESPNOW BRIDGE: Initializing...\n", getLogDateTime());
+
   // Initialize WiFi in station mode
   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);
+    return;
+  }
 
   // Initialize ESP-NOW
   if (esp_now_init() != ESP_OK) {
@@ -44,13 +52,41 @@ 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 = 0;
+  peerInfo.channel = _channel;
   peerInfo.encrypt = false;
 
   if (esp_now_add_peer(&peerInfo) != ESP_OK) {
     Serial.printf("%s: ESPNOW BRIDGE: Failed to add broadcast peer\n", getLogDateTime());
     return;
   }
+
+  // Update bridge state
+  _initialized = true;
+}
+
+void ESPNowBridge::end() {
+  Serial.printf("%s: ESPNOW BRIDGE: Stopping...\n", getLogDateTime());
+
+  // Remove broadcast peer
+  uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+  if (esp_now_del_peer(broadcastAddress) != ESP_OK) {
+    Serial.printf("%s: ESPNOW BRIDGE: Error removing broadcast peer\n", getLogDateTime());
+  }
+
+  // Unregister callbacks
+  esp_now_register_recv_cb(nullptr);
+  esp_now_register_send_cb(nullptr);
+
+  // Deinitialize ESP-NOW
+  if (esp_now_deinit() != ESP_OK) {
+    Serial.printf("%s: ESPNOW BRIDGE: Error deinitializing ESP-NOW\n", getLogDateTime());
+  }
+
+  // Turn off WiFi
+  WiFi.mode(WIFI_OFF);
+
+  // Update bridge state
+  _initialized = false;
 }
 
 void ESPNowBridge::loop() {
@@ -130,11 +166,13 @@ void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sta
   // Could add transmission error handling here if needed
 }
 
-void ESPNowBridge::onPacketReceived(mesh::Packet *packet) {
-  handleReceivedPacket(packet);
-}
-
 void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
+  // Guard against uninitialized state
+  if (_initialized == false) {
+    Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime());
+    return;
+  }
+
   // First validate the packet pointer
   if (!packet) {
 #if MESH_PACKET_LOGGING
@@ -144,7 +182,6 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
   }
 
   if (!_seen_packets.hasSeen(packet)) {
-
     // Create a temporary buffer just for size calculation and reuse for actual writing
     uint8_t sizingBuffer[MAX_PAYLOAD_SIZE];
     uint16_t meshPacketLen = packet->writeTo(sizingBuffer);
@@ -193,4 +230,8 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
   }
 }
 
+void ESPNowBridge::onPacketReceived(mesh::Packet *packet) {
+  handleReceivedPacket(packet);
+}
+
 #endif

+ 30 - 0
src/helpers/bridges/ESPNowBridge.h

@@ -80,6 +80,12 @@ private:
    */
   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
    *
@@ -130,6 +136,16 @@ public:
    */
   void begin() override;
 
+  /**
+   * Stops the ESP-NOW bridge
+   *
+   * - Removes broadcast peer
+   * - Unregisters callbacks
+   * - Deinitializes ESP-NOW protocol
+   * - Turns off WiFi to release radio resources
+   */
+  void end() override;
+
   /**
    * Main loop handler
    * ESP-NOW is callback-based, so this is currently empty
@@ -151,6 +167,20 @@ 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; }
 };
 
 #endif

+ 63 - 40
src/helpers/bridges/RS232Bridge.cpp

@@ -8,6 +8,7 @@ RS232Bridge::RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCCloc
     : BridgeBase(mgr, rtc), _serial(&serial) {}
 
 void RS232Bridge::begin() {
+  Serial.printf("%s: RS232 BRIDGE: Initializing...\n", getLogDateTime());
 #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,52 +27,25 @@ void RS232Bridge::begin() {
 #error RS232Bridge was not tested on the current platform
 #endif
   ((HardwareSerial *)_serial)->begin(115200);
-}
-
-void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) {
-  // First validate the packet pointer
-  if (!packet) {
-#if MESH_PACKET_LOGGING
-    Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime());
-#endif
-    return;
-  }
-
-  if (!_seen_packets.hasSeen(packet)) {
 
-    uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
-    uint16_t len = packet->writeTo(buffer + 4);
-
-    // Check if packet fits within our maximum payload size
-    if (len > (MAX_TRANS_UNIT + 1)) {
-#if MESH_PACKET_LOGGING
-      Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len,
-                    MAX_TRANS_UNIT + 1);
-#endif
-      return;
-    }
-
-    // Build packet header
-    buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte
-    buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF;        // Magic low byte
-    buffer[2] = (len >> 8) & 0xFF;                 // Length high byte
-    buffer[3] = len & 0xFF;                        // Length low byte
-
-    // Calculate checksum over the payload
-    uint16_t checksum = fletcher16(buffer + 4, len);
-    buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte
-    buffer[5 + len] = checksum & 0xFF;        // Checksum low byte
+  // Update bridge state
+  _initialized = true;
+}
 
-    // Send complete packet
-    _serial->write(buffer, len + SERIAL_OVERHEAD);
+void RS232Bridge::end() {
+  Serial.printf("%s: RS232 BRIDGE: Stopping...\n", getLogDateTime());
+  ((HardwareSerial *)_serial)->end();
 
-#if MESH_PACKET_LOGGING
-    Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum);
-#endif
-  }
+  // Update bridge state
+  _initialized = false;
 }
 
 void RS232Bridge::loop() {
+  // Guard against uninitialized state
+  if (_initialized == false) {
+    return;
+  }
+
   while (_serial->available()) {
     uint8_t b = _serial->read();
 
@@ -140,6 +114,55 @@ void RS232Bridge::loop() {
   }
 }
 
+void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) {
+  // Guard against uninitialized state
+  if (_initialized == false) {
+    Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime());
+    return;
+  }
+
+  // First validate the packet pointer
+  if (!packet) {
+#if MESH_PACKET_LOGGING
+    Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime());
+#endif
+    return;
+  }
+
+  if (!_seen_packets.hasSeen(packet)) {
+
+    uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
+    uint16_t len = packet->writeTo(buffer + 4);
+
+    // Check if packet fits within our maximum payload size
+    if (len > (MAX_TRANS_UNIT + 1)) {
+#if MESH_PACKET_LOGGING
+      Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len,
+                    MAX_TRANS_UNIT + 1);
+#endif
+      return;
+    }
+
+    // Build packet header
+    buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte
+    buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF;        // Magic low byte
+    buffer[2] = (len >> 8) & 0xFF;                 // Length high byte
+    buffer[3] = len & 0xFF;                        // Length low byte
+
+    // Calculate checksum over the payload
+    uint16_t checksum = fletcher16(buffer + 4, len);
+    buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte
+    buffer[5 + len] = checksum & 0xFF;        // Checksum low byte
+
+    // Send complete packet
+    _serial->write(buffer, len + SERIAL_OVERHEAD);
+
+#if MESH_PACKET_LOGGING
+    Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum);
+#endif
+  }
+}
+
 void RS232Bridge::onPacketReceived(mesh::Packet *packet) {
   handleReceivedPacket(packet);
 }

+ 6 - 0
src/helpers/bridges/RS232Bridge.h

@@ -65,6 +65,12 @@ public:
    */
   void begin() override;
 
+  /**
+   * Stops the RS232 bridge
+   *
+   */
+  void end() override;
+
   /**
    * @brief Main loop handler for processing incoming serial data
    *