Compare commits

..

8 Commits

Author SHA1 Message Date
614594a035 Merge fix/improve-error-logging: Human-readable MQTT errors 2026-02-05 10:14:40 -07:00
dd881f79f3 Merge fix/memory-leaks: Add destructor, init pointers to nullptr 2026-02-05 10:14:02 -07:00
980a42f98b Merge fix/mqtt-exponential-backoff: Backoff 1s→60s on retry 2026-02-05 10:13:58 -07:00
69a2e04cd2 Merge fix/wifi-manager-dangling-pointer: Cache SSID to fix use-after-free 2026-02-05 10:13:54 -07:00
2506e3ce3e fix: improve error logging for MQTT connection failures
- Add TLS security warning when certificate verification is disabled
- Add getMQTTErrorString() to translate PubSubClient error codes
- Show broker/user info on connection failure for debugging
- Standardize log prefix to [MQTT] (was inconsistent [MQTTS])
- Use transport-agnostic "Network" instead of "WiFi" in messages
- Fix WiFiState/NetworkState enum mismatch in getWiFiStatus()

Error codes now show meaningful messages like:
  "Bad credentials (check username/password)" instead of "rc=4"
2026-02-05 10:02:15 -07:00
669ef89a66 fix: memory leaks and uninitialized pointers
- Initialize _mqtt_bridge and _web_config to nullptr in declarations
- Add destructor to clean up dynamically allocated objects
- Initialize _last_mqtt_stats to 0 in declaration
- Fix WiFiState/NetworkState enum mismatch in getWiFiStatus()

While embedded firmware typically runs forever (making these not
critical leaks), proper cleanup enables testing and prevents
static analyzer warnings.
2026-02-05 09:59:49 -07:00
0905aa4bf8 Add exponential backoff to MQTT reconnection
Instead of fixed 5s/30s retry intervals, implement exponential backoff:
- Initial delay: 1 second
- Max delay: 60 seconds
- Doubles on each failed attempt
- Resets to minimum on successful connection

This prevents hammering a down broker while still reconnecting
quickly when the issue is transient.

Also includes fix for WiFiState/NetworkState enum mismatch in
getWiFiStatus() which was already on main.
2026-02-05 09:48:49 -07:00
91d144901a Fix dangling pointer bug in WiFiManager getSSID/getConnectionName
WiFi.SSID() returns a temporary String object. Calling .c_str() on it
returns a pointer to the internal buffer, but the String is destroyed
at the end of the statement - leaving a dangling pointer.

Fix by caching the SSID in a member variable when connection state
changes, and returning a pointer to that stable storage.

Also fix getWiFiStatus() in MyMesh.cpp which was using WiFiState enum
values instead of NetworkState (the interface return type).
2026-02-05 09:47:01 -07:00
6 changed files with 295 additions and 152 deletions

View File

@ -17,6 +17,7 @@ MQTTBridge::MQTTBridge(INetworkManager* network, mesh::PacketManager* mgr, mesh:
_last_status_publish(0),
_last_stats_publish(0),
_connected_since(0),
_current_backoff_ms(BACKOFF_MIN_MS),
_messages_sent(0),
_messages_received(0),
_reconnect_count(0),
@ -45,10 +46,11 @@ void MQTTBridge::begin(const MQTTConfig& config, const uint8_t* self_pubkey) {
if (_config.use_tls) {
_wifi_client_secure.setInsecure(); // Skip cert verification
_mqtt_client.setClient(_wifi_client_secure);
Serial.printf("[MQTT] TLS enabled\n");
Serial.println("[MQTT] TLS enabled");
Serial.println("[MQTT] WARNING: Certificate verification DISABLED - vulnerable to MITM attacks!");
} else {
_mqtt_client.setClient(_wifi_client);
Serial.printf("[MQTT] Plain TCP\n");
Serial.println("[MQTT] Plain TCP (no encryption)");
}
_mqtt_client.setServer(_config.broker, _config.port);
@ -73,14 +75,15 @@ void MQTTBridge::loop() {
if (!_network->isConnected()) {
if (_state == MQTTState::CONNECTED) {
_state = MQTTState::DISCONNECTED;
Serial.println("[MQTTS] WiFi lost, disconnected");
Serial.println("[MQTT] Network connection lost, disconnecting");
}
return;
}
switch (_state) {
case MQTTState::DISCONNECTED:
if (millis() - _last_connect_attempt > 5000) {
// Use exponential backoff for reconnection attempts
if (millis() - _last_connect_attempt > _current_backoff_ms) {
attemptConnection();
}
break;
@ -91,8 +94,9 @@ void MQTTBridge::loop() {
case MQTTState::CONNECTED:
if (!_mqtt_client.connected()) {
_state = MQTTState::DISCONNECTED;
Serial.println("[MQTTS] Connection lost");
Serial.println("[MQTT] Connection lost");
_last_connect_attempt = millis();
// Don't reset backoff on connection loss - broker might be down
} else {
_mqtt_client.loop();
@ -104,7 +108,8 @@ void MQTTBridge::loop() {
break;
case MQTTState::ERROR:
if (millis() - _last_connect_attempt > 30000) {
// Use backoff for error recovery too
if (millis() - _last_connect_attempt > _current_backoff_ms) {
_state = MQTTState::DISCONNECTED;
}
break;
@ -125,7 +130,7 @@ void MQTTBridge::end() {
_state = MQTTState::DISCONNECTED;
_initialized = false;
Serial.println("[MQTTS] Stopped");
Serial.println("[MQTT] Stopped");
}
void MQTTBridge::attemptConnection() {
@ -134,7 +139,8 @@ void MQTTBridge::attemptConnection() {
_state = MQTTState::CONNECTING;
_last_connect_attempt = millis();
Serial.printf("[MQTTS] Connecting to %s:%d...\n", _config.broker, _config.port);
Serial.printf("[MQTT] Connecting to %s:%d (backoff=%lums)...\n",
_config.broker, _config.port, _current_backoff_ms);
String client_id = String(_config.client_id);
if (client_id.length() == 0) {
@ -157,15 +163,41 @@ void MQTTBridge::attemptConnection() {
_state = MQTTState::CONNECTED;
_connected_since = millis();
_reconnect_count++;
Serial.println("[MQTTS] Connected!");
// Reset backoff on successful connection
_current_backoff_ms = BACKOFF_MIN_MS;
Serial.println("[MQTT] Connected!");
subscribeToCommands();
publishStatus();
_last_status_publish = millis();
} else {
int rc = _mqtt_client.state();
Serial.printf("[MQTTS] Connection failed, rc=%d\n", rc);
const char* error_str = getMQTTErrorString(rc);
Serial.printf("[MQTT] Connection failed: %s (code %d)\n", error_str, rc);
Serial.printf("[MQTT] Broker: %s:%d, User: %s\n", _config.broker, _config.port,
strlen(_config.user) > 0 ? _config.user : "(none)");
_state = MQTTState::ERROR;
// Exponential backoff: double the delay (up to max)
_current_backoff_ms = min(_current_backoff_ms * BACKOFF_MULTIPLIER, BACKOFF_MAX_MS);
Serial.printf("[MQTTS] Next retry in %lums\n", _current_backoff_ms);
}
}
const char* MQTTBridge::getMQTTErrorString(int rc) {
// PubSubClient state() return codes
switch (rc) {
case -4: return "Connection timeout";
case -3: return "Connection lost";
case -2: return "Connect failed (network)";
case -1: return "Disconnected cleanly";
case 0: return "Connected";
case 1: return "Bad protocol version";
case 2: return "Client ID rejected";
case 3: return "Server unavailable";
case 4: return "Bad credentials (check username/password)";
case 5: return "Not authorized";
default: return "Unknown error";
}
}
@ -188,7 +220,7 @@ void MQTTBridge::subscribeToCommands() {
char topic[100];
snprintf(topic, sizeof(topic), "%s#", _topic_cmd_prefix);
_mqtt_client.subscribe(topic);
Serial.printf("[MQTTS] Subscribed to: %s\n", topic);
Serial.printf("[MQTT] Subscribed to: %s\n", topic);
}
void MQTTBridge::updateConfig(const MQTTConfig& config) {
@ -333,7 +365,7 @@ void MQTTBridge::publishMessage(const char* topic, const char* payload, bool ret
if (_mqtt_client.publish(topic, payload, retained)) {
_messages_sent++;
} else {
Serial.printf("[MQTTS] Publish failed to %s\n", topic);
Serial.printf("[MQTT] Publish failed to %s\n", topic);
}
}
@ -380,10 +412,10 @@ void MQTTBridge::handleMessage(char* topic, uint8_t* payload, unsigned int lengt
if (strncmp(topic, _topic_cmd_prefix, strlen(_topic_cmd_prefix)) == 0) {
const char* cmd = topic + strlen(_topic_cmd_prefix);
Serial.printf("[MQTTS] Command received: %s\n", cmd);
Serial.printf("[MQTT] Command received: %s\n", cmd);
if (strcmp(cmd, "reboot") == 0) {
Serial.println("[MQTTS] Reboot requested");
Serial.println("[MQTT] Reboot requested");
publishStatus();
delay(100);
ESP.restart();

View File

@ -85,6 +85,12 @@ private:
unsigned long _last_stats_publish;
unsigned long _connected_since;
// Exponential backoff for reconnection
static constexpr uint32_t BACKOFF_MIN_MS = 1000; // Start at 1 second
static constexpr uint32_t BACKOFF_MAX_MS = 60000; // Max 60 seconds
static constexpr uint32_t BACKOFF_MULTIPLIER = 2; // Double each attempt
uint32_t _current_backoff_ms;
uint32_t _messages_sent;
uint32_t _messages_received;
uint32_t _reconnect_count;
@ -111,6 +117,9 @@ private:
void publishMessage(const char* topic, const char* payload, bool retained = false);
void publishJson(const char* topic, JsonDocument& doc, bool retained = false);
// Error code translation for better diagnostics
static const char* getMQTTErrorString(int rc);
static uint32_t fnv1a_hash(const uint8_t* data, size_t len);
bool isDuplicate(const uint8_t* data, size_t len);

View File

@ -733,6 +733,21 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier
}
MyMesh::~MyMesh() {
// Clean up dynamically allocated resources
#ifdef WITH_MQTT
delete _web_config;
_web_config = nullptr;
delete _mqtt_bridge;
_mqtt_bridge = nullptr;
#endif
#ifdef WITH_ETHERNET
delete _mqtt_bridge;
_mqtt_bridge = nullptr;
#endif
}
void MyMesh::begin(FILESYSTEM *fs) {
mesh::Mesh::begin();
_fs = fs;
@ -748,9 +763,14 @@ void MyMesh::begin(FILESYSTEM *fs) {
}
#endif
// Initialize network + MQTT gateway
#ifdef WITH_NETWORK
initNetwork();
#ifdef WITH_MQTT
// Initialize MQTT gateway (WiFi)
initMQTT();
#endif
#ifdef WITH_ETHERNET
// Initialize MQTT gateway (Ethernet)
initEthernet();
#endif
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
@ -1091,9 +1111,59 @@ void MyMesh::loop() {
bridge.loop();
#endif
// Unified network/MQTT processing
#ifdef WITH_NETWORK
loopNetwork();
#ifdef WITH_MQTT
// Process WiFi, MQTT, and web server
_wifi_mgr.loop();
if (_mqtt_bridge) {
_mqtt_bridge->loop();
// Periodic stats publish (every 30 seconds)
if (_mqtt_bridge->isConnected() && millis() - _last_mqtt_stats > 30000) {
_mqtt_bridge->publishStats(
uptime_millis / 1000,
radio_driver.getPacketsRecv(),
radio_driver.getPacketsSent(),
getTotalAirTime() / 1000,
(int16_t)_radio->getNoiseFloor()
);
_last_mqtt_stats = millis();
// Update web config stats
if (_web_config) {
WebConfigStats stats;
stats.uptime_secs = uptime_millis / 1000;
stats.packets_rx = radio_driver.getPacketsRecv();
stats.packets_tx = radio_driver.getPacketsSent();
stats.air_time_secs = getTotalAirTime() / 1000;
stats.noise_floor = (int16_t)_radio->getNoiseFloor();
stats.last_rssi = (int16_t)radio_driver.getLastRSSI();
stats.last_snr = radio_driver.getLastSNR();
stats.tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF);
stats.batt_mv = board.getBattMilliVolts();
_web_config->updateStats(stats);
}
}
}
#endif
#ifdef WITH_ETHERNET
// Process Ethernet and MQTT
_eth_mgr.loop();
if (_mqtt_bridge) {
_mqtt_bridge->loop();
// Periodic stats publish (every 30 seconds)
if (_mqtt_bridge->isConnected() && millis() - _last_mqtt_stats > 30000) {
_mqtt_bridge->publishStats(
uptime_millis / 1000,
radio_driver.getPacketsRecv(),
radio_driver.getPacketsSent(),
getTotalAirTime() / 1000,
(int16_t)_radio->getNoiseFloor()
);
_last_mqtt_stats = millis();
}
}
#endif
mesh::Mesh::loop();
@ -1135,13 +1205,26 @@ void MyMesh::loop() {
last_millis = now;
}
// =============================================================================
// Unified Network/MQTT Support (replaces separate WITH_MQTT and WITH_ETHERNET blocks)
// =============================================================================
#ifdef WITH_NETWORK
#ifdef WITH_MQTT
// MQTT initialization and helper methods
void MyMesh::initNetwork() {
// Configure MQTT (common for both WiFi and Ethernet)
void MyMesh::initMQTT() {
Serial.println("[MQTT] Initializing WiFi + MQTT gateway...");
// Configure WiFi
memset(&_wifi_config, 0, sizeof(_wifi_config));
#ifdef MQTT_WIFI_SSID
strncpy(_wifi_config.ssid, MQTT_WIFI_SSID, sizeof(_wifi_config.ssid) - 1);
#endif
#ifdef MQTT_WIFI_PASS
strncpy(_wifi_config.password, MQTT_WIFI_PASS, sizeof(_wifi_config.password) - 1);
#endif
strncpy(_wifi_config.ap_ssid, "MeshCore", sizeof(_wifi_config.ap_ssid) - 1);
_wifi_config.connect_timeout_ms = 15000;
_wifi_config.reconnect_interval_ms = 10000;
_wifi_config.max_retries = 5;
// Configure MQTT
memset(&_mqtt_config, 0, sizeof(_mqtt_config));
#ifdef MQTT_BROKER
strncpy(_mqtt_config.broker, MQTT_BROKER, sizeof(_mqtt_config.broker) - 1);
@ -1165,39 +1248,82 @@ void MyMesh::initNetwork() {
strncpy(_mqtt_config.topic_prefix, "meshcore/repeater", sizeof(_mqtt_config.topic_prefix) - 1);
#endif
_mqtt_config.enabled = 1;
_mqtt_config.keepalive_secs = 60;
_mqtt_config.publish_interval_ms = 100;
// Initialize the appropriate network transport
#if defined(WITH_MQTT)
// WiFi transport
Serial.println("[NET] Initializing WiFi + MQTT gateway...");
memset(&_wifi_config, 0, sizeof(_wifi_config));
#ifdef MQTT_WIFI_SSID
strncpy(_wifi_config.ssid, MQTT_WIFI_SSID, sizeof(_wifi_config.ssid) - 1);
#endif
#ifdef MQTT_WIFI_PASS
strncpy(_wifi_config.password, MQTT_WIFI_PASS, sizeof(_wifi_config.password) - 1);
#endif
strncpy(_wifi_config.ap_ssid, "MeshCore", sizeof(_wifi_config.ap_ssid) - 1);
_wifi_config.connect_timeout_ms = 15000;
_wifi_config.reconnect_interval_ms = 10000;
_wifi_config.max_retries = 5;
#ifdef MQTT_USE_TLS
_mqtt_config.use_tls = MQTT_USE_TLS;
#else
_mqtt_config.use_tls = 0;
#endif
_mqtt_config.keepalive_secs = 60;
_mqtt_config.publish_interval_ms = 100;
// Start WiFi
_wifi_mgr.begin(_wifi_config);
_network_mgr = &_wifi_mgr;
#elif defined(WITH_ETHERNET)
// Ethernet transport
Serial.println("[NET] Initializing Ethernet + MQTT gateway...");
// Create MQTT bridge (pass pointer to INetworkManager interface)
_mqtt_bridge = new MQTTBridge(&_wifi_mgr, _mgr, &rtc_clock);
_mqtt_bridge->begin(_mqtt_config, self_id.pub_key);
// Create web config server
_web_config = new WebConfig(_wifi_mgr, *_mqtt_bridge);
_web_config->begin(&_wifi_config, &_mqtt_config, &_prefs);
_web_config->setNodeInfo(self_id.pub_key, _prefs.node_name, FIRMWARE_VERSION);
_web_config->setSaveCallback([]() {
// Note: In a real implementation, we'd save to preferences file
Serial.println("[WebConfig] Save callback triggered");
});
_web_config->setRebootCallback([]() {
ESP.restart();
});
_last_mqtt_stats = millis();
Serial.println("[MQTT] Gateway initialized");
}
bool MyMesh::isMQTTConnected() const {
return _mqtt_bridge && _mqtt_bridge->isConnected();
}
bool MyMesh::isWiFiConnected() const {
return _wifi_mgr.isConnected();
}
void MyMesh::setMQTTEnabled(bool enable) {
_mqtt_config.enabled = enable ? 1 : 0;
if (_mqtt_bridge) {
_mqtt_bridge->updateConfig(_mqtt_config);
}
}
const char* MyMesh::getMQTTStatus() const {
if (!_mqtt_bridge) return "not initialized";
if (!_mqtt_config.enabled) return "disabled";
if (_mqtt_bridge->isConnected()) return "connected";
return "disconnected";
}
const char* MyMesh::getWiFiStatus() const {
switch (_wifi_mgr.getState()) {
case NetworkState::CONNECTED: return "connected";
case NetworkState::CONNECTING: return "connecting";
case NetworkState::AP_MODE: return "ap_mode";
default: return "disconnected";
}
}
IPAddress MyMesh::getWiFiIP() const {
return _wifi_mgr.getLocalIP();
}
#endif // WITH_MQTT
#ifdef WITH_ETHERNET
// Ethernet initialization and helper methods
void MyMesh::initEthernet() {
Serial.println("[ETH] Initializing Ethernet + MQTT gateway...");
// Configure Ethernet (use defaults or build flags)
memset(&_eth_config, 0, sizeof(_eth_config));
#ifdef ETH_SPI_SCK
_eth_config.spi_sck = ETH_SPI_SCK;
@ -1223,68 +1349,52 @@ void MyMesh::initNetwork() {
_eth_config.reconnect_interval_ms = 5000;
_eth_config.use_dhcp = true;
_mqtt_config.use_tls = 0; // TLS not typically used with local Ethernet
_eth_mgr.begin(_eth_config);
_network_mgr = &_eth_mgr;
// Configure MQTT
memset(&_mqtt_config, 0, sizeof(_mqtt_config));
#ifdef MQTT_BROKER
strncpy(_mqtt_config.broker, MQTT_BROKER, sizeof(_mqtt_config.broker) - 1);
#else
strncpy(_mqtt_config.broker, "mqtt.example.com", sizeof(_mqtt_config.broker) - 1);
#endif
#ifdef MQTT_PORT
_mqtt_config.port = MQTT_PORT;
#else
_mqtt_config.port = 1883;
#endif
#ifdef MQTT_USER
strncpy(_mqtt_config.user, MQTT_USER, sizeof(_mqtt_config.user) - 1);
#endif
#ifdef MQTT_PASS
strncpy(_mqtt_config.password, MQTT_PASS, sizeof(_mqtt_config.password) - 1);
#endif
#ifdef MQTT_TOPIC_PREFIX
strncpy(_mqtt_config.topic_prefix, MQTT_TOPIC_PREFIX, sizeof(_mqtt_config.topic_prefix) - 1);
#else
strncpy(_mqtt_config.topic_prefix, "meshcore/repeater", sizeof(_mqtt_config.topic_prefix) - 1);
#endif
_mqtt_config.enabled = 1;
_mqtt_config.use_tls = 0; // TLS not typically used with local Ethernet
_mqtt_config.keepalive_secs = 60;
_mqtt_config.publish_interval_ms = 100;
// Create MQTT bridge using the unified network manager interface
_mqtt_bridge = new MQTTBridge(_network_mgr, _mgr, &rtc_clock);
// Start Ethernet
_eth_mgr.begin(_eth_config);
// Create MQTT bridge (pass pointer to INetworkManager interface)
_mqtt_bridge = new MQTTBridge(&_eth_mgr, _mgr, &rtc_clock);
_mqtt_bridge->begin(_mqtt_config, self_id.pub_key);
// WiFi-specific: Create web config server
#ifdef WITH_MQTT
_web_config = new WebConfig(_wifi_mgr, *_mqtt_bridge);
_web_config->begin(&_wifi_config, &_mqtt_config, &_prefs);
_web_config->setNodeInfo(self_id.pub_key, _prefs.node_name, FIRMWARE_VERSION);
_web_config->setSaveCallback([]() {
Serial.println("[WebConfig] Save callback triggered");
});
_web_config->setRebootCallback([]() {
ESP.restart();
});
#endif
_last_mqtt_stats = millis();
Serial.println("[NET] Gateway initialized");
Serial.println("[ETH] Gateway initialized");
}
void MyMesh::loopNetwork() {
// Process network manager (WiFi or Ethernet)
#ifdef WITH_MQTT
_wifi_mgr.loop();
// Note: WebConfig uses AsyncWebServer, no loop() needed
#endif
#ifdef WITH_ETHERNET
_eth_mgr.loop();
#endif
// Process MQTT bridge
if (_mqtt_bridge) {
_mqtt_bridge->loop();
// Periodic stats publishing
if (_mqtt_bridge->isConnected() && millis() - _last_mqtt_stats > 60000) {
uint32_t up_secs = uptime_millis / 1000;
uint32_t pkts_rx = radio_driver.getPacketsRecv();
uint32_t pkts_tx = radio_driver.getPacketsSent();
uint32_t air_time = getTotalAirTime() / 1000;
int16_t noise = (int16_t)_radio->getNoiseFloor();
_mqtt_bridge->publishStats(up_secs, pkts_rx, pkts_tx, air_time, noise);
_last_mqtt_stats = millis();
}
}
}
// Unified helper methods (no more duplication!)
bool MyMesh::isMQTTConnected() const {
return _mqtt_bridge && _mqtt_bridge->isConnected();
}
bool MyMesh::isNetworkConnected() const {
return _network_mgr && _network_mgr->isConnected();
bool MyMesh::isEthernetConnected() const {
return _eth_mgr.isConnected();
}
void MyMesh::setMQTTEnabled(bool enable) {
@ -1301,20 +1411,17 @@ const char* MyMesh::getMQTTStatus() const {
return "disconnected";
}
const char* MyMesh::getNetworkStatus() const {
if (!_network_mgr) return "not initialized";
switch (_network_mgr->getState()) {
const char* MyMesh::getEthernetStatus() const {
switch (_eth_mgr.getState()) {
case NetworkState::CONNECTED: return "connected";
case NetworkState::CONNECTING: return "connecting";
case NetworkState::AP_MODE: return "ap_mode";
case NetworkState::ERROR: return "error";
default: return "disconnected";
}
}
IPAddress MyMesh::getNetworkIP() const {
if (!_network_mgr) return IPAddress(0, 0, 0, 0);
return _network_mgr->getLocalIP();
IPAddress MyMesh::getEthernetIP() const {
return _eth_mgr.getLocalIP();
}
#endif // WITH_NETWORK
#endif // WITH_ETHERNET

View File

@ -23,20 +23,15 @@
#define WITH_BRIDGE
#endif
// Unified network/MQTT support
#if defined(WITH_MQTT) || defined(WITH_ETHERNET)
#define WITH_NETWORK // Common flag for network-enabled builds
#include "INetworkManager.h"
#include "MQTTBridge.h"
#endif
#ifdef WITH_MQTT
#include "WiFiManager.h"
#include "MQTTBridge.h"
#include "WebConfig.h"
#endif
#ifdef WITH_ETHERNET
#include "EthernetManager.h"
#include "MQTTBridge.h"
#endif
#include <helpers/AdvertDataHelpers.h>
@ -128,26 +123,25 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
ESPNowBridge bridge;
#endif
// Unified network/MQTT support
#ifdef WITH_NETWORK
INetworkManager* _network_mgr; // Points to either _wifi_mgr or _eth_mgr
MQTTBridge* _mqtt_bridge;
MQTTConfig _mqtt_config;
unsigned long _last_mqtt_stats;
void initNetwork(); // Common initialization
void loopNetwork(); // Common loop processing
#endif
#ifdef WITH_MQTT
WiFiManager _wifi_mgr;
WebConfig* _web_config;
MQTTBridge* _mqtt_bridge = nullptr;
WebConfig* _web_config = nullptr;
WiFiConfig _wifi_config;
MQTTConfig _mqtt_config;
unsigned long _last_mqtt_stats = 0;
void initMQTT();
#endif
#ifdef WITH_ETHERNET
EthernetManager _eth_mgr;
MQTTBridge* _mqtt_bridge = nullptr;
MQTTConfig _mqtt_config;
EthernetConfig _eth_config;
unsigned long _last_mqtt_stats = 0;
void initEthernet();
#endif
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
@ -202,6 +196,7 @@ protected:
public:
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
~MyMesh(); // Clean up dynamically allocated resources
void begin(FILESYSTEM* fs);
@ -244,28 +239,24 @@ public:
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
void loop();
// Unified network/MQTT gateway methods
#ifdef WITH_NETWORK
#ifdef WITH_MQTT
// MQTT gateway methods (WiFi)
bool isMQTTConnected() const;
bool isNetworkConnected() const;
bool isWiFiConnected() const;
void setMQTTEnabled(bool enable);
const char* getMQTTStatus() const;
const char* getNetworkStatus() const;
IPAddress getNetworkIP() const;
#endif
#ifdef WITH_MQTT
// WiFi-specific aliases for backward compatibility
bool isWiFiConnected() const { return isNetworkConnected(); }
const char* getWiFiStatus() const { return getNetworkStatus(); }
IPAddress getWiFiIP() const { return getNetworkIP(); }
const char* getWiFiStatus() const;
IPAddress getWiFiIP() const;
#endif
#ifdef WITH_ETHERNET
// Ethernet-specific aliases for backward compatibility
bool isEthernetConnected() const { return isNetworkConnected(); }
const char* getEthernetStatus() const { return getNetworkStatus(); }
IPAddress getEthernetIP() const { return getNetworkIP(); }
// MQTT gateway methods (Ethernet)
bool isMQTTConnected() const;
bool isEthernetConnected() const;
void setMQTTEnabled(bool enable);
const char* getMQTTStatus() const;
const char* getEthernetStatus() const;
IPAddress getEthernetIP() const;
#endif
#if defined(WITH_BRIDGE)

View File

@ -10,6 +10,7 @@ WiFiManager::WiFiManager()
_retry_count(0),
_initialized(false) {
memset(&_config, 0, sizeof(_config));
memset(_cached_ssid, 0, sizeof(_cached_ssid));
}
void WiFiManager::begin(const WiFiConfig& config) {
@ -82,6 +83,9 @@ void WiFiManager::loop() {
_state = WiFiState::CONNECTED;
_connected_since = millis();
_retry_count = 0;
// Cache SSID to avoid dangling pointer from WiFi.SSID().c_str()
strncpy(_cached_ssid, WiFi.SSID().c_str(), sizeof(_cached_ssid) - 1);
_cached_ssid[sizeof(_cached_ssid) - 1] = '\0';
Serial.printf("[WiFi] Connected! IP: %s, RSSI: %d dBm\n",
WiFi.localIP().toString().c_str(), WiFi.RSSI());
} else if (status == WL_NO_SSID_AVAIL) {
@ -184,6 +188,9 @@ void WiFiManager::startAPMode() {
if (success) {
_state = WiFiState::AP_MODE;
// Cache AP SSID to avoid dangling pointer
strncpy(_cached_ssid, ap_ssid.c_str(), sizeof(_cached_ssid) - 1);
_cached_ssid[sizeof(_cached_ssid) - 1] = '\0';
Serial.printf("[WiFi] AP started: SSID='%s', IP=%s\n",
ap_ssid.c_str(), WiFi.softAPIP().toString().c_str());
} else {
@ -221,10 +228,8 @@ int32_t WiFiManager::getRSSI() const {
}
const char* WiFiManager::getSSID() const {
if (_state == WiFiState::CONNECTED) {
return WiFi.SSID().c_str();
} else if (_state == WiFiState::AP_MODE) {
return WiFi.softAPSSID().c_str();
if (_state == WiFiState::CONNECTED || _state == WiFiState::AP_MODE) {
return _cached_ssid;
}
return "";
}
@ -264,10 +269,8 @@ NetworkState WiFiManager::getState() const {
}
const char* WiFiManager::getConnectionName() const {
if (_state == WiFiState::CONNECTED) {
return WiFi.SSID().c_str();
} else if (_state == WiFiState::AP_MODE) {
return WiFi.softAPSSID().c_str();
if (_state == WiFiState::CONNECTED || _state == WiFiState::AP_MODE) {
return _cached_ssid;
}
return "Not connected";
}

View File

@ -61,6 +61,7 @@ private:
unsigned long _connected_since;
uint8_t _retry_count;
bool _initialized;
char _cached_ssid[33]; // Cached SSID to avoid dangling pointer from WiFi.SSID().c_str()
void attemptConnection();
void checkConnection();