diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4265e1cd..4806c28a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/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); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 798d31f2..7ae10812 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include @@ -12,16 +13,6 @@ #include #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include - #ifdef WITH_RS232_BRIDGE #include "helpers/bridges/RS232Bridge.h" #define WITH_BRIDGE @@ -32,6 +23,15 @@ #define WITH_BRIDGE #endif +#include +#include +#include +#include +#include +#include +#include +#include + #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 { diff --git a/src/helpers/AbstractBridge.h b/src/helpers/AbstractBridge.h index a348e933..73a967d8 100644 --- a/src/helpers/AbstractBridge.h +++ b/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; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 7125e5b0..97c7eada 100644 --- a/src/helpers/CommonCLI.cpp +++ b/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) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ff8ff50e..55751d45 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -3,29 +3,35 @@ #include "Mesh.h" #include -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 { diff --git a/src/helpers/bridges/BridgeBase.cpp b/src/helpers/bridges/BridgeBase.cpp index 03871418..9434b23b 100644 --- a/src/helpers/bridges/BridgeBase.cpp +++ b/src/helpers/bridges/BridgeBase.cpp @@ -2,6 +2,10 @@ #include +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 { diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h index ab62619f..a3b1c85e 100644 --- a/src/helpers/bridges/BridgeBase.h +++ b/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; diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index a02f804e..c508c5a0 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/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 diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h index b43f1744..401c9eee 100644 --- a/src/helpers/bridges/ESPNowBridge.h +++ b/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 diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index d182aea6..02e36397 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/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); + + // Update bridge state + _initialized = true; } -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; - } +void RS232Bridge::end() { + Serial.printf("%s: RS232 BRIDGE: Stopping...\n", getLogDateTime()); + ((HardwareSerial *)_serial)->end(); - 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 - } + // 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); } diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 3b09de75..e9cc22d0 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/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 *