Enhance MQTT implementation by adding optional SNMP support, allowing for monitoring and management of MQTT statistics. Update configuration options to include SNMP settings, and modify the MQTTBridge to integrate SNMP agent functionality. Additionally, improve documentation for the MQTT observer setup and expand the number of concurrent MQTT connection slots from 3 to 5, enhancing overall flexibility and performance.
This commit is contained in:
1 parent
cc17c6b745
commit
202acacfe1
13 files changed
+706
-96
No files matched your search
@@ -159,7 +159,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
remaining -= to_read;
|
||||
}
|
||||
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
|
||||
// next: 291
|
||||
file.read((uint8_t *)&_prefs->snmp_enabled, sizeof(_prefs->snmp_enabled)); // 291
|
||||
file.read((uint8_t *)&_prefs->snmp_community, sizeof(_prefs->snmp_community)); // 292
|
||||
// next: 316
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -189,6 +191,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||
|
||||
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
|
||||
_prefs->snmp_enabled = constrain(_prefs->snmp_enabled, 0, 1);
|
||||
_prefs->snmp_community[sizeof(_prefs->snmp_community) - 1] = '\0'; // ensure null terminated
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -274,7 +278,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
remaining -= to_write;
|
||||
}
|
||||
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
|
||||
// next: 291
|
||||
file.write((uint8_t *)&_prefs->snmp_enabled, sizeof(_prefs->snmp_enabled)); // 291
|
||||
file.write((uint8_t *)&_prefs->snmp_community, sizeof(_prefs->snmp_community)); // 292
|
||||
// next: 316
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -304,11 +310,7 @@ static void setMQTTPrefsDefaults(MQTTPrefs* prefs) {
|
||||
strncpy(prefs->mqtt_slot_preset[i], "none", sizeof(prefs->mqtt_slot_preset[i]) - 1);
|
||||
prefs->mqtt_slot_preset[i][sizeof(prefs->mqtt_slot_preset[i]) - 1] = '\0';
|
||||
}
|
||||
#ifdef MQTT_WIFI_POWER_SAVE_DEFAULT
|
||||
prefs->wifi_power_save = MQTT_WIFI_POWER_SAVE_DEFAULT; // 0=min, 1=none, 2=max
|
||||
#else
|
||||
prefs->wifi_power_save = 0; // Default to WIFI_PS_MIN_MODEM (0=min)
|
||||
#endif
|
||||
prefs->wifi_power_save = 1; // Default to none (0=min, 1=none, 2=max)
|
||||
// String fields are already zero-initialized by memset
|
||||
}
|
||||
|
||||
@@ -714,6 +716,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
strcpy(reply, "> strict");
|
||||
}
|
||||
} else if (memcmp(config, "snmp.community", 14) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->snmp_community);
|
||||
} else if (memcmp(config, "snmp", 4) == 0 && (config[4] == '\0' || config[4] == '\n' || config[4] == '\r')) {
|
||||
strcpy(reply, _prefs->snmp_enabled ? "> on" : "> off");
|
||||
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
|
||||
sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
|
||||
} else if (memcmp(config, "freq", 4) == 0) {
|
||||
@@ -1104,6 +1110,14 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
} else if (memcmp(config, "snmp.community ", 15) == 0) {
|
||||
StrHelper::strncpy(_prefs->snmp_community, &config[15], sizeof(_prefs->snmp_community));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - restart to apply");
|
||||
} else if (memcmp(config, "snmp ", 5) == 0) {
|
||||
_prefs->snmp_enabled = memcmp(&config[5], "on", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - restart to apply");
|
||||
} else if (memcmp(config, "tx ", 3) == 0) {
|
||||
_prefs->tx_power_dbm = atoi(&config[3]);
|
||||
savePrefs();
|
||||
|
||||
@@ -72,7 +72,7 @@ struct NodePrefs { // persisted to file
|
||||
// WiFi settings
|
||||
char wifi_ssid[32]; // WiFi SSID
|
||||
char wifi_password[64]; // WiFi password
|
||||
uint8_t wifi_power_save; // WiFi power save mode: 0=min, 1=none, 2=max (default: 0=min)
|
||||
uint8_t wifi_power_save; // WiFi power save mode: 0=min, 1=none, 2=max (default: 1=none)
|
||||
|
||||
// Timezone settings
|
||||
char timezone_string[32]; // Timezone string (e.g., "America/Los_Angeles")
|
||||
@@ -96,6 +96,10 @@ struct NodePrefs { // persisted to file
|
||||
char mqtt_slot_topic[MAX_MQTT_SLOTS][96]; // Per-slot custom topic template (custom preset only)
|
||||
|
||||
uint8_t loop_detect;
|
||||
|
||||
// SNMP settings (optional, only used when WITH_SNMP is defined)
|
||||
uint8_t snmp_enabled; // boolean: 0=off, 1=on
|
||||
char snmp_community[24]; // community string (default "public")
|
||||
};
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
@@ -137,7 +141,7 @@ struct MQTTPrefs {
|
||||
// WiFi settings
|
||||
char wifi_ssid[32]; // WiFi SSID
|
||||
char wifi_password[64]; // WiFi password
|
||||
uint8_t wifi_power_save; // WiFi power save mode: 0=min, 1=none, 2=max (default: 0=min)
|
||||
uint8_t wifi_power_save; // WiFi power save mode: 0=min, 1=none, 2=max (default: 1=none)
|
||||
|
||||
// Timezone settings
|
||||
char timezone_string[32]; // Timezone string (e.g., "America/Los_Angeles")
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#ifdef WITH_SNMP
|
||||
|
||||
#include "SNMPAgent.h"
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
#define SNMP_PORT 161
|
||||
|
||||
MeshSNMPAgent::MeshSNMPAgent()
|
||||
: _snmp("public"),
|
||||
_running(false),
|
||||
_uptime_secs(0),
|
||||
_packets_recv(0), _packets_sent(0), _recv_errors(0),
|
||||
_noise_floor(0), _last_rssi(0), _last_snr(0),
|
||||
_sent_flood(0), _sent_direct(0), _recv_flood(0), _recv_direct(0),
|
||||
_total_air_time_secs(0),
|
||||
_mqtt_connected_slots(0), _mqtt_queue_depth(0), _mqtt_skipped_publishes(0),
|
||||
_free_heap(0), _max_alloc(0), _internal_free(0), _psram_free(0),
|
||||
_wifi_rssi(0)
|
||||
{
|
||||
_firmware_version[0] = '\0';
|
||||
_node_name[0] = '\0';
|
||||
}
|
||||
|
||||
void MeshSNMPAgent::begin(const char* community) {
|
||||
if (_running) return;
|
||||
|
||||
_snmp = SNMPAgent(community);
|
||||
_snmp.setUDP(&_udp);
|
||||
_snmp.begin();
|
||||
|
||||
// System group (.1.x.0)
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".1.1.0", &_uptime_secs);
|
||||
_snmp.addReadOnlyStaticStringHandler(MESHCORE_OID_BASE ".1.2.0", _firmware_version, sizeof(_firmware_version));
|
||||
_snmp.addReadOnlyStaticStringHandler(MESHCORE_OID_BASE ".1.3.0", _node_name, sizeof(_node_name));
|
||||
|
||||
// Radio group (.2.x.0)
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.1.0", &_packets_recv);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.2.0", &_packets_sent);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.3.0", &_recv_errors);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.4.0", &_noise_floor);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.5.0", &_last_rssi);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.6.0", &_last_snr);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.7.0", &_sent_flood);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.8.0", &_sent_direct);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.9.0", &_recv_flood);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.10.0", &_recv_direct);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".2.11.0", &_total_air_time_secs);
|
||||
|
||||
// MQTT group (.3.x.0)
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".3.1.0", &_mqtt_connected_slots);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".3.2.0", &_mqtt_queue_depth);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".3.3.0", &_mqtt_skipped_publishes);
|
||||
|
||||
// Memory group (.4.x.0)
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".4.1.0", &_free_heap);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".4.2.0", &_max_alloc);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".4.3.0", &_internal_free);
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".4.4.0", &_psram_free);
|
||||
|
||||
// Network group (.5.x.0)
|
||||
_snmp.addIntegerHandler(MESHCORE_OID_BASE ".5.1.0", &_wifi_rssi);
|
||||
|
||||
_snmp.sortHandlers();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
void MeshSNMPAgent::loop() {
|
||||
if (!_running) return;
|
||||
|
||||
// Update memory and network stats locally (we're on Core 0 with WiFi)
|
||||
_free_heap = (int)ESP.getFreeHeap();
|
||||
_max_alloc = (int)ESP.getMaxAllocHeap();
|
||||
_internal_free = (int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#ifdef BOARD_HAS_PSRAM
|
||||
_psram_free = (int)heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
#else
|
||||
_psram_free = 0;
|
||||
#endif
|
||||
|
||||
if (WiFi.isConnected()) {
|
||||
_wifi_rssi = (int)WiFi.RSSI();
|
||||
}
|
||||
|
||||
_snmp.loop();
|
||||
}
|
||||
|
||||
void MeshSNMPAgent::updateRadioStats(
|
||||
uint32_t packets_recv, uint32_t packets_sent, uint32_t recv_errors,
|
||||
int16_t noise_floor, int16_t last_rssi, int16_t last_snr,
|
||||
uint32_t sent_flood, uint32_t sent_direct,
|
||||
uint32_t recv_flood, uint32_t recv_direct,
|
||||
uint32_t total_air_time_secs, uint32_t uptime_secs) {
|
||||
_packets_recv = (int)packets_recv;
|
||||
_packets_sent = (int)packets_sent;
|
||||
_recv_errors = (int)recv_errors;
|
||||
_noise_floor = (int)noise_floor;
|
||||
_last_rssi = (int)last_rssi;
|
||||
_last_snr = (int)last_snr;
|
||||
_sent_flood = (int)sent_flood;
|
||||
_sent_direct = (int)sent_direct;
|
||||
_recv_flood = (int)recv_flood;
|
||||
_recv_direct = (int)recv_direct;
|
||||
_total_air_time_secs = (int)total_air_time_secs;
|
||||
_uptime_secs = (int)uptime_secs;
|
||||
}
|
||||
|
||||
void MeshSNMPAgent::updateMQTTStats(int connected_slots, int queue_depth, int skipped_publishes) {
|
||||
_mqtt_connected_slots = connected_slots;
|
||||
_mqtt_queue_depth = queue_depth;
|
||||
_mqtt_skipped_publishes = skipped_publishes;
|
||||
}
|
||||
|
||||
void MeshSNMPAgent::setNodeName(const char* name) {
|
||||
strncpy(_node_name, name, sizeof(_node_name) - 1);
|
||||
_node_name[sizeof(_node_name) - 1] = '\0';
|
||||
}
|
||||
|
||||
void MeshSNMPAgent::setFirmwareVersion(const char* version) {
|
||||
strncpy(_firmware_version, version, sizeof(_firmware_version) - 1);
|
||||
_firmware_version[sizeof(_firmware_version) - 1] = '\0';
|
||||
}
|
||||
|
||||
#endif // WITH_SNMP
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_SNMP
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <SNMP_Agent.h>
|
||||
|
||||
// Temporary private enterprise OID base — replace with registered PEN when available.
|
||||
// All MeshCore OIDs live under this subtree.
|
||||
#define MESHCORE_OID_BASE ".1.3.6.1.4.1.99999"
|
||||
|
||||
// OID layout:
|
||||
// .1.x.0 = system (uptime, version, node name)
|
||||
// .2.x.0 = radio (packets, RSSI, SNR, noise floor, air time)
|
||||
// .3.x.0 = mqtt (connected slots, queue depth, skipped publishes)
|
||||
// .4.x.0 = memory (free heap, max alloc, internal free, PSRAM free)
|
||||
// .5.x.0 = network (WiFi RSSI)
|
||||
|
||||
class MeshSNMPAgent {
|
||||
public:
|
||||
MeshSNMPAgent();
|
||||
void begin(const char* community);
|
||||
void loop();
|
||||
|
||||
// Called from the mesh task (Core 1) to push fresh stats into SNMP-visible variables.
|
||||
// Copies are atomic for 32-bit aligned ints on ESP32, so no mutex needed.
|
||||
void updateRadioStats(uint32_t packets_recv, uint32_t packets_sent, uint32_t recv_errors,
|
||||
int16_t noise_floor, int16_t last_rssi, int16_t last_snr,
|
||||
uint32_t sent_flood, uint32_t sent_direct,
|
||||
uint32_t recv_flood, uint32_t recv_direct,
|
||||
uint32_t total_air_time_secs, uint32_t uptime_secs);
|
||||
|
||||
void updateMQTTStats(int connected_slots, int queue_depth, int skipped_publishes);
|
||||
|
||||
void setNodeName(const char* name);
|
||||
void setFirmwareVersion(const char* version);
|
||||
|
||||
bool isRunning() const { return _running; }
|
||||
|
||||
private:
|
||||
WiFiUDP _udp;
|
||||
SNMPAgent _snmp;
|
||||
bool _running;
|
||||
|
||||
// System OIDs
|
||||
int _uptime_secs;
|
||||
char _firmware_version[32];
|
||||
char _node_name[32];
|
||||
|
||||
// Radio OIDs
|
||||
int _packets_recv;
|
||||
int _packets_sent;
|
||||
int _recv_errors;
|
||||
int _noise_floor;
|
||||
int _last_rssi;
|
||||
int _last_snr;
|
||||
int _sent_flood;
|
||||
int _sent_direct;
|
||||
int _recv_flood;
|
||||
int _recv_direct;
|
||||
int _total_air_time_secs;
|
||||
|
||||
// MQTT OIDs
|
||||
int _mqtt_connected_slots;
|
||||
int _mqtt_queue_depth;
|
||||
int _mqtt_skipped_publishes;
|
||||
|
||||
// Memory OIDs (updated in loop() since we're on Core 0 with WiFi)
|
||||
int _free_heap;
|
||||
int _max_alloc;
|
||||
int _internal_free;
|
||||
int _psram_free;
|
||||
|
||||
// Network OIDs
|
||||
int _wifi_rssi;
|
||||
};
|
||||
|
||||
#endif // WITH_SNMP
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <WiFiUdp.h>
|
||||
#include <Timezone.h>
|
||||
|
||||
#ifdef WITH_SNMP
|
||||
#include "../SNMPAgent.h"
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_heap_caps.h>
|
||||
@@ -168,8 +172,12 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc
|
||||
_cached_has_connected_slots(false),
|
||||
_last_memory_check(0), _skipped_publishes(0), _last_fragmentation_recovery(0),
|
||||
_fragmentation_pressure_since(0), _last_critical_check_run(0),
|
||||
_last_no_broker_log(0), _last_config_warning(0),
|
||||
_last_no_broker_log(0), _queue_disconnected_since(0), _all_tripped_since(0),
|
||||
_last_config_warning(0),
|
||||
_dispatcher(nullptr), _radio(nullptr), _board(nullptr), _ms(nullptr),
|
||||
#ifdef WITH_SNMP
|
||||
_snmp_agent(nullptr),
|
||||
#endif
|
||||
_last_wifi_check(0), _last_wifi_status(WL_DISCONNECTED), _wifi_status_initialized(false),
|
||||
_wifi_disconnected_time(0), _last_wifi_reconnect_attempt(0), _wifi_reconnect_backoff_attempt(0)
|
||||
#ifdef ESP_PLATFORM
|
||||
@@ -673,6 +681,25 @@ void MQTTBridge::mqttTaskLoop() {
|
||||
// Process packet queue
|
||||
processPacketQueue();
|
||||
|
||||
#ifdef WITH_SNMP
|
||||
// SNMP agent loop — process incoming UDP requests
|
||||
if (_snmp_agent) {
|
||||
if (!_snmp_agent->isRunning() && WiFi.isConnected() && _prefs->snmp_enabled) {
|
||||
_snmp_agent->begin(_prefs->snmp_community);
|
||||
MQTT_DEBUG_PRINTLN("SNMP agent started on port 161 (community: %s)", _prefs->snmp_community);
|
||||
}
|
||||
if (_snmp_agent->isRunning()) {
|
||||
// Update MQTT stats from this core
|
||||
int connected = 0;
|
||||
for (int i = 0; i < MAX_MQTT_SLOTS; i++) {
|
||||
if (_slots[i].enabled && _slots[i].connected) connected++;
|
||||
}
|
||||
_snmp_agent->updateMQTTStats(connected, _queue_count, _skipped_publishes);
|
||||
_snmp_agent->loop();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Periodic configuration check (throttled to avoid spam)
|
||||
checkConfigurationMismatch();
|
||||
|
||||
@@ -937,6 +964,9 @@ void MQTTBridge::maintainSlotConnections() {
|
||||
// Only allow one reconnect attempt per maintenance cycle to avoid
|
||||
// multiple simultaneous TLS handshakes blocking the network stack
|
||||
bool reconnect_attempted_this_cycle = false;
|
||||
// Only allow one full teardown+setup per cycle to limit heap fragmentation
|
||||
// when multiple slots fail simultaneously
|
||||
bool teardown_attempted_this_cycle = false;
|
||||
|
||||
for (int i = 0; i < MAX_MQTT_SLOTS; i++) {
|
||||
if (!_slots[i].enabled || !_slots[i].client) continue;
|
||||
@@ -946,11 +976,11 @@ void MQTTBridge::maintainSlotConnections() {
|
||||
continue;
|
||||
}
|
||||
|
||||
maintainSlotConnection(i, now_millis, current_time, time_synced, reconnect_attempted_this_cycle);
|
||||
maintainSlotConnection(i, now_millis, current_time, time_synced, reconnect_attempted_this_cycle, teardown_attempted_this_cycle);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, unsigned long current_time, bool time_synced, bool& reconnect_attempted) {
|
||||
void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, unsigned long current_time, bool time_synced, bool& reconnect_attempted, bool& teardown_attempted) {
|
||||
MQTTSlot& slot = _slots[index];
|
||||
|
||||
if (slot.connected) {
|
||||
@@ -1037,13 +1067,23 @@ void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, uns
|
||||
}
|
||||
slot.client->connect();
|
||||
} else {
|
||||
// Token expired or near expiry — full teardown for fresh TLS + new token
|
||||
bool saved_tripped = slot.circuit_breaker_tripped;
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d token expired/near expiry, full teardown+setup for probe", index + 1);
|
||||
teardownSlot(index);
|
||||
setupSlot(index);
|
||||
_slots[index].circuit_breaker_tripped = saved_tripped;
|
||||
_slots[index].last_reconnect_attempt = now_millis;
|
||||
// Token expired — regenerate token but avoid teardown+setup
|
||||
// which would allocate new TLS context on potentially fragmented heap
|
||||
if (slot.client) {
|
||||
if (createSlotAuthToken(index)) {
|
||||
slot.client->setCredentials(_jwt_username, slot.auth_token);
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d circuit breaker probe (regenerated expired token)", index + 1);
|
||||
}
|
||||
slot.client->connect();
|
||||
} else {
|
||||
// Client was destroyed — must do full setup
|
||||
bool saved_tripped = slot.circuit_breaker_tripped;
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d circuit breaker probe (full setup, no client)", index + 1);
|
||||
teardownSlot(index);
|
||||
setupSlot(index);
|
||||
_slots[index].circuit_breaker_tripped = saved_tripped;
|
||||
_slots[index].last_reconnect_attempt = now_millis;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
slot.client->connect();
|
||||
@@ -1094,6 +1134,12 @@ void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, uns
|
||||
slot.client->reconnect();
|
||||
} else {
|
||||
// Full teardown: token expired/near expiry, or lightweight reconnect failed (backoff 3+)
|
||||
if (teardown_attempted) {
|
||||
// Defer to next cycle to limit heap fragmentation from simultaneous teardowns
|
||||
slot.last_reconnect_attempt = now_millis;
|
||||
return;
|
||||
}
|
||||
teardown_attempted = true;
|
||||
uint8_t saved_backoff = slot.reconnect_backoff;
|
||||
uint8_t saved_failures = slot.max_backoff_failures;
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d full teardown+setup for reconnect (backoff %d)", index + 1, saved_backoff);
|
||||
@@ -1110,6 +1156,11 @@ void MQTTBridge::maintainSlotConnection(int index, unsigned long now_millis, uns
|
||||
slot.client->reconnect();
|
||||
} else {
|
||||
// Full teardown after lightweight reconnect failed (backoff 3+)
|
||||
if (teardown_attempted) {
|
||||
slot.last_reconnect_attempt = now_millis;
|
||||
return;
|
||||
}
|
||||
teardown_attempted = true;
|
||||
uint8_t saved_backoff = slot.reconnect_backoff;
|
||||
uint8_t saved_failures = slot.max_backoff_failures;
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d full teardown+setup for reconnect (backoff %d)", index + 1, saved_backoff);
|
||||
@@ -1769,6 +1820,7 @@ void MQTTBridge::processPacketQueue() {
|
||||
_queue_count = uxQueueMessagesWaiting(_packet_queue_handle);
|
||||
|
||||
if (_queue_count == 0) {
|
||||
_queue_disconnected_since = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1782,10 +1834,21 @@ void MQTTBridge::processPacketQueue() {
|
||||
MQTT_DEBUG_PRINTLN("Queue has %d packets but no slots connected", _queue_count);
|
||||
_last_no_broker_log = now;
|
||||
}
|
||||
// Flush stale packets after extended disconnect
|
||||
if (_queue_disconnected_since == 0) {
|
||||
_queue_disconnected_since = now;
|
||||
} else if ((now - _queue_disconnected_since) >= QUEUE_STALE_MS) {
|
||||
QueuedPacket discard;
|
||||
while (xQueueReceive(_packet_queue_handle, &discard, 0) == pdTRUE) {}
|
||||
_queue_count = 0;
|
||||
MQTT_DEBUG_PRINTLN("Flushed stale packet queue after %lu ms disconnected", now - _queue_disconnected_since);
|
||||
_queue_disconnected_since = now;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_queue_disconnected_since = 0;
|
||||
_last_no_broker_log = 0;
|
||||
|
||||
// Adaptive drain: burst-process when queue has backlog, gentle otherwise
|
||||
@@ -2273,6 +2336,31 @@ void MQTTBridge::runCriticalMemoryCheckAndRecovery() {
|
||||
MQTT_DEBUG_PRINTLN("Fragmentation recovery: recreating MQTT clients (max_alloc=%d, pressure %lu min)", (int)max_alloc, (unsigned long)(required_window_ms / 60000));
|
||||
recreateMqttClientsForFragmentationRecovery();
|
||||
}
|
||||
|
||||
// Last resort: if ALL enabled slots have circuit breakers tripped for >1 hour,
|
||||
// heap is likely too fragmented for TLS to ever succeed — reboot.
|
||||
bool all_tripped = true;
|
||||
int enabled_count = 0;
|
||||
for (int i = 0; i < MAX_MQTT_SLOTS; i++) {
|
||||
if (_slots[i].enabled) {
|
||||
enabled_count++;
|
||||
if (!_slots[i].circuit_breaker_tripped) {
|
||||
all_tripped = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (enabled_count > 0 && all_tripped) {
|
||||
if (_all_tripped_since == 0) {
|
||||
_all_tripped_since = now;
|
||||
} else if ((now - _all_tripped_since) >= 3600000UL) {
|
||||
MQTT_DEBUG_PRINTLN("All MQTT slots circuit-breaker tripped for >1 hour. Restarting ESP.");
|
||||
delay(100);
|
||||
ESP.restart();
|
||||
}
|
||||
} else {
|
||||
_all_tripped_since = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "helpers/JWTHelper.h"
|
||||
#include "helpers/MQTTPresets.h"
|
||||
|
||||
#ifdef WITH_SNMP
|
||||
class MeshSNMPAgent; // Forward declaration
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
@@ -186,6 +190,17 @@ private:
|
||||
// Cached connection status (updated in callbacks to avoid redundant checks)
|
||||
bool _cached_has_connected_slots;
|
||||
|
||||
// Queue staleness tracking
|
||||
unsigned long _queue_disconnected_since; // 0 = has connected slots
|
||||
static const unsigned long QUEUE_STALE_MS = 300000UL; // Flush queue after 5 min disconnected
|
||||
|
||||
// Recovery: restart ESP after prolonged total failure
|
||||
unsigned long _all_tripped_since; // 0 = not all tripped
|
||||
|
||||
#ifdef WITH_SNMP
|
||||
MeshSNMPAgent* _snmp_agent;
|
||||
#endif
|
||||
|
||||
// Throttle logging
|
||||
unsigned long _last_no_broker_log;
|
||||
static const unsigned long NO_BROKER_LOG_INTERVAL = 30000; // Log every 30 seconds max
|
||||
@@ -216,7 +231,7 @@ private:
|
||||
void setupSlot(int index); // Create/destroy client for a slot based on its preset
|
||||
void teardownSlot(int index); // Disconnect and free slot resources
|
||||
void maintainSlotConnections(); // Maintain all slot connections (token renewal, reconnect)
|
||||
void maintainSlotConnection(int index, unsigned long now_millis, unsigned long current_time, bool time_synced, bool& reconnect_attempted);
|
||||
void maintainSlotConnection(int index, unsigned long now_millis, unsigned long current_time, bool time_synced, bool& reconnect_attempted, bool& teardown_attempted);
|
||||
bool createSlotAuthToken(int index); // Create/renew JWT token for a slot
|
||||
bool publishToSlot(int index, const char* topic, const char* payload, bool retained = false);
|
||||
bool publishToAllSlots(const char* topic, const char* payload, bool retained = false);
|
||||
@@ -304,6 +319,10 @@ public:
|
||||
|
||||
void setStatsSources(mesh::Dispatcher* dispatcher, mesh::Radio* radio,
|
||||
mesh::MainBoard* board, mesh::MillisecondClock* ms);
|
||||
|
||||
#ifdef WITH_SNMP
|
||||
void setSNMPAgent(MeshSNMPAgent* agent) { _snmp_agent = agent; }
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user