refactor(mqtt): migrate observer settings to MQTTPrefs structure
Refactored the handling of observer-related settings by moving them from NodePrefs to a new MQTTPrefs structure. This change centralizes MQTT, WiFi, timezone, SNMP, and alert configurations, improving code organization and maintainability. The new structure allows for better separation of concerns and prepares the codebase for future enhancements.
This commit is contained in:
1 parent
9535f243af
commit
2eb41baeda
14 files changed
+372
-561
No files matched your search
@@ -951,21 +951,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
_prefs.agc_reset_interval = 7; // 28 seconds (secs/4) — prevents AGC drift on long-running observers
|
_prefs.agc_reset_interval = 7; // 28 seconds (secs/4) — prevents AGC drift on long-running observers
|
||||||
#endif
|
#endif
|
||||||
_prefs.radio_watchdog_minutes = 5; // 5 minutes default
|
// Observer defaults (radio_watchdog, alert.*, snmp.*) moved to applyMQTTDefaults()
|
||||||
|
// in MQTTDefaults.h — they live in /mqtt_prefs now, not NodePrefs.
|
||||||
// Alert channel defaults — disabled by default, and the channel is left
|
|
||||||
// unconfigured so a freshly-flashed observer never broadcasts on the
|
|
||||||
// well-known Public hashtag. Operators must explicitly pick a private
|
|
||||||
// key (`set alert.psk`) or a hashtag (`set alert.hashtag`) before alerts
|
|
||||||
// can fire. The sender prefix on outgoing alert messages is always the
|
|
||||||
// node name (`set name ...`), so there's no separate `alert.name`.
|
|
||||||
_prefs.alert_enabled = 0;
|
|
||||||
_prefs.alert_psk_hex[0] = '\0';
|
|
||||||
_prefs.alert_hashtag[0] = '\0';
|
|
||||||
_prefs.alert_region[0] = '\0'; // empty = use default_scope
|
|
||||||
_prefs.alert_wifi_minutes = 30; // 30 minutes
|
|
||||||
_prefs.alert_mqtt_minutes = 240; // 4 hours
|
|
||||||
_prefs.alert_min_interval_min = 60; // re-arm window: 1 hour
|
|
||||||
|
|
||||||
// bridge defaults
|
// bridge defaults
|
||||||
_prefs.bridge_enabled = 1; // enabled
|
_prefs.bridge_enabled = 1; // enabled
|
||||||
@@ -976,21 +963,12 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
|
|
||||||
StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret));
|
StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret));
|
||||||
|
|
||||||
// SNMP defaults
|
|
||||||
_prefs.snmp_enabled = 0;
|
|
||||||
StrHelper::strncpy(_prefs.snmp_community, "public", sizeof(_prefs.snmp_community));
|
|
||||||
|
|
||||||
// GPS defaults
|
// GPS defaults
|
||||||
_prefs.gps_enabled = 0;
|
_prefs.gps_enabled = 0;
|
||||||
_prefs.gps_interval = 0;
|
_prefs.gps_interval = 0;
|
||||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||||
|
|
||||||
// MQTT slot/IATA/timezone defaults come from /mqtt_prefs via loadPrefs (see MQTTDefaults.h)
|
// MQTT/WiFi/timezone/radio_watchdog defaults live in /mqtt_prefs now (see applyMQTTDefaults).
|
||||||
_prefs.mqtt_origin[0] = '\0';
|
|
||||||
|
|
||||||
// WiFi defaults (user-configured via CLI; placeholders until set)
|
|
||||||
StrHelper::strncpy(_prefs.wifi_ssid, "ssid_here", sizeof(_prefs.wifi_ssid));
|
|
||||||
StrHelper::strncpy(_prefs.wifi_password, "password_here", sizeof(_prefs.wifi_password));
|
|
||||||
|
|
||||||
_prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier
|
_prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier
|
||||||
|
|
||||||
@@ -1042,7 +1020,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
if (_prefs.bridge_enabled) {
|
if (_prefs.bridge_enabled) {
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
// Defer construction to avoid static init crashes on ESP32 classic
|
// Defer construction to avoid static init crashes on ESP32 classic
|
||||||
bridge = new MQTTBridge(&_prefs, _mgr, getRTCClock(), &self_id);
|
bridge = new MQTTBridge(&_prefs, _cli.getObserverPrefs(), _mgr, getRTCClock(), &self_id);
|
||||||
#endif
|
#endif
|
||||||
if (bridge) {
|
if (bridge) {
|
||||||
// Set device public key for MQTT topics
|
// Set device public key for MQTT topics
|
||||||
@@ -1065,7 +1043,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
// Set stats sources for automatic stats collection
|
// Set stats sources for automatic stats collection
|
||||||
bridge->setStatsSources(this, _radio, _cli.getBoard(), _ms);
|
bridge->setStatsSources(this, _radio, _cli.getBoard(), _ms);
|
||||||
#ifdef WITH_SNMP
|
#ifdef WITH_SNMP
|
||||||
if (_prefs.snmp_enabled) {
|
if (_cli.getObserverPrefs()->snmp_enabled) {
|
||||||
_snmp_agent.setNodeName(_prefs.node_name);
|
_snmp_agent.setNodeName(_prefs.node_name);
|
||||||
_snmp_agent.setFirmwareVersion(getFirmwareVer());
|
_snmp_agent.setFirmwareVersion(getFirmwareVer());
|
||||||
bridge->setSNMPAgent(&_snmp_agent);
|
bridge->setSNMPAgent(&_snmp_agent);
|
||||||
@@ -1082,8 +1060,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
// Passing `this` as the callbacks lets the reporter resolve a TransportKey
|
// Passing `this` as the callbacks lets the reporter resolve a TransportKey
|
||||||
// scope (alert.region override, falling back to default_scope) so alert
|
// scope (alert.region override, falling back to default_scope) so alert
|
||||||
// floods ride the same scope as adverts/channel messages.
|
// floods ride the same scope as adverts/channel messages.
|
||||||
_alerter.begin(&_prefs, this, this);
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
#if defined(WITH_MQTT_BRIDGE)
|
_alerter.begin(&_prefs, _cli.getObserverPrefs(), this, this);
|
||||||
_alerter.setBridge(bridge);
|
_alerter.setBridge(bridge);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1118,12 +1096,15 @@ bool MyMesh::resolveAlertScope(TransportKey& dest) {
|
|||||||
// RegionMap so the operator can name a region that doesn't exist yet
|
// RegionMap so the operator can name a region that doesn't exist yet
|
||||||
// without polluting region_map state — we just silently fall through
|
// without polluting region_map state — we just silently fall through
|
||||||
// to default_scope on miss.
|
// to default_scope on miss.
|
||||||
if (_prefs.alert_region[0]) {
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
auto r = region_map.findByNamePrefix(_prefs.alert_region);
|
const char* alert_region = _cli.getObserverPrefs()->alert_region;
|
||||||
|
if (alert_region[0]) {
|
||||||
|
auto r = region_map.findByNamePrefix(alert_region);
|
||||||
if (r && region_map.getTransportKeysFor(*r, &dest, 1) > 0 && !dest.isNull()) {
|
if (r && region_map.getTransportKeysFor(*r, &dest, 1) > 0 && !dest.isNull()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (!default_scope.isNull()) {
|
if (!default_scope.isNull()) {
|
||||||
dest = default_scope;
|
dest = default_scope;
|
||||||
return true;
|
return true;
|
||||||
@@ -1473,7 +1454,9 @@ void MyMesh::loop() {
|
|||||||
uptime_millis += now - last_millis;
|
uptime_millis += now - last_millis;
|
||||||
last_millis = now;
|
last_millis = now;
|
||||||
|
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
_alerter.onLoop(now);
|
_alerter.onLoop(now);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_SNMP
|
#ifdef WITH_SNMP
|
||||||
// Push radio stats to SNMP agent every 2 seconds
|
// Push radio stats to SNMP agent every 2 seconds
|
||||||
|
|||||||
@@ -132,7 +132,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
#ifdef WITH_SNMP
|
#ifdef WITH_SNMP
|
||||||
MeshSNMPAgent _snmp_agent;
|
MeshSNMPAgent _snmp_agent;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
AlertReporter _alerter;
|
AlertReporter _alerter;
|
||||||
|
#endif
|
||||||
|
|
||||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||||
@@ -168,9 +170,11 @@ protected:
|
|||||||
int getAGCResetInterval() const override {
|
int getAGCResetInterval() const override {
|
||||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||||
}
|
}
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
uint32_t getRadioWatchdogMillis() const override {
|
uint32_t getRadioWatchdogMillis() const override {
|
||||||
return ((uint32_t)_prefs.radio_watchdog_minutes) * 60000UL;
|
return ((uint32_t)_cli.getObserverPrefs()->radio_watchdog_minutes) * 60000UL;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
uint8_t getExtraAckTransmitCount() const override {
|
uint8_t getExtraAckTransmitCount() const override {
|
||||||
return _prefs.multi_acks;
|
return _prefs.multi_acks;
|
||||||
}
|
}
|
||||||
@@ -215,8 +219,10 @@ public:
|
|||||||
// CommonCLICallbacks
|
// CommonCLICallbacks
|
||||||
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
|
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
|
||||||
|
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
void onAlertConfigChanged() override { _alerter.onConfigChanged(); }
|
void onAlertConfigChanged() override { _alerter.onConfigChanged(); }
|
||||||
bool sendAlertText(const char* text) override { return _alerter.sendText(text); }
|
bool sendAlertText(const char* text) override { return _alerter.sendText(text); }
|
||||||
|
#endif
|
||||||
bool resolveAlertScope(TransportKey& dest) override;
|
bool resolveAlertScope(TransportKey& dest) override;
|
||||||
bool formatFileSystem() override;
|
bool formatFileSystem() override;
|
||||||
void sendSelfAdvertisement(int delay_millis, bool flood) override;
|
void sendSelfAdvertisement(int delay_millis, bool flood) override;
|
||||||
@@ -253,7 +259,7 @@ public:
|
|||||||
void setBridgeState(bool enable) override {
|
void setBridgeState(bool enable) override {
|
||||||
if (!bridge) {
|
if (!bridge) {
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
bridge = new MQTTBridge(&_prefs, _mgr, getRTCClock(), &self_id);
|
bridge = new MQTTBridge(&_prefs, _cli.getObserverPrefs(), _mgr, getRTCClock(), &self_id);
|
||||||
#endif
|
#endif
|
||||||
if (!bridge) return;
|
if (!bridge) return;
|
||||||
}
|
}
|
||||||
@@ -303,8 +309,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void restartBridgeSlot(int slot) override {
|
void restartBridgeSlot(int slot) override {
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
if (!bridge || !bridge->isRunning()) return;
|
if (!bridge || !bridge->isRunning()) return;
|
||||||
bridge->setSlotPreset(slot, _prefs.mqtt_slot_preset[slot]);
|
bridge->setSlotPreset(slot, _cli.getObserverPrefs()->mqtt_slot_preset[slot]);
|
||||||
|
#else
|
||||||
|
(void)slot;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule the pull-OTA flash to run from loop() in ~2.5 s, leaving time for the
|
// Schedule the pull-OTA flash to run from loop() in ~2.5 s, leaving time for the
|
||||||
|
|||||||
@@ -681,15 +681,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
_prefs.gps_interval = 0;
|
_prefs.gps_interval = 0;
|
||||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||||
|
|
||||||
// Alert channel defaults (same as repeater; off by default and unconfigured).
|
// Observer defaults (alert.*, etc.) moved to applyMQTTDefaults() — they live
|
||||||
// Operator must pick `set alert.psk` or `set alert.hashtag` before alerts fire.
|
// in /mqtt_prefs now, not NodePrefs.
|
||||||
_prefs.alert_enabled = 0;
|
|
||||||
_prefs.alert_psk_hex[0] = '\0';
|
|
||||||
_prefs.alert_hashtag[0] = '\0';
|
|
||||||
_prefs.alert_region[0] = '\0';
|
|
||||||
_prefs.alert_wifi_minutes = 30;
|
|
||||||
_prefs.alert_mqtt_minutes = 240;
|
|
||||||
_prefs.alert_min_interval_min = 60;
|
|
||||||
|
|
||||||
// bridge defaults (same as repeater)
|
// bridge defaults (same as repeater)
|
||||||
_prefs.bridge_enabled = 1; // enabled
|
_prefs.bridge_enabled = 1; // enabled
|
||||||
@@ -698,12 +691,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
_prefs.bridge_baud = 115200; // baud rate
|
_prefs.bridge_baud = 115200; // baud rate
|
||||||
_prefs.bridge_channel = 1; // channel 1
|
_prefs.bridge_channel = 1; // channel 1
|
||||||
|
|
||||||
// MQTT slot/IATA/timezone defaults come from /mqtt_prefs via loadPrefs (see MQTTDefaults.h)
|
// MQTT/WiFi/timezone defaults live in /mqtt_prefs now (see applyMQTTDefaults).
|
||||||
_prefs.mqtt_origin[0] = '\0';
|
|
||||||
|
|
||||||
// WiFi defaults (user-configured via CLI; placeholders until set)
|
|
||||||
StrHelper::strncpy(_prefs.wifi_ssid, "ssid_here", sizeof(_prefs.wifi_ssid));
|
|
||||||
StrHelper::strncpy(_prefs.wifi_password, "password_here", sizeof(_prefs.wifi_password));
|
|
||||||
|
|
||||||
next_post_idx = 0;
|
next_post_idx = 0;
|
||||||
next_client_idx = 0;
|
next_client_idx = 0;
|
||||||
@@ -757,7 +745,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
if (_prefs.bridge_enabled) {
|
if (_prefs.bridge_enabled) {
|
||||||
// Defer construction to avoid static init crashes on ESP32 classic
|
// Defer construction to avoid static init crashes on ESP32 classic
|
||||||
bridge = new MQTTBridge(&_prefs, _mgr, getRTCClock(), &self_id);
|
bridge = new MQTTBridge(&_prefs, _cli.getObserverPrefs(), _mgr, getRTCClock(), &self_id);
|
||||||
if (bridge) {
|
if (bridge) {
|
||||||
// Set device public key for MQTT topics
|
// Set device public key for MQTT topics
|
||||||
char device_id[65];
|
char device_id[65];
|
||||||
@@ -787,8 +775,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
// Passing `this` as the callbacks lets the reporter resolve a TransportKey
|
// Passing `this` as the callbacks lets the reporter resolve a TransportKey
|
||||||
// scope (alert.region override, falling back to default_scope) so alert
|
// scope (alert.region override, falling back to default_scope) so alert
|
||||||
// floods ride the same scope as adverts/channel messages.
|
// floods ride the same scope as adverts/channel messages.
|
||||||
_alerter.begin(&_prefs, this, this);
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
#if defined(WITH_MQTT_BRIDGE)
|
_alerter.begin(&_prefs, _cli.getObserverPrefs(), this, this);
|
||||||
_alerter.setBridge(bridge);
|
_alerter.setBridge(bridge);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -806,12 +794,15 @@ void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3
|
|||||||
|
|
||||||
bool MyMesh::resolveAlertScope(TransportKey& dest) {
|
bool MyMesh::resolveAlertScope(TransportKey& dest) {
|
||||||
// Same resolution policy as simple_repeater: alert.region > default_scope.
|
// Same resolution policy as simple_repeater: alert.region > default_scope.
|
||||||
if (_prefs.alert_region[0]) {
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
auto r = region_map.findByNamePrefix(_prefs.alert_region);
|
const char* alert_region = _cli.getObserverPrefs()->alert_region;
|
||||||
|
if (alert_region[0]) {
|
||||||
|
auto r = region_map.findByNamePrefix(alert_region);
|
||||||
if (r && region_map.getTransportKeysFor(*r, &dest, 1) > 0 && !dest.isNull()) {
|
if (r && region_map.getTransportKeysFor(*r, &dest, 1) > 0 && !dest.isNull()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (!default_scope.isNull()) {
|
if (!default_scope.isNull()) {
|
||||||
dest = default_scope;
|
dest = default_scope;
|
||||||
return true;
|
return true;
|
||||||
@@ -1131,5 +1122,7 @@ void MyMesh::loop() {
|
|||||||
uptime_millis += now - last_millis;
|
uptime_millis += now - last_millis;
|
||||||
last_millis = now;
|
last_millis = now;
|
||||||
|
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
_alerter.onLoop(now);
|
_alerter.onLoop(now);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
MQTTBridge* bridge;
|
MQTTBridge* bridge;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
AlertReporter _alerter;
|
AlertReporter _alerter;
|
||||||
|
#endif
|
||||||
|
|
||||||
void addPost(ClientInfo* client, const char* postData);
|
void addPost(ClientInfo* client, const char* postData);
|
||||||
void pushPostToClient(ClientInfo* client, PostInfo& post);
|
void pushPostToClient(ClientInfo* client, PostInfo& post);
|
||||||
@@ -201,8 +203,10 @@ public:
|
|||||||
// CommonCLICallbacks
|
// CommonCLICallbacks
|
||||||
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
|
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
|
||||||
|
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
void onAlertConfigChanged() override { _alerter.onConfigChanged(); }
|
void onAlertConfigChanged() override { _alerter.onConfigChanged(); }
|
||||||
bool sendAlertText(const char* text) override { return _alerter.sendText(text); }
|
bool sendAlertText(const char* text) override { return _alerter.sendText(text); }
|
||||||
|
#endif
|
||||||
bool resolveAlertScope(TransportKey& dest) override;
|
bool resolveAlertScope(TransportKey& dest) override;
|
||||||
bool formatFileSystem() override;
|
bool formatFileSystem() override;
|
||||||
void sendSelfAdvertisement(int delay_millis, bool flood) override;
|
void sendSelfAdvertisement(int delay_millis, bool flood) override;
|
||||||
@@ -241,7 +245,7 @@ public:
|
|||||||
void setBridgeState(bool enable) override {
|
void setBridgeState(bool enable) override {
|
||||||
if (!bridge) {
|
if (!bridge) {
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
bridge = new MQTTBridge(&_prefs, _mgr, getRTCClock(), &self_id);
|
bridge = new MQTTBridge(&_prefs, _cli.getObserverPrefs(), _mgr, getRTCClock(), &self_id);
|
||||||
#endif
|
#endif
|
||||||
if (!bridge) return;
|
if (!bridge) return;
|
||||||
}
|
}
|
||||||
@@ -289,8 +293,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void restartBridgeSlot(int slot) override {
|
void restartBridgeSlot(int slot) override {
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
if (!bridge || !bridge->isRunning()) return;
|
if (!bridge || !bridge->isRunning()) return;
|
||||||
bridge->setSlotPreset(slot, _prefs.mqtt_slot_preset[slot]);
|
bridge->setSlotPreset(slot, _cli.getObserverPrefs()->mqtt_slot_preset[slot]);
|
||||||
|
#else
|
||||||
|
(void)slot;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int getQueueSize() override {
|
int getQueueSize() override {
|
||||||
|
|||||||
+6
-4
@@ -39,9 +39,11 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
|
|||||||
return 4000; // 4 seconds
|
return 4000; // 4 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
uint32_t Dispatcher::getRadioWatchdogMillis() const {
|
uint32_t Dispatcher::getRadioWatchdogMillis() const {
|
||||||
return RADIO_WATCHDOG_MS;
|
return RADIO_WATCHDOG_MS;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void Dispatcher::loop() {
|
void Dispatcher::loop() {
|
||||||
if (millisHasNowPassed(next_floor_calib_time)) {
|
if (millisHasNowPassed(next_floor_calib_time)) {
|
||||||
@@ -63,10 +65,9 @@ void Dispatcher::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Radio watchdog: detect radio stuck in RX mode but not receiving any packets.
|
// Radio watchdog: detect radio stuck in RX mode but not receiving any packets.
|
||||||
// Use a composite "last radio activity" timestamp: the most recent of any valid RX,
|
// Observer-only feature (gated behind WITH_MQTT_BRIDGE); configured via the
|
||||||
// any ISR event (even CRC errors), or any successful TX. This prevents false firings
|
// MQTTPrefs radio_watchdog_minutes setting.
|
||||||
// in quiet mesh environments where packets may be more than RADIO_WATCHDOG_MS apart,
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
// while still catching a truly stuck radio (PSRAM starvation → missed ISR → no activity).
|
|
||||||
{
|
{
|
||||||
const uint32_t watchdog_ms = getRadioWatchdogMillis();
|
const uint32_t watchdog_ms = getRadioWatchdogMillis();
|
||||||
if (watchdog_ms > 0) {
|
if (watchdog_ms > 0) {
|
||||||
@@ -87,6 +88,7 @@ void Dispatcher::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif // WITH_MQTT_BRIDGE (radio watchdog)
|
||||||
|
|
||||||
if (outbound) { // waiting for outbound send to be completed
|
if (outbound) { // waiting for outbound send to be completed
|
||||||
if (_radio->isSendComplete()) {
|
if (_radio->isSendComplete()) {
|
||||||
|
|||||||
+3
-1
@@ -180,7 +180,9 @@ protected:
|
|||||||
virtual uint32_t getCADFailMaxDuration() const;
|
virtual uint32_t getCADFailMaxDuration() const;
|
||||||
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
||||||
virtual int getAGCResetInterval() const { return 0; } // disabled by default
|
virtual int getAGCResetInterval() const { return 0; } // disabled by default
|
||||||
virtual uint32_t getRadioWatchdogMillis() const;
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
|
virtual uint32_t getRadioWatchdogMillis() const; // observer-only radio recovery
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void begin();
|
void begin();
|
||||||
|
|||||||
@@ -26,8 +26,9 @@
|
|||||||
#define ALERT_DEBUG_PRINTLN(...) do {} while (0)
|
#define ALERT_DEBUG_PRINTLN(...) do {} while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
AlertReporter::AlertReporter()
|
AlertReporter::AlertReporter()
|
||||||
: _prefs(nullptr), _mesh(nullptr), _callbacks(nullptr),
|
: _prefs(nullptr), _obs(nullptr), _mesh(nullptr), _callbacks(nullptr),
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
_bridge(nullptr),
|
_bridge(nullptr),
|
||||||
#endif
|
#endif
|
||||||
@@ -38,18 +39,18 @@ AlertReporter::AlertReporter()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlertReporter::begin(NodePrefs* prefs, mesh::Mesh* mesh, CommonCLICallbacks* callbacks) {
|
void AlertReporter::begin(NodePrefs* prefs, MQTTPrefs* obs, mesh::Mesh* mesh, CommonCLICallbacks* callbacks) {
|
||||||
_prefs = prefs;
|
_prefs = prefs;
|
||||||
|
_obs = obs;
|
||||||
_mesh = mesh;
|
_mesh = mesh;
|
||||||
_callbacks = callbacks;
|
_callbacks = callbacks;
|
||||||
onConfigChanged();
|
onConfigChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
|
||||||
void AlertReporter::setBridge(MQTTBridge* bridge) {
|
void AlertReporter::setBridge(MQTTBridge* bridge) {
|
||||||
_bridge = bridge;
|
_bridge = bridge;
|
||||||
}
|
}
|
||||||
#endif
|
#endif // WITH_MQTT_BRIDGE (AlertReporter methods, part 1)
|
||||||
|
|
||||||
// Channels banned as fault-alert destinations. Fault alerts are noisy
|
// Channels banned as fault-alert destinations. Fault alerts are noisy
|
||||||
// operator-infrastructure messages; routing them to community channels would
|
// operator-infrastructure messages; routing them to community channels would
|
||||||
@@ -97,6 +98,7 @@ const char* alertReporterBannedChannelMatchHex(const char* psk_hex) {
|
|||||||
return alertReporterBannedChannelMatch(secret);
|
return alertReporterBannedChannelMatch(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
bool AlertReporter::resolveChannel(mesh::GroupChannel& out) const {
|
bool AlertReporter::resolveChannel(mesh::GroupChannel& out) const {
|
||||||
if (!_prefs) return false;
|
if (!_prefs) return false;
|
||||||
|
|
||||||
@@ -105,7 +107,7 @@ bool AlertReporter::resolveChannel(mesh::GroupChannel& out) const {
|
|||||||
// Only 16-byte secrets (32 hex chars) are supported; 32-byte channel keys
|
// Only 16-byte secrets (32 hex chars) are supported; 32-byte channel keys
|
||||||
// are not used anywhere in MeshCore practice and not represented in the
|
// are not used anywhere in MeshCore practice and not represented in the
|
||||||
// banned table either.
|
// banned table either.
|
||||||
const char* psk = _prefs->alert_psk_hex;
|
const char* psk = _obs->alert_psk_hex;
|
||||||
if (strlen(psk) != 32) return false;
|
if (strlen(psk) != 32) return false;
|
||||||
|
|
||||||
memset(out.secret, 0, sizeof(out.secret));
|
memset(out.secret, 0, sizeof(out.secret));
|
||||||
@@ -204,7 +206,7 @@ void AlertReporter::formatAge(unsigned long age_ms, char* out, size_t out_size)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AlertReporter::onLoop(unsigned long now_ms) {
|
void AlertReporter::onLoop(unsigned long now_ms) {
|
||||||
if (!_prefs || !_prefs->alert_enabled) return;
|
if (!_prefs || !_obs->alert_enabled) return;
|
||||||
if (!_mesh) return;
|
if (!_mesh) return;
|
||||||
|
|
||||||
// Throttle: ~5 s cadence. The thresholds are minutes-scale so this is fine.
|
// Throttle: ~5 s cadence. The thresholds are minutes-scale so this is fine.
|
||||||
@@ -216,17 +218,17 @@ void AlertReporter::onLoop(unsigned long now_ms) {
|
|||||||
// already enforces this on set, but a stale prefs file or future field
|
// already enforces this on set, but a stale prefs file or future field
|
||||||
// tweak shouldn't be able to drag the floor below 1 hour and let a
|
// tweak shouldn't be able to drag the floor below 1 hour and let a
|
||||||
// flapping link spam the mesh.
|
// flapping link spam the mesh.
|
||||||
uint16_t cfg_min = _prefs->alert_min_interval_min;
|
uint16_t cfg_min = _obs->alert_min_interval_min;
|
||||||
if (cfg_min < 60) cfg_min = 60;
|
if (cfg_min < 60) cfg_min = 60;
|
||||||
unsigned long min_interval_ms = (unsigned long)cfg_min * 60000UL;
|
unsigned long min_interval_ms = (unsigned long)cfg_min * 60000UL;
|
||||||
|
|
||||||
// -------- WiFi fault --------
|
// -------- WiFi fault --------
|
||||||
if (_prefs->alert_wifi_minutes > 0) {
|
if (_obs->alert_wifi_minutes > 0) {
|
||||||
unsigned long wifi_disc_ms = MQTTBridge::getLastWifiDisconnectTime();
|
unsigned long wifi_disc_ms = MQTTBridge::getLastWifiDisconnectTime();
|
||||||
unsigned long wifi_conn_ms = MQTTBridge::getWifiConnectedAtMillis();
|
unsigned long wifi_conn_ms = MQTTBridge::getWifiConnectedAtMillis();
|
||||||
bool wifi_down = (wifi_disc_ms != 0 && wifi_conn_ms == 0);
|
bool wifi_down = (wifi_disc_ms != 0 && wifi_conn_ms == 0);
|
||||||
unsigned long down_ms = wifi_down ? (now_ms - wifi_disc_ms) : 0;
|
unsigned long down_ms = wifi_down ? (now_ms - wifi_disc_ms) : 0;
|
||||||
unsigned long thresh_ms = (unsigned long)_prefs->alert_wifi_minutes * 60000UL;
|
unsigned long thresh_ms = (unsigned long)_obs->alert_wifi_minutes * 60000UL;
|
||||||
|
|
||||||
if (_wifi.state == OK) {
|
if (_wifi.state == OK) {
|
||||||
if (wifi_down && down_ms >= thresh_ms &&
|
if (wifi_down && down_ms >= thresh_ms &&
|
||||||
@@ -263,10 +265,10 @@ void AlertReporter::onLoop(unsigned long now_ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- MQTT slot faults --------
|
// -------- MQTT slot faults --------
|
||||||
if (_prefs->alert_mqtt_minutes > 0 && _bridge != nullptr) {
|
if (_obs->alert_mqtt_minutes > 0 && _bridge != nullptr) {
|
||||||
int n = MQTTBridge::getRuntimeSlotCount();
|
int n = MQTTBridge::getRuntimeSlotCount();
|
||||||
if (n > (int)(sizeof(_mqtt) / sizeof(_mqtt[0]))) n = (int)(sizeof(_mqtt) / sizeof(_mqtt[0]));
|
if (n > (int)(sizeof(_mqtt) / sizeof(_mqtt[0]))) n = (int)(sizeof(_mqtt) / sizeof(_mqtt[0]));
|
||||||
unsigned long thresh_ms = (unsigned long)_prefs->alert_mqtt_minutes * 60000UL;
|
unsigned long thresh_ms = (unsigned long)_obs->alert_mqtt_minutes * 60000UL;
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
Fault& f = _mqtt[i];
|
Fault& f = _mqtt[i];
|
||||||
@@ -311,3 +313,4 @@ void AlertReporter::onLoop(unsigned long now_ms) {
|
|||||||
(void)now_ms;
|
(void)now_ms;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif // WITH_MQTT_BRIDGE (AlertReporter methods, part 2)
|
||||||
@@ -49,6 +49,7 @@ const char* alertReporterBannedChannelMatchHex(const char* psk_hex);
|
|||||||
* - WiFi/MQTT polling is #ifdef WITH_MQTT_BRIDGE-gated; without it, the
|
* - WiFi/MQTT polling is #ifdef WITH_MQTT_BRIDGE-gated; without it, the
|
||||||
* reporter still supports manual `alert test` sends.
|
* reporter still supports manual `alert test` sends.
|
||||||
*/
|
*/
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
class AlertReporter {
|
class AlertReporter {
|
||||||
public:
|
public:
|
||||||
AlertReporter();
|
AlertReporter();
|
||||||
@@ -59,7 +60,7 @@ public:
|
|||||||
* it to resolve a TransportKey scope for outgoing alert floods (so the
|
* it to resolve a TransportKey scope for outgoing alert floods (so the
|
||||||
* packet rides the repeater's default scope or an `alert.region` override).
|
* packet rides the repeater's default scope or an `alert.region` override).
|
||||||
*/
|
*/
|
||||||
void begin(NodePrefs* prefs, mesh::Mesh* mesh, CommonCLICallbacks* callbacks = nullptr);
|
void begin(NodePrefs* prefs, MQTTPrefs* obs, mesh::Mesh* mesh, CommonCLICallbacks* callbacks = nullptr);
|
||||||
|
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
/** Bridge can be (re)created lazily; pass nullptr to detach. */
|
/** Bridge can be (re)created lazily; pass nullptr to detach. */
|
||||||
@@ -98,6 +99,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
NodePrefs* _prefs;
|
NodePrefs* _prefs;
|
||||||
|
MQTTPrefs* _obs;
|
||||||
mesh::Mesh* _mesh;
|
mesh::Mesh* _mesh;
|
||||||
CommonCLICallbacks* _callbacks;
|
CommonCLICallbacks* _callbacks;
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
@@ -107,3 +109,4 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
unsigned long _next_check_ms;
|
unsigned long _next_check_ms;
|
||||||
};
|
};
|
||||||
|
#endif // WITH_MQTT_BRIDGE
|
||||||
+26
-231
@@ -18,20 +18,6 @@
|
|||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
#include "bridges/MQTTBridge.h"
|
#include "bridges/MQTTBridge.h"
|
||||||
#include "MQTTDefaults.h"
|
#include "MQTTDefaults.h"
|
||||||
|
|
||||||
// Helper function to calculate total size of MQTT fields for file format compatibility
|
|
||||||
// Uses NodePrefs struct to get accurate field sizes
|
|
||||||
static size_t getMQTTFieldsSize(const NodePrefs* prefs) {
|
|
||||||
return sizeof(prefs->mqtt_origin) + sizeof(prefs->mqtt_iata) +
|
|
||||||
sizeof(prefs->mqtt_status_enabled) + sizeof(prefs->mqtt_packets_enabled) +
|
|
||||||
sizeof(prefs->mqtt_raw_enabled) + sizeof(prefs->mqtt_tx_enabled) +
|
|
||||||
sizeof(prefs->mqtt_status_interval) + sizeof(prefs->wifi_ssid) +
|
|
||||||
sizeof(prefs->wifi_password) + sizeof(prefs->timezone_string) +
|
|
||||||
sizeof(prefs->timezone_offset) + sizeof(prefs->mqtt_slot_preset) +
|
|
||||||
sizeof(prefs->mqtt_slot_host) + sizeof(prefs->mqtt_slot_port) +
|
|
||||||
sizeof(prefs->mqtt_slot_username) + sizeof(prefs->mqtt_slot_password) +
|
|
||||||
sizeof(prefs->mqtt_owner_public_key) + sizeof(prefs->mqtt_email);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Believe it or not, this std C function is busted on some platforms!
|
// Believe it or not, this std C function is busted on some platforms!
|
||||||
@@ -71,11 +57,11 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
|||||||
_prefs->bridge_pkt_src = 1; // Default to RX (logRx) for new installs
|
_prefs->bridge_pkt_src = 1; // Default to RX (logRx) for new installs
|
||||||
}
|
}
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
// Load MQTT preferences from separate file
|
// Load observer preferences (MQTT/WiFi/timezone/SNMP/alert) from /mqtt_prefs.
|
||||||
|
// Readers (MQTTBridge, AlertReporter, observer CLI) use _mqtt_prefs directly —
|
||||||
|
// these fields no longer exist in NodePrefs, so there is nothing to sync.
|
||||||
loadMQTTPrefs(fs);
|
loadMQTTPrefs(fs);
|
||||||
// Sync MQTT prefs to NodePrefs so existing code (like MQTTBridge) can access them
|
|
||||||
syncMQTTPrefsToNodePrefs();
|
|
||||||
|
|
||||||
// For MQTT bridge, migrate bridge.source to RX (logRx) only on fresh installs or upgrades
|
// For MQTT bridge, migrate bridge.source to RX (logRx) only on fresh installs or upgrades
|
||||||
// so legacy "tx" is not the default. mqtt.rx / mqtt.tx are separate (fresh default: advert for TX)
|
// so legacy "tx" is not the default. mqtt.rx / mqtt.tx are separate (fresh default: advert for TX)
|
||||||
if ((is_fresh_install || is_upgrade) && _prefs->bridge_pkt_src == 0) {
|
if ((is_fresh_install || is_upgrade) && _prefs->bridge_pkt_src == 0) {
|
||||||
@@ -140,84 +126,20 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
|||||||
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||||
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||||
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||||
// MQTT settings - skip reading from main prefs file (now stored separately)
|
// MQTT/observer settings are no longer stored in /com_prefs — they live in
|
||||||
// For backward compatibility, we'll skip these bytes if they exist in old files
|
// /mqtt_prefs (loaded by loadMQTTPrefs). The legacy zero-gap and trailing
|
||||||
// The actual MQTT prefs will be loaded from /mqtt_prefs in loadMQTTPrefs()
|
// snmp/alert block that older firmware wrote here are gone; on upgrade those
|
||||||
// Skip MQTT fields for file format compatibility (whether MQTT bridge is enabled or not)
|
// bytes are simply not read, so rx_boosted_gain/flood_max_* (which followed the
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
// gap) reset once and self-heal on the next save.
|
||||||
size_t mqtt_fields_size = getMQTTFieldsSize(_prefs);
|
if (file.available() >= (int)sizeof(_prefs->rx_boosted_gain)) {
|
||||||
#else
|
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain));
|
||||||
// If MQTT bridge not enabled, still skip these fields for file format compatibility
|
|
||||||
size_t mqtt_fields_size =
|
|
||||||
sizeof(_prefs->mqtt_origin) + sizeof(_prefs->mqtt_iata) +
|
|
||||||
sizeof(_prefs->mqtt_status_enabled) + sizeof(_prefs->mqtt_packets_enabled) +
|
|
||||||
sizeof(_prefs->mqtt_raw_enabled) + sizeof(_prefs->mqtt_tx_enabled) +
|
|
||||||
sizeof(_prefs->mqtt_status_interval) + sizeof(_prefs->wifi_ssid) +
|
|
||||||
sizeof(_prefs->wifi_password) + sizeof(_prefs->timezone_string) +
|
|
||||||
sizeof(_prefs->timezone_offset) + sizeof(_prefs->mqtt_slot_preset) +
|
|
||||||
sizeof(_prefs->mqtt_slot_host) + sizeof(_prefs->mqtt_slot_port) +
|
|
||||||
sizeof(_prefs->mqtt_slot_username) + sizeof(_prefs->mqtt_slot_password) +
|
|
||||||
sizeof(_prefs->mqtt_owner_public_key) + sizeof(_prefs->mqtt_email);
|
|
||||||
#endif
|
|
||||||
uint8_t skip_buffer[512]; // Large enough buffer
|
|
||||||
size_t remaining = mqtt_fields_size;
|
|
||||||
while (remaining > 0) {
|
|
||||||
size_t to_read = remaining > sizeof(skip_buffer) ? sizeof(skip_buffer) : remaining;
|
|
||||||
file.read(skip_buffer, to_read);
|
|
||||||
remaining -= to_read;
|
|
||||||
}
|
}
|
||||||
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
|
if (file.available() >= (int)sizeof(_prefs->flood_max_unscoped)) {
|
||||||
{
|
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped));
|
||||||
// Tail layout (current): 291-292 flood_max_*, 293+ snmp/alert fields.
|
|
||||||
// Legacy flex-branch files stored snmp at 291 without flood_max_* fields.
|
|
||||||
uint8_t byte291, byte292;
|
|
||||||
file.read(&byte291, 1);
|
|
||||||
file.read(&byte292, 1);
|
|
||||||
if (byte291 <= 1 && byte292 > 64) {
|
|
||||||
_prefs->snmp_enabled = byte291;
|
|
||||||
_prefs->snmp_community[0] = (char)byte292;
|
|
||||||
file.read((uint8_t *)&_prefs->snmp_community[1], sizeof(_prefs->snmp_community) - 1);
|
|
||||||
} else {
|
|
||||||
_prefs->flood_max_unscoped = byte291;
|
|
||||||
_prefs->flood_max_advert = byte292;
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->snmp_enabled)) {
|
|
||||||
file.read((uint8_t *)&_prefs->snmp_enabled, sizeof(_prefs->snmp_enabled)); // 293
|
|
||||||
}
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->snmp_community)) {
|
|
||||||
file.read((uint8_t *)&_prefs->snmp_community, sizeof(_prefs->snmp_community)); // 294
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (file.available() >= (int)sizeof(_prefs->radio_watchdog_minutes)) {
|
if (file.available() >= (int)sizeof(_prefs->flood_max_advert)) {
|
||||||
file.read((uint8_t *)&_prefs->radio_watchdog_minutes, sizeof(_prefs->radio_watchdog_minutes)); // 318
|
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert));
|
||||||
}
|
}
|
||||||
// Alert channel fields (appended; older files won't have them — defaults from MyMesh ctor remain)
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->alert_enabled)) {
|
|
||||||
file.read((uint8_t *)&_prefs->alert_enabled, sizeof(_prefs->alert_enabled));
|
|
||||||
}
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->alert_psk_hex)) {
|
|
||||||
file.read((uint8_t *)&_prefs->alert_psk_hex, sizeof(_prefs->alert_psk_hex));
|
|
||||||
}
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->alert_wifi_minutes)) {
|
|
||||||
file.read((uint8_t *)&_prefs->alert_wifi_minutes, sizeof(_prefs->alert_wifi_minutes));
|
|
||||||
}
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->alert_mqtt_minutes)) {
|
|
||||||
file.read((uint8_t *)&_prefs->alert_mqtt_minutes, sizeof(_prefs->alert_mqtt_minutes));
|
|
||||||
}
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->alert_min_interval_min)) {
|
|
||||||
file.read((uint8_t *)&_prefs->alert_min_interval_min, sizeof(_prefs->alert_min_interval_min));
|
|
||||||
}
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->alert_hashtag)) {
|
|
||||||
file.read((uint8_t *)&_prefs->alert_hashtag, sizeof(_prefs->alert_hashtag));
|
|
||||||
}
|
|
||||||
if (file.available() >= (int)sizeof(_prefs->alert_region)) {
|
|
||||||
file.read((uint8_t *)&_prefs->alert_region, sizeof(_prefs->alert_region));
|
|
||||||
}
|
|
||||||
// ensure null termination after raw read
|
|
||||||
_prefs->snmp_community[sizeof(_prefs->snmp_community) - 1] = '\0';
|
|
||||||
_prefs->alert_psk_hex[sizeof(_prefs->alert_psk_hex) - 1] = '\0';
|
|
||||||
_prefs->alert_hashtag[sizeof(_prefs->alert_hashtag) - 1] = '\0';
|
|
||||||
_prefs->alert_region[sizeof(_prefs->alert_region) - 1] = '\0';
|
|
||||||
|
|
||||||
// sanitise bad pref values
|
// sanitise bad pref values
|
||||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||||
@@ -247,11 +169,6 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
|||||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||||
|
|
||||||
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
|
_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
|
|
||||||
if (_prefs->radio_watchdog_minutes > 120) {
|
|
||||||
_prefs->radio_watchdog_minutes = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
@@ -312,50 +229,17 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
|||||||
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||||
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||||
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||||
// MQTT settings - no longer saved here (stored in separate /mqtt_prefs file)
|
// MQTT/observer settings are stored in /mqtt_prefs, not here. No zero-gap is
|
||||||
// Write zeros/padding to maintain file format compatibility
|
// written anymore — /com_prefs holds only the (non-observer) fields below.
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain));
|
||||||
size_t mqtt_fields_size = getMQTTFieldsSize(_prefs);
|
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped));
|
||||||
#else
|
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert));
|
||||||
// If MQTT bridge not enabled, still write zeros for file format compatibility
|
|
||||||
size_t mqtt_fields_size =
|
|
||||||
sizeof(_prefs->mqtt_origin) + sizeof(_prefs->mqtt_iata) +
|
|
||||||
sizeof(_prefs->mqtt_status_enabled) + sizeof(_prefs->mqtt_packets_enabled) +
|
|
||||||
sizeof(_prefs->mqtt_raw_enabled) + sizeof(_prefs->mqtt_tx_enabled) +
|
|
||||||
sizeof(_prefs->mqtt_status_interval) + sizeof(_prefs->wifi_ssid) +
|
|
||||||
sizeof(_prefs->wifi_password) + sizeof(_prefs->timezone_string) +
|
|
||||||
sizeof(_prefs->timezone_offset) + sizeof(_prefs->mqtt_slot_preset) +
|
|
||||||
sizeof(_prefs->mqtt_slot_host) + sizeof(_prefs->mqtt_slot_port) +
|
|
||||||
sizeof(_prefs->mqtt_slot_username) + sizeof(_prefs->mqtt_slot_password) +
|
|
||||||
sizeof(_prefs->mqtt_owner_public_key) + sizeof(_prefs->mqtt_email);
|
|
||||||
#endif
|
|
||||||
memset(pad, 0, sizeof(pad));
|
|
||||||
size_t remaining = mqtt_fields_size;
|
|
||||||
while (remaining > 0) {
|
|
||||||
size_t to_write = remaining > sizeof(pad) ? sizeof(pad) : remaining;
|
|
||||||
file.write(pad, to_write);
|
|
||||||
remaining -= to_write;
|
|
||||||
}
|
|
||||||
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
|
|
||||||
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
|
|
||||||
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
|
|
||||||
file.write((uint8_t *)&_prefs->snmp_enabled, sizeof(_prefs->snmp_enabled)); // 293
|
|
||||||
file.write((uint8_t *)&_prefs->snmp_community, sizeof(_prefs->snmp_community)); // 294
|
|
||||||
file.write((uint8_t *)&_prefs->radio_watchdog_minutes, sizeof(_prefs->radio_watchdog_minutes)); // 318
|
|
||||||
// Alert channel fields (appended)
|
|
||||||
file.write((uint8_t *)&_prefs->alert_enabled, sizeof(_prefs->alert_enabled));
|
|
||||||
file.write((uint8_t *)&_prefs->alert_psk_hex, sizeof(_prefs->alert_psk_hex));
|
|
||||||
file.write((uint8_t *)&_prefs->alert_wifi_minutes, sizeof(_prefs->alert_wifi_minutes));
|
|
||||||
file.write((uint8_t *)&_prefs->alert_mqtt_minutes, sizeof(_prefs->alert_mqtt_minutes));
|
|
||||||
file.write((uint8_t *)&_prefs->alert_min_interval_min, sizeof(_prefs->alert_min_interval_min));
|
|
||||||
file.write((uint8_t *)&_prefs->alert_hashtag, sizeof(_prefs->alert_hashtag));
|
|
||||||
file.write((uint8_t *)&_prefs->alert_region, sizeof(_prefs->alert_region));
|
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
// Save MQTT preferences to separate file
|
// Observer config (MQTT/WiFi/timezone/SNMP/alert) is persisted separately. The
|
||||||
syncNodePrefsToMQTTPrefs(); // Sync any changes from NodePrefs to MQTTPrefs
|
// observer CLI writes _mqtt_prefs directly, so no NodePrefs->MQTTPrefs sync runs.
|
||||||
saveMQTTPrefs(fs);
|
saveMQTTPrefs(fs);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -519,69 +403,6 @@ void CommonCLI::saveMQTTPrefs(FILESYSTEM* fs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommonCLI::syncMQTTPrefsToNodePrefs() {
|
|
||||||
// Copy MQTT prefs to NodePrefs so existing code can access them
|
|
||||||
// Use StrHelper::strncpy to ensure proper null termination
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_origin, _mqtt_prefs.mqtt_origin, sizeof(_prefs->mqtt_origin));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_iata, _mqtt_prefs.mqtt_iata, sizeof(_prefs->mqtt_iata));
|
|
||||||
_prefs->mqtt_status_enabled = _mqtt_prefs.mqtt_status_enabled;
|
|
||||||
_prefs->mqtt_packets_enabled = _mqtt_prefs.mqtt_packets_enabled;
|
|
||||||
_prefs->mqtt_raw_enabled = _mqtt_prefs.mqtt_raw_enabled;
|
|
||||||
_prefs->mqtt_tx_enabled = _mqtt_prefs.mqtt_tx_enabled;
|
|
||||||
_prefs->mqtt_rx_enabled = _mqtt_prefs.mqtt_rx_enabled;
|
|
||||||
_prefs->mqtt_status_interval = _mqtt_prefs.mqtt_status_interval;
|
|
||||||
StrHelper::strncpy(_prefs->wifi_ssid, _mqtt_prefs.wifi_ssid, sizeof(_prefs->wifi_ssid));
|
|
||||||
StrHelper::strncpy(_prefs->wifi_password, _mqtt_prefs.wifi_password, sizeof(_prefs->wifi_password));
|
|
||||||
_prefs->wifi_power_save = _mqtt_prefs.wifi_power_save;
|
|
||||||
StrHelper::strncpy(_prefs->timezone_string, _mqtt_prefs.timezone_string, sizeof(_prefs->timezone_string));
|
|
||||||
_prefs->timezone_offset = _mqtt_prefs.timezone_offset;
|
|
||||||
// Slot-based fields
|
|
||||||
for (int i = 0; i < MAX_MQTT_SLOTS; i++) {
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_preset[i], _mqtt_prefs.mqtt_slot_preset[i], sizeof(_prefs->mqtt_slot_preset[i]));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_host[i], _mqtt_prefs.mqtt_slot_host[i], sizeof(_prefs->mqtt_slot_host[i]));
|
|
||||||
_prefs->mqtt_slot_port[i] = _mqtt_prefs.mqtt_slot_port[i];
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_username[i], _mqtt_prefs.mqtt_slot_username[i], sizeof(_prefs->mqtt_slot_username[i]));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_password[i], _mqtt_prefs.mqtt_slot_password[i], sizeof(_prefs->mqtt_slot_password[i]));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_token[i], _mqtt_prefs.mqtt_slot_token[i], sizeof(_prefs->mqtt_slot_token[i]));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_topic[i], _mqtt_prefs.mqtt_slot_topic[i], sizeof(_prefs->mqtt_slot_topic[i]));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_audience[i], _mqtt_prefs.mqtt_slot_audience[i], sizeof(_prefs->mqtt_slot_audience[i]));
|
|
||||||
}
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_owner_public_key, _mqtt_prefs.mqtt_owner_public_key, sizeof(_prefs->mqtt_owner_public_key));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_email, _mqtt_prefs.mqtt_email, sizeof(_prefs->mqtt_email));
|
|
||||||
StrHelper::strncpy(_prefs->mqtt_ntp_server, _mqtt_prefs.mqtt_ntp_server, sizeof(_prefs->mqtt_ntp_server));
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommonCLI::syncNodePrefsToMQTTPrefs() {
|
|
||||||
// Copy NodePrefs to MQTT prefs (used when saving after changes via CLI)
|
|
||||||
// Use StrHelper::strncpy to ensure proper null termination
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_origin, _prefs->mqtt_origin, sizeof(_mqtt_prefs.mqtt_origin));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_iata, _prefs->mqtt_iata, sizeof(_mqtt_prefs.mqtt_iata));
|
|
||||||
_mqtt_prefs.mqtt_status_enabled = _prefs->mqtt_status_enabled;
|
|
||||||
_mqtt_prefs.mqtt_packets_enabled = _prefs->mqtt_packets_enabled;
|
|
||||||
_mqtt_prefs.mqtt_raw_enabled = _prefs->mqtt_raw_enabled;
|
|
||||||
_mqtt_prefs.mqtt_tx_enabled = _prefs->mqtt_tx_enabled;
|
|
||||||
_mqtt_prefs.mqtt_rx_enabled = _prefs->mqtt_rx_enabled;
|
|
||||||
_mqtt_prefs.mqtt_status_interval = _prefs->mqtt_status_interval;
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.wifi_ssid, _prefs->wifi_ssid, sizeof(_mqtt_prefs.wifi_ssid));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.wifi_password, _prefs->wifi_password, sizeof(_mqtt_prefs.wifi_password));
|
|
||||||
_mqtt_prefs.wifi_power_save = _prefs->wifi_power_save;
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.timezone_string, _prefs->timezone_string, sizeof(_mqtt_prefs.timezone_string));
|
|
||||||
_mqtt_prefs.timezone_offset = _prefs->timezone_offset;
|
|
||||||
// Slot-based fields
|
|
||||||
for (int i = 0; i < MAX_MQTT_SLOTS; i++) {
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[i], _prefs->mqtt_slot_preset[i], sizeof(_mqtt_prefs.mqtt_slot_preset[i]));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_host[i], _prefs->mqtt_slot_host[i], sizeof(_mqtt_prefs.mqtt_slot_host[i]));
|
|
||||||
_mqtt_prefs.mqtt_slot_port[i] = _prefs->mqtt_slot_port[i];
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_username[i], _prefs->mqtt_slot_username[i], sizeof(_mqtt_prefs.mqtt_slot_username[i]));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_password[i], _prefs->mqtt_slot_password[i], sizeof(_mqtt_prefs.mqtt_slot_password[i]));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_token[i], _prefs->mqtt_slot_token[i], sizeof(_mqtt_prefs.mqtt_slot_token[i]));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_topic[i], _prefs->mqtt_slot_topic[i], sizeof(_mqtt_prefs.mqtt_slot_topic[i]));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_audience[i], _prefs->mqtt_slot_audience[i], sizeof(_mqtt_prefs.mqtt_slot_audience[i]));
|
|
||||||
}
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_owner_public_key, _prefs->mqtt_owner_public_key, sizeof(_mqtt_prefs.mqtt_owner_public_key));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_email, _prefs->mqtt_email, sizeof(_mqtt_prefs.mqtt_email));
|
|
||||||
StrHelper::strncpy(_mqtt_prefs.mqtt_ntp_server, _prefs->mqtt_ntp_server, sizeof(_mqtt_prefs.mqtt_ntp_server));
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define MIN_LOCAL_ADVERT_INTERVAL 60
|
#define MIN_LOCAL_ADVERT_INTERVAL 60
|
||||||
@@ -918,30 +739,6 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
|||||||
_prefs->agc_reset_interval = atoi(&config[19]) / 4;
|
_prefs->agc_reset_interval = atoi(&config[19]) / 4;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
|
sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
|
||||||
} else if (memcmp(config, "radio.watchdog ", 15) == 0) {
|
|
||||||
const char* val = &config[15];
|
|
||||||
if (*val == 0) {
|
|
||||||
strcpy(reply, "Error: missing radio.watchdog minutes");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const char* sp = val; *sp; sp++) {
|
|
||||||
if (*sp < '0' || *sp > '9') {
|
|
||||||
strcpy(reply, "Error: radio.watchdog must be an integer 0-120");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int mins = atoi(val);
|
|
||||||
if (mins > 120) {
|
|
||||||
strcpy(reply, "Error: radio.watchdog must be 0-120 minutes");
|
|
||||||
} else {
|
|
||||||
_prefs->radio_watchdog_minutes = (uint8_t)mins;
|
|
||||||
savePrefs();
|
|
||||||
if (mins == 0) {
|
|
||||||
strcpy(reply, "OK - radio watchdog disabled");
|
|
||||||
} else {
|
|
||||||
sprintf(reply, "OK - radio watchdog %d min", mins);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (memcmp(config, "multi.acks ", 11) == 0) {
|
} else if (memcmp(config, "multi.acks ", 11) == 0) {
|
||||||
_prefs->multi_acks = atoi(&config[11]);
|
_prefs->multi_acks = atoi(&config[11]);
|
||||||
savePrefs();
|
savePrefs();
|
||||||
@@ -1154,11 +951,11 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
|||||||
_prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0;
|
_prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0;
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
if (_prefs->bridge_pkt_src == 1) {
|
if (_prefs->bridge_pkt_src == 1) {
|
||||||
_prefs->mqtt_rx_enabled = 1;
|
_mqtt_prefs.mqtt_rx_enabled = 1;
|
||||||
_prefs->mqtt_tx_enabled = 0;
|
_mqtt_prefs.mqtt_tx_enabled = 0;
|
||||||
} else {
|
} else {
|
||||||
_prefs->mqtt_rx_enabled = 0;
|
_mqtt_prefs.mqtt_rx_enabled = 0;
|
||||||
_prefs->mqtt_tx_enabled = 1;
|
_mqtt_prefs.mqtt_tx_enabled = 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
savePrefs();
|
savePrefs();
|
||||||
@@ -1226,8 +1023,6 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
|||||||
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
|
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
|
||||||
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
|
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
|
||||||
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
|
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
|
||||||
} else if (memcmp(config, "radio.watchdog", 14) == 0) {
|
|
||||||
sprintf(reply, "> %d", (uint32_t)_prefs->radio_watchdog_minutes);
|
|
||||||
} else if (memcmp(config, "multi.acks", 10) == 0) {
|
} else if (memcmp(config, "multi.acks", 10) == 0) {
|
||||||
sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks);
|
sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks);
|
||||||
} else if (memcmp(config, "allow.read.only", 15) == 0) {
|
} else if (memcmp(config, "allow.read.only", 15) == 0) {
|
||||||
|
|||||||
+23
-64
@@ -63,71 +63,12 @@ struct NodePrefs { // persisted to file
|
|||||||
uint32_t discovery_mod_timestamp;
|
uint32_t discovery_mod_timestamp;
|
||||||
float adc_multiplier;
|
float adc_multiplier;
|
||||||
char owner_info[120];
|
char owner_info[120];
|
||||||
// MQTT settings (stored separately in /mqtt_prefs, but kept here for backward compatibility)
|
|
||||||
char mqtt_origin[32]; // Device name for MQTT topics
|
|
||||||
char mqtt_iata[8]; // IATA code for MQTT topics
|
|
||||||
uint8_t mqtt_status_enabled; // Enable status messages
|
|
||||||
uint8_t mqtt_packets_enabled; // Enable packet messages
|
|
||||||
uint8_t mqtt_raw_enabled; // Enable raw messages
|
|
||||||
uint8_t mqtt_tx_enabled; // TX packet uplinking: 0=off, 1=all, 2=advert (self-originated only)
|
|
||||||
uint32_t mqtt_status_interval; // Status publish interval (ms)
|
|
||||||
uint8_t mqtt_rx_enabled; // Enable RX packet uplinking (default: on)
|
|
||||||
|
|
||||||
// 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: 1=none)
|
|
||||||
|
|
||||||
// Timezone settings
|
|
||||||
char timezone_string[32]; // Timezone string (e.g., "America/Los_Angeles")
|
|
||||||
int8_t timezone_offset; // Timezone offset in hours (-12 to +14) - fallback
|
|
||||||
|
|
||||||
// MQTT slot presets (up to MAX_MQTT_SLOTS, each can be a preset name or "custom"/"none")
|
|
||||||
char mqtt_slot_preset[MAX_MQTT_SLOTS][24]; // e.g. "analyzer-us", "meshmapper", "custom", "none"
|
|
||||||
|
|
||||||
// Per-slot custom broker settings (only used when slot preset is "custom")
|
|
||||||
char mqtt_slot_host[MAX_MQTT_SLOTS][64];
|
|
||||||
uint16_t mqtt_slot_port[MAX_MQTT_SLOTS];
|
|
||||||
char mqtt_slot_username[MAX_MQTT_SLOTS][32];
|
|
||||||
char mqtt_slot_password[MAX_MQTT_SLOTS][64];
|
|
||||||
|
|
||||||
// Shared MQTT authentication
|
|
||||||
char mqtt_owner_public_key[65]; // Owner public key (hex string, same length as repeater public key)
|
|
||||||
char mqtt_email[64]; // Owner email address for matching nodes with owners
|
|
||||||
|
|
||||||
// Per-slot extended fields
|
|
||||||
char mqtt_slot_token[MAX_MQTT_SLOTS][48]; // Per-slot token (e.g., MeshRank account token)
|
|
||||||
char mqtt_slot_topic[MAX_MQTT_SLOTS][96]; // Per-slot custom topic template (custom preset only)
|
|
||||||
char mqtt_slot_audience[MAX_MQTT_SLOTS][64]; // JWT audience (non-empty enables JWT auth for custom slots)
|
|
||||||
|
|
||||||
uint8_t loop_detect;
|
uint8_t loop_detect;
|
||||||
|
|
||||||
// SNMP settings (optional, only used when WITH_SNMP is defined)
|
// NOTE: observer settings (MQTT/WiFi/timezone/SNMP/alert) were moved out of
|
||||||
uint8_t snmp_enabled; // boolean: 0=off, 1=on
|
// NodePrefs into MQTTPrefs (persisted to /mqtt_prefs) so this struct stays
|
||||||
char snmp_community[24]; // community string (default "public")
|
// aligned with upstream. See struct MQTTPrefs below.
|
||||||
uint8_t radio_watchdog_minutes; // 0=disabled, 1-120 minutes
|
|
||||||
|
|
||||||
// Fault alert channel (LoRa group-channel "observer status" message on prolonged WiFi/MQTT outage).
|
|
||||||
// Sent over the radio (NOT over MQTT) so the alert still works while the MQTT path is broken.
|
|
||||||
// All fields are appended at the end of NodePrefs for binary-compatible upgrades.
|
|
||||||
uint8_t alert_enabled; // 0 = off (default), 1 = on
|
|
||||||
char alert_psk_hex[33]; // 32 lowercase hex chars (16-byte channel secret) + null; empty = alerts disabled. Banned keys (Public/#test/#bot) are rejected.
|
|
||||||
uint16_t alert_wifi_minutes; // WiFi-down threshold in minutes (0 = disabled), default 30
|
|
||||||
uint16_t alert_mqtt_minutes; // MQTT-down threshold in minutes (0 = disabled), default 240 (4 h)
|
|
||||||
uint16_t alert_min_interval_min; // min minutes between alerts for the same fault, default 60, floor 60
|
|
||||||
// When the operator configures via `set alert.hashtag <name>`, we derive
|
|
||||||
// alert_psk_hex from sha256("#name")[0..15] once and remember the hashtag
|
|
||||||
// text here purely for `get alert.hashtag` readback. A subsequent
|
|
||||||
// `set alert.psk` clears this field so it doesn't lie about provenance.
|
|
||||||
char alert_hashtag[24];
|
|
||||||
// Optional region name (e.g. "us", "eu"); empty = use the repeater's
|
|
||||||
// default_scope. Looked up lazily via RegionMap::findByNamePrefix at send
|
|
||||||
// time, so the operator can name a region that doesn't exist yet without
|
|
||||||
// polluting region_map state. Falls back to default_scope on miss.
|
|
||||||
char alert_region[31];
|
|
||||||
|
|
||||||
// Custom NTP server (MQTT observer); empty = built-in default primary (pool.ntp.org)
|
|
||||||
char mqtt_ntp_server[64];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
@@ -206,6 +147,20 @@ struct MQTTPrefs {
|
|||||||
// --- Appended fields (added after initial 6-slot migration) ---
|
// --- Appended fields (added after initial 6-slot migration) ---
|
||||||
uint8_t mqtt_rx_enabled; // Enable RX packet uplinking (default: on)
|
uint8_t mqtt_rx_enabled; // Enable RX packet uplinking (default: on)
|
||||||
char mqtt_ntp_server[64]; // Custom NTP server; empty = pool.ntp.org
|
char mqtt_ntp_server[64]; // Custom NTP server; empty = pool.ntp.org
|
||||||
|
|
||||||
|
// --- Observer non-MQTT settings, moved out of NodePrefs (Phase 2). Appended at
|
||||||
|
// the end so existing /mqtt_prefs files (which lack them) still load; values are
|
||||||
|
// migrated one-time from the old /com_prefs trailing block on first boot. ---
|
||||||
|
uint8_t snmp_enabled; // boolean
|
||||||
|
char snmp_community[24]; // community string (default "public")
|
||||||
|
uint8_t radio_watchdog_minutes; // 0=disabled, 1-120 minutes (observer-only radio recovery)
|
||||||
|
uint8_t alert_enabled; // 0 = off (default)
|
||||||
|
char alert_psk_hex[33]; // 32 hex chars + null; empty = alerts disabled
|
||||||
|
uint16_t alert_wifi_minutes; // WiFi-down threshold (0 = disabled), default 30
|
||||||
|
uint16_t alert_mqtt_minutes; // MQTT-down threshold (0 = disabled), default 240
|
||||||
|
uint16_t alert_min_interval_min; // min minutes between same-fault alerts, default 60
|
||||||
|
char alert_hashtag[24]; // readback for `get alert.hashtag`
|
||||||
|
char alert_region[31]; // optional region override; empty = default_scope
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3-slot MQTTPrefs layout — used for migrating from 3-slot to 6-slot format.
|
// 3-slot MQTTPrefs layout — used for migrating from 3-slot to 6-slot format.
|
||||||
@@ -360,8 +315,6 @@ class CommonCLI {
|
|||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
void loadMQTTPrefs(FILESYSTEM* fs);
|
void loadMQTTPrefs(FILESYSTEM* fs);
|
||||||
void saveMQTTPrefs(FILESYSTEM* fs);
|
void saveMQTTPrefs(FILESYSTEM* fs);
|
||||||
void syncMQTTPrefsToNodePrefs();
|
|
||||||
void syncNodePrefsToMQTTPrefs();
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void handleRegionCmd(char* command, char* reply);
|
void handleRegionCmd(char* command, char* reply);
|
||||||
@@ -387,4 +340,10 @@ public:
|
|||||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||||
mesh::MainBoard* getBoard() { return _board; }
|
mesh::MainBoard* getBoard() { return _board; }
|
||||||
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
|
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
|
// Observer config (MQTT/WiFi/timezone/SNMP/alert), persisted to /mqtt_prefs.
|
||||||
|
// Exposed so the app can hand it to MQTTBridge/AlertReporter, which read these
|
||||||
|
// fields directly (they no longer live in NodePrefs).
|
||||||
|
MQTTPrefs* getObserverPrefs() const { return const_cast<MQTTPrefs*>(&_mqtt_prefs); }
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
+153
-112
@@ -160,64 +160,89 @@ static void formatMQTTPresetListReply(char* reply, size_t reply_size, int start)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* config, char* reply) {
|
bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* config, char* reply) {
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
bool handled = true;
|
bool handled = true;
|
||||||
if (memcmp(config, "snmp.community ", 15) == 0) {
|
if (memcmp(config, "snmp.community ", 15) == 0) {
|
||||||
StrHelper::strncpy(_prefs->snmp_community, &config[15], sizeof(_prefs->snmp_community));
|
StrHelper::strncpy(_mqtt_prefs.snmp_community, &config[15], sizeof(_mqtt_prefs.snmp_community));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK - restart to apply");
|
strcpy(reply, "OK - restart to apply");
|
||||||
} else if (memcmp(config, "snmp ", 5) == 0) {
|
} else if (memcmp(config, "snmp ", 5) == 0) {
|
||||||
_prefs->snmp_enabled = memcmp(&config[5], "on", 2) == 0;
|
_mqtt_prefs.snmp_enabled = memcmp(&config[5], "on", 2) == 0;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK - restart to apply");
|
strcpy(reply, "OK - restart to apply");
|
||||||
|
} else if (memcmp(config, "radio.watchdog ", 15) == 0) {
|
||||||
|
const char* val = &config[15];
|
||||||
|
bool all_digits = (*val != '\0');
|
||||||
|
for (const char* sp = val; *sp; sp++) {
|
||||||
|
if (*sp < '0' || *sp > '9') { all_digits = false; break; }
|
||||||
|
}
|
||||||
|
if (*val == '\0') {
|
||||||
|
strcpy(reply, "Error: missing radio.watchdog minutes");
|
||||||
|
} else if (!all_digits) {
|
||||||
|
strcpy(reply, "Error: radio.watchdog must be an integer 0-120");
|
||||||
|
} else {
|
||||||
|
int mins = atoi(val);
|
||||||
|
if (mins > 120) {
|
||||||
|
strcpy(reply, "Error: radio.watchdog must be 0-120 minutes");
|
||||||
|
} else {
|
||||||
|
_mqtt_prefs.radio_watchdog_minutes = (uint8_t)mins;
|
||||||
|
savePrefs();
|
||||||
|
if (mins == 0) {
|
||||||
|
strcpy(reply, "OK - radio watchdog disabled");
|
||||||
|
} else {
|
||||||
|
sprintf(reply, "OK - radio watchdog %d min", mins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
} else if (strcmp(config, "mqtt.origin") == 0) {
|
} else if (strcmp(config, "mqtt.origin") == 0) {
|
||||||
_prefs->mqtt_origin[0] = '\0';
|
_mqtt_prefs.mqtt_origin[0] = '\0';
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.origin ", 12) == 0) {
|
} else if (memcmp(config, "mqtt.origin ", 12) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_origin, &config[12], sizeof(_prefs->mqtt_origin));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_origin, &config[12], sizeof(_mqtt_prefs.mqtt_origin));
|
||||||
StrHelper::stripSurroundingQuotes(_prefs->mqtt_origin, sizeof(_prefs->mqtt_origin));
|
StrHelper::stripSurroundingQuotes(_mqtt_prefs.mqtt_origin, sizeof(_mqtt_prefs.mqtt_origin));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.iata ", 10) == 0) {
|
} else if (memcmp(config, "mqtt.iata ", 10) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_iata, &config[10], sizeof(_prefs->mqtt_iata));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_iata, &config[10], sizeof(_mqtt_prefs.mqtt_iata));
|
||||||
for (int i = 0; _prefs->mqtt_iata[i]; i++) {
|
for (int i = 0; _mqtt_prefs.mqtt_iata[i]; i++) {
|
||||||
_prefs->mqtt_iata[i] = toupper(_prefs->mqtt_iata[i]);
|
_mqtt_prefs.mqtt_iata[i] = toupper(_mqtt_prefs.mqtt_iata[i]);
|
||||||
}
|
}
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridge();
|
_callbacks->restartBridge();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.status ", 12) == 0) {
|
} else if (memcmp(config, "mqtt.status ", 12) == 0) {
|
||||||
_prefs->mqtt_status_enabled = memcmp(&config[12], "on", 2) == 0;
|
_mqtt_prefs.mqtt_status_enabled = memcmp(&config[12], "on", 2) == 0;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.packets ", 13) == 0) {
|
} else if (memcmp(config, "mqtt.packets ", 13) == 0) {
|
||||||
_prefs->mqtt_packets_enabled = memcmp(&config[13], "on", 2) == 0;
|
_mqtt_prefs.mqtt_packets_enabled = memcmp(&config[13], "on", 2) == 0;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.raw ", 9) == 0) {
|
} else if (memcmp(config, "mqtt.raw ", 9) == 0) {
|
||||||
_prefs->mqtt_raw_enabled = memcmp(&config[9], "on", 2) == 0;
|
_mqtt_prefs.mqtt_raw_enabled = memcmp(&config[9], "on", 2) == 0;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.tx ", 8) == 0) {
|
} else if (memcmp(config, "mqtt.tx ", 8) == 0) {
|
||||||
if (memcmp(&config[8], "advert", 6) == 0) {
|
if (memcmp(&config[8], "advert", 6) == 0) {
|
||||||
_prefs->mqtt_tx_enabled = 2;
|
_mqtt_prefs.mqtt_tx_enabled = 2;
|
||||||
} else {
|
} else {
|
||||||
_prefs->mqtt_tx_enabled = memcmp(&config[8], "on", 2) == 0 ? 1 : 0;
|
_mqtt_prefs.mqtt_tx_enabled = memcmp(&config[8], "on", 2) == 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.rx ", 8) == 0) {
|
} else if (memcmp(config, "mqtt.rx ", 8) == 0) {
|
||||||
_prefs->mqtt_rx_enabled = memcmp(&config[8], "on", 2) == 0 ? 1 : 0;
|
_mqtt_prefs.mqtt_rx_enabled = memcmp(&config[8], "on", 2) == 0 ? 1 : 0;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "mqtt.interval ", 14) == 0) {
|
} else if (memcmp(config, "mqtt.interval ", 14) == 0) {
|
||||||
uint32_t minutes = _atoi(&config[14]);
|
uint32_t minutes = _atoi(&config[14]);
|
||||||
if (minutes >= 1 && minutes <= 60) {
|
if (minutes >= 1 && minutes <= 60) {
|
||||||
_prefs->mqtt_status_interval = minutes * 60000;
|
_mqtt_prefs.mqtt_status_interval = minutes * 60000;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridge();
|
_callbacks->restartBridge();
|
||||||
sprintf(reply, "OK - interval set to %u minutes (%lu ms), bridge restarted", minutes, (unsigned long)_prefs->mqtt_status_interval);
|
sprintf(reply, "OK - interval set to %u minutes (%lu ms), bridge restarted", minutes, (unsigned long)_mqtt_prefs.mqtt_status_interval);
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "Error: interval must be between 1-60 minutes");
|
strcpy(reply, "Error: interval must be between 1-60 minutes");
|
||||||
}
|
}
|
||||||
@@ -229,9 +254,9 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
strcpy(reply, "Error: invalid NTP hostname");
|
strcpy(reply, "Error: invalid NTP hostname");
|
||||||
} else {
|
} else {
|
||||||
if (clearing) {
|
if (clearing) {
|
||||||
_prefs->mqtt_ntp_server[0] = '\0';
|
_mqtt_prefs.mqtt_ntp_server[0] = '\0';
|
||||||
} else {
|
} else {
|
||||||
StrHelper::strncpy(_prefs->mqtt_ntp_server, host, sizeof(_prefs->mqtt_ntp_server));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_ntp_server, host, sizeof(_mqtt_prefs.mqtt_ntp_server));
|
||||||
}
|
}
|
||||||
savePrefs();
|
savePrefs();
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
@@ -251,11 +276,11 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "wifi.ssid ", 10) == 0) {
|
} else if (memcmp(config, "wifi.ssid ", 10) == 0) {
|
||||||
StrHelper::strncpy(_prefs->wifi_ssid, &config[10], sizeof(_prefs->wifi_ssid));
|
StrHelper::strncpy(_mqtt_prefs.wifi_ssid, &config[10], sizeof(_mqtt_prefs.wifi_ssid));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "wifi.pwd ", 9) == 0) {
|
} else if (memcmp(config, "wifi.pwd ", 9) == 0) {
|
||||||
StrHelper::strncpy(_prefs->wifi_password, &config[9], sizeof(_prefs->wifi_password));
|
StrHelper::strncpy(_mqtt_prefs.wifi_password, &config[9], sizeof(_mqtt_prefs.wifi_password));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "wifi.powersave ", 15) == 0) {
|
} else if (memcmp(config, "wifi.powersave ", 15) == 0) {
|
||||||
@@ -275,7 +300,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
if (!valid) {
|
if (!valid) {
|
||||||
strcpy(reply, "Error: must be none, min, or max");
|
strcpy(reply, "Error: must be none, min, or max");
|
||||||
} else {
|
} else {
|
||||||
_prefs->wifi_power_save = ps_value;
|
_mqtt_prefs.wifi_power_save = ps_value;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
@@ -298,13 +323,13 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "timezone ", 9) == 0) {
|
} else if (memcmp(config, "timezone ", 9) == 0) {
|
||||||
StrHelper::strncpy(_prefs->timezone_string, &config[9], sizeof(_prefs->timezone_string));
|
StrHelper::strncpy(_mqtt_prefs.timezone_string, &config[9], sizeof(_mqtt_prefs.timezone_string));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(config, "timezone.offset ", 16) == 0) {
|
} else if (memcmp(config, "timezone.offset ", 16) == 0) {
|
||||||
int8_t offset = _atoi(&config[16]);
|
int8_t offset = _atoi(&config[16]);
|
||||||
if (offset >= -12 && offset <= 14) {
|
if (offset >= -12 && offset <= 14) {
|
||||||
_prefs->timezone_offset = offset;
|
_mqtt_prefs.timezone_offset = offset;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else {
|
} else {
|
||||||
@@ -325,7 +350,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
int dup_slot = -1;
|
int dup_slot = -1;
|
||||||
if (findMQTTPreset(preset_name) != nullptr) {
|
if (findMQTTPreset(preset_name) != nullptr) {
|
||||||
for (int s = 0; s < MAX_MQTT_SLOTS; s++) {
|
for (int s = 0; s < MAX_MQTT_SLOTS; s++) {
|
||||||
if (s != slot && strcmp(_prefs->mqtt_slot_preset[s], preset_name) == 0) {
|
if (s != slot && strcmp(_mqtt_prefs.mqtt_slot_preset[s], preset_name) == 0) {
|
||||||
dup_slot = s;
|
dup_slot = s;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -334,19 +359,19 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
if (dup_slot >= 0) {
|
if (dup_slot >= 0) {
|
||||||
sprintf(reply, "Error: preset '%s' is already assigned to slot %d", preset_name, dup_slot + 1);
|
sprintf(reply, "Error: preset '%s' is already assigned to slot %d", preset_name, dup_slot + 1);
|
||||||
} else {
|
} else {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_preset[slot], preset_name, sizeof(_prefs->mqtt_slot_preset[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], preset_name, sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
// Check if the slot has everything it needs to connect
|
// Check if the slot has everything it needs to connect
|
||||||
const MQTTPresetDef* p = findMQTTPreset(preset_name);
|
const MQTTPresetDef* p = findMQTTPreset(preset_name);
|
||||||
if (p && p->topic_style == MQTT_TOPIC_MESHRANK && _prefs->mqtt_slot_token[slot][0] == '\0') {
|
if (p && p->topic_style == MQTT_TOPIC_MESHRANK && _mqtt_prefs.mqtt_slot_token[slot][0] == '\0') {
|
||||||
sprintf(reply, "OK - slot %d preset: %s (run 'set mqtt%d.token <your_token>' to connect)", slot + 1, preset_name, slot + 1);
|
sprintf(reply, "OK - slot %d preset: %s (run 'set mqtt%d.token <your_token>' to connect)", slot + 1, preset_name, slot + 1);
|
||||||
} else if (p && p->topic_style == MQTT_TOPIC_MESHCORE &&
|
} else if (p && p->topic_style == MQTT_TOPIC_MESHCORE &&
|
||||||
(strlen(_prefs->mqtt_iata) == 0 || strcmp(_prefs->mqtt_iata, "XXX") == 0)) {
|
(strlen(_mqtt_prefs.mqtt_iata) == 0 || strcmp(_mqtt_prefs.mqtt_iata, "XXX") == 0)) {
|
||||||
sprintf(reply, "OK - slot %d preset: %s (run 'set mqtt.iata <airport_code>' to publish)", slot + 1, preset_name);
|
sprintf(reply, "OK - slot %d preset: %s (run 'set mqtt.iata <airport_code>' to publish)", slot + 1, preset_name);
|
||||||
} else if (p && mqttPresetNeedsSlotCredentials(p) &&
|
} else if (p && mqttPresetNeedsSlotCredentials(p) &&
|
||||||
(_prefs->mqtt_slot_username[slot][0] == '\0' ||
|
(_mqtt_prefs.mqtt_slot_username[slot][0] == '\0' ||
|
||||||
_prefs->mqtt_slot_password[slot][0] == '\0')) {
|
_mqtt_prefs.mqtt_slot_password[slot][0] == '\0')) {
|
||||||
sprintf(reply,
|
sprintf(reply,
|
||||||
"OK - slot %d preset: %s (run 'set mqtt%d.username <user>' and 'set mqtt%d.password <pass>' to connect)",
|
"OK - slot %d preset: %s (run 'set mqtt%d.username <user>' and 'set mqtt%d.password <pass>' to connect)",
|
||||||
slot + 1, preset_name, slot + 1, slot + 1);
|
slot + 1, preset_name, slot + 1, slot + 1);
|
||||||
@@ -358,54 +383,54 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
strcpy(reply, "Error: unknown preset. Use 'get mqtt.presets'");
|
strcpy(reply, "Error: unknown preset. Use 'get mqtt.presets'");
|
||||||
}
|
}
|
||||||
} else if (memcmp(subcmd, "server ", 7) == 0) {
|
} else if (memcmp(subcmd, "server ", 7) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_host[slot], &subcmd[7], sizeof(_prefs->mqtt_slot_host[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_host[slot], &subcmd[7], sizeof(_mqtt_prefs.mqtt_slot_host[slot]));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(subcmd, "port ", 5) == 0) {
|
} else if (memcmp(subcmd, "port ", 5) == 0) {
|
||||||
int port = atoi(&subcmd[5]);
|
int port = atoi(&subcmd[5]);
|
||||||
if (port > 0 && port <= 65535) {
|
if (port > 0 && port <= 65535) {
|
||||||
_prefs->mqtt_slot_port[slot] = port;
|
_mqtt_prefs.mqtt_slot_port[slot] = port;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "Error: port must be between 1 and 65535");
|
strcpy(reply, "Error: port must be between 1 and 65535");
|
||||||
}
|
}
|
||||||
} else if (memcmp(subcmd, "username ", 9) == 0) {
|
} else if (memcmp(subcmd, "username ", 9) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_username[slot], &subcmd[9], sizeof(_prefs->mqtt_slot_username[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_username[slot], &subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_username[slot]));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(subcmd, "password ", 9) == 0) {
|
} else if (memcmp(subcmd, "password ", 9) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_password[slot], &subcmd[9], sizeof(_prefs->mqtt_slot_password[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_password[slot], &subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_password[slot]));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (memcmp(subcmd, "token ", 6) == 0) {
|
} else if (memcmp(subcmd, "token ", 6) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_token[slot], &subcmd[6], sizeof(_prefs->mqtt_slot_token[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_token[slot], &subcmd[6], sizeof(_mqtt_prefs.mqtt_slot_token[slot]));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
sprintf(reply, "OK - slot %d token set", slot + 1);
|
sprintf(reply, "OK - slot %d token set", slot + 1);
|
||||||
} else if (memcmp(subcmd, "topic ", 6) == 0) {
|
} else if (memcmp(subcmd, "topic ", 6) == 0) {
|
||||||
if (strcmp(_prefs->mqtt_slot_preset[slot], "custom") != 0) {
|
if (strcmp(_mqtt_prefs.mqtt_slot_preset[slot], "custom") != 0) {
|
||||||
sprintf(reply, "Error: topic template only applies to custom preset slots");
|
sprintf(reply, "Error: topic template only applies to custom preset slots");
|
||||||
} else {
|
} else {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_topic[slot], &subcmd[6], sizeof(_prefs->mqtt_slot_topic[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_topic[slot], &subcmd[6], sizeof(_mqtt_prefs.mqtt_slot_topic[slot]));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
sprintf(reply, "OK - slot %d topic: %s", slot + 1, _prefs->mqtt_slot_topic[slot]);
|
sprintf(reply, "OK - slot %d topic: %s", slot + 1, _mqtt_prefs.mqtt_slot_topic[slot]);
|
||||||
}
|
}
|
||||||
} else if (memcmp(subcmd, "audience ", 9) == 0) {
|
} else if (memcmp(subcmd, "audience ", 9) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_audience[slot], &subcmd[9], sizeof(_prefs->mqtt_slot_audience[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_audience[slot], &subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_audience[slot]));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
if (_prefs->mqtt_slot_audience[slot][0] != '\0') {
|
if (_mqtt_prefs.mqtt_slot_audience[slot][0] != '\0') {
|
||||||
sprintf(reply, "OK - slot %d JWT audience: %s", slot + 1, _prefs->mqtt_slot_audience[slot]);
|
sprintf(reply, "OK - slot %d JWT audience: %s", slot + 1, _mqtt_prefs.mqtt_slot_audience[slot]);
|
||||||
} else {
|
} else {
|
||||||
sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1);
|
sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1);
|
||||||
}
|
}
|
||||||
} else if (memcmp(subcmd, "audience", 8) == 0 && subcmd[8] == '\0') {
|
} else if (memcmp(subcmd, "audience", 8) == 0 && subcmd[8] == '\0') {
|
||||||
// "set mqttN.audience" with no value — clear the audience
|
// "set mqttN.audience" with no value — clear the audience
|
||||||
_prefs->mqtt_slot_audience[slot][0] = '\0';
|
_mqtt_prefs.mqtt_slot_audience[slot][0] = '\0';
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1);
|
sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1);
|
||||||
@@ -415,9 +440,9 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
} else if (memcmp(config, "mqtt.analyzer.us ", 17) == 0) {
|
} else if (memcmp(config, "mqtt.analyzer.us ", 17) == 0) {
|
||||||
const int slot = 0;
|
const int slot = 0;
|
||||||
if (memcmp(&config[17], "on", 2) == 0) {
|
if (memcmp(&config[17], "on", 2) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_preset[slot], "analyzer-us", sizeof(_prefs->mqtt_slot_preset[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], "analyzer-us", sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
|
||||||
} else {
|
} else {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_preset[slot], MQTT_PRESET_NONE, sizeof(_prefs->mqtt_slot_preset[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], MQTT_PRESET_NONE, sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
|
||||||
}
|
}
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
@@ -425,9 +450,9 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
} else if (memcmp(config, "mqtt.analyzer.eu ", 17) == 0) {
|
} else if (memcmp(config, "mqtt.analyzer.eu ", 17) == 0) {
|
||||||
const int slot = 1;
|
const int slot = 1;
|
||||||
if (memcmp(&config[17], "on", 2) == 0) {
|
if (memcmp(&config[17], "on", 2) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_preset[slot], "analyzer-eu", sizeof(_prefs->mqtt_slot_preset[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], "analyzer-eu", sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
|
||||||
} else {
|
} else {
|
||||||
StrHelper::strncpy(_prefs->mqtt_slot_preset[slot], MQTT_PRESET_NONE, sizeof(_prefs->mqtt_slot_preset[slot]));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], MQTT_PRESET_NONE, sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
|
||||||
}
|
}
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->restartBridgeSlot(slot);
|
_callbacks->restartBridgeSlot(slot);
|
||||||
@@ -446,7 +471,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (valid_key) {
|
if (valid_key) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_owner_public_key, owner_key, sizeof(_prefs->mqtt_owner_public_key));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_owner_public_key, owner_key, sizeof(_mqtt_prefs.mqtt_owner_public_key));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else {
|
} else {
|
||||||
@@ -456,7 +481,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
strcpy(reply, "Error: public key must be 64 hex characters (32 bytes)");
|
strcpy(reply, "Error: public key must be 64 hex characters (32 bytes)");
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "mqtt.email ", 11) == 0) {
|
} else if (memcmp(config, "mqtt.email ", 11) == 0) {
|
||||||
StrHelper::strncpy(_prefs->mqtt_email, &config[11], sizeof(_prefs->mqtt_email));
|
StrHelper::strncpy(_mqtt_prefs.mqtt_email, &config[11], sizeof(_mqtt_prefs.mqtt_email));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
#endif
|
#endif
|
||||||
@@ -464,12 +489,12 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
// set alert on|off
|
// set alert on|off
|
||||||
const char* val = &config[6];
|
const char* val = &config[6];
|
||||||
if (memcmp(val, "on", 2) == 0 && (val[2] == 0 || val[2] == ' ')) {
|
if (memcmp(val, "on", 2) == 0 && (val[2] == 0 || val[2] == ' ')) {
|
||||||
_prefs->alert_enabled = 1;
|
_mqtt_prefs.alert_enabled = 1;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
strcpy(reply, "OK - alerts on");
|
strcpy(reply, "OK - alerts on");
|
||||||
} else if (memcmp(val, "off", 3) == 0 && (val[3] == 0 || val[3] == ' ')) {
|
} else if (memcmp(val, "off", 3) == 0 && (val[3] == 0 || val[3] == ' ')) {
|
||||||
_prefs->alert_enabled = 0;
|
_mqtt_prefs.alert_enabled = 0;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
strcpy(reply, "OK - alerts off");
|
strcpy(reply, "OK - alerts off");
|
||||||
@@ -483,8 +508,8 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
while (*val == ' ') val++;
|
while (*val == ' ') val++;
|
||||||
size_t len = strlen(val);
|
size_t len = strlen(val);
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
_prefs->alert_psk_hex[0] = '\0';
|
_mqtt_prefs.alert_psk_hex[0] = '\0';
|
||||||
_prefs->alert_hashtag[0] = '\0';
|
_mqtt_prefs.alert_hashtag[0] = '\0';
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
strcpy(reply, "OK - alert.psk cleared (alerts disabled until configured)");
|
strcpy(reply, "OK - alert.psk cleared (alerts disabled until configured)");
|
||||||
@@ -515,10 +540,10 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
// those channels would spam every node in the area.
|
// those channels would spam every node in the area.
|
||||||
sprintf(reply, "Error: refusing banned channel '%s'; pick a private key or hashtag", banned);
|
sprintf(reply, "Error: refusing banned channel '%s'; pick a private key or hashtag", banned);
|
||||||
} else {
|
} else {
|
||||||
StrHelper::strncpy(_prefs->alert_psk_hex, normalized, sizeof(_prefs->alert_psk_hex));
|
StrHelper::strncpy(_mqtt_prefs.alert_psk_hex, normalized, sizeof(_mqtt_prefs.alert_psk_hex));
|
||||||
// The new PSK is operator-supplied, so any previously-derived
|
// The new PSK is operator-supplied, so any previously-derived
|
||||||
// hashtag name is no longer accurate provenance — drop it.
|
// hashtag name is no longer accurate provenance — drop it.
|
||||||
_prefs->alert_hashtag[0] = '\0';
|
_mqtt_prefs.alert_hashtag[0] = '\0';
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
strcpy(reply, "OK - alert.psk updated");
|
strcpy(reply, "OK - alert.psk updated");
|
||||||
@@ -530,8 +555,8 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
while (*val == ' ') val++;
|
while (*val == ' ') val++;
|
||||||
size_t in_len = strlen(val);
|
size_t in_len = strlen(val);
|
||||||
if (in_len == 0) {
|
if (in_len == 0) {
|
||||||
_prefs->alert_psk_hex[0] = '\0';
|
_mqtt_prefs.alert_psk_hex[0] = '\0';
|
||||||
_prefs->alert_hashtag[0] = '\0';
|
_mqtt_prefs.alert_hashtag[0] = '\0';
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
strcpy(reply, "OK - alert.hashtag cleared (alerts disabled until configured)");
|
strcpy(reply, "OK - alert.hashtag cleared (alerts disabled until configured)");
|
||||||
@@ -540,7 +565,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
// the sha256 input (matching the companion-app hashtag-channel
|
// the sha256 input (matching the companion-app hashtag-channel
|
||||||
// derivation in docs/companion_protocol.md). Accept the user typing
|
// derivation in docs/companion_protocol.md). Accept the user typing
|
||||||
// either "alerts" or "#alerts".
|
// either "alerts" or "#alerts".
|
||||||
char hashtag[sizeof(_prefs->alert_hashtag)];
|
char hashtag[sizeof(_mqtt_prefs.alert_hashtag)];
|
||||||
size_t need = (val[0] == '#') ? in_len : in_len + 1;
|
size_t need = (val[0] == '#') ? in_len : in_len + 1;
|
||||||
if (need >= sizeof(hashtag)) {
|
if (need >= sizeof(hashtag)) {
|
||||||
strcpy(reply, "Error: hashtag too long");
|
strcpy(reply, "Error: hashtag too long");
|
||||||
@@ -566,11 +591,11 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
} else {
|
} else {
|
||||||
char hex[33];
|
char hex[33];
|
||||||
mesh::Utils::toHex(hex, digest, 16);
|
mesh::Utils::toHex(hex, digest, 16);
|
||||||
StrHelper::strncpy(_prefs->alert_hashtag, hashtag, sizeof(_prefs->alert_hashtag));
|
StrHelper::strncpy(_mqtt_prefs.alert_hashtag, hashtag, sizeof(_mqtt_prefs.alert_hashtag));
|
||||||
StrHelper::strncpy(_prefs->alert_psk_hex, hex, sizeof(_prefs->alert_psk_hex));
|
StrHelper::strncpy(_mqtt_prefs.alert_psk_hex, hex, sizeof(_mqtt_prefs.alert_psk_hex));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
sprintf(reply, "OK - alert.hashtag: %s", _prefs->alert_hashtag);
|
sprintf(reply, "OK - alert.hashtag: %s", _mqtt_prefs.alert_hashtag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,25 +610,25 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
while (*val == ' ') val++;
|
while (*val == ' ') val++;
|
||||||
size_t len = strlen(val);
|
size_t len = strlen(val);
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
_prefs->alert_region[0] = '\0';
|
_mqtt_prefs.alert_region[0] = '\0';
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
strcpy(reply, "OK - alert.region cleared (using default scope)");
|
strcpy(reply, "OK - alert.region cleared (using default scope)");
|
||||||
} else if (len >= sizeof(_prefs->alert_region)) {
|
} else if (len >= sizeof(_mqtt_prefs.alert_region)) {
|
||||||
strcpy(reply, "Error: alert.region too long");
|
strcpy(reply, "Error: alert.region too long");
|
||||||
} else {
|
} else {
|
||||||
StrHelper::strncpy(_prefs->alert_region, val, sizeof(_prefs->alert_region));
|
StrHelper::strncpy(_mqtt_prefs.alert_region, val, sizeof(_mqtt_prefs.alert_region));
|
||||||
StrHelper::stripSurroundingQuotes(_prefs->alert_region, sizeof(_prefs->alert_region));
|
StrHelper::stripSurroundingQuotes(_mqtt_prefs.alert_region, sizeof(_mqtt_prefs.alert_region));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
_callbacks->onAlertConfigChanged();
|
_callbacks->onAlertConfigChanged();
|
||||||
sprintf(reply, "OK - alert.region: %s", _prefs->alert_region);
|
sprintf(reply, "OK - alert.region: %s", _mqtt_prefs.alert_region);
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "alert.wifi ", 11) == 0) {
|
} else if (memcmp(config, "alert.wifi ", 11) == 0) {
|
||||||
int mins = (int)_atoi(&config[11]);
|
int mins = (int)_atoi(&config[11]);
|
||||||
if (mins < 0 || mins > 1440) {
|
if (mins < 0 || mins > 1440) {
|
||||||
strcpy(reply, "Error: alert.wifi must be 0-1440 minutes (0=off)");
|
strcpy(reply, "Error: alert.wifi must be 0-1440 minutes (0=off)");
|
||||||
} else {
|
} else {
|
||||||
_prefs->alert_wifi_minutes = (uint16_t)mins;
|
_mqtt_prefs.alert_wifi_minutes = (uint16_t)mins;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
sprintf(reply, "OK - alert.wifi %d min%s", mins, mins == 0 ? " (disabled)" : "");
|
sprintf(reply, "OK - alert.wifi %d min%s", mins, mins == 0 ? " (disabled)" : "");
|
||||||
}
|
}
|
||||||
@@ -612,7 +637,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
if (mins < 0 || mins > 10080) {
|
if (mins < 0 || mins > 10080) {
|
||||||
strcpy(reply, "Error: alert.mqtt must be 0-10080 minutes (0=off)");
|
strcpy(reply, "Error: alert.mqtt must be 0-10080 minutes (0=off)");
|
||||||
} else {
|
} else {
|
||||||
_prefs->alert_mqtt_minutes = (uint16_t)mins;
|
_mqtt_prefs.alert_mqtt_minutes = (uint16_t)mins;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
sprintf(reply, "OK - alert.mqtt %d min%s", mins, mins == 0 ? " (disabled)" : "");
|
sprintf(reply, "OK - alert.mqtt %d min%s", mins, mins == 0 ? " (disabled)" : "");
|
||||||
}
|
}
|
||||||
@@ -623,7 +648,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
if (mins < 60 || mins > 10080) {
|
if (mins < 60 || mins > 10080) {
|
||||||
strcpy(reply, "Error: alert.interval must be 60-10080 minutes");
|
strcpy(reply, "Error: alert.interval must be 60-10080 minutes");
|
||||||
} else {
|
} else {
|
||||||
_prefs->alert_min_interval_min = (uint16_t)mins;
|
_mqtt_prefs.alert_min_interval_min = (uint16_t)mins;
|
||||||
savePrefs();
|
savePrefs();
|
||||||
sprintf(reply, "OK - alert.interval %d min", mins);
|
sprintf(reply, "OK - alert.interval %d min", mins);
|
||||||
}
|
}
|
||||||
@@ -631,21 +656,28 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
handled = false;
|
handled = false;
|
||||||
}
|
}
|
||||||
return handled;
|
return handled;
|
||||||
|
#else
|
||||||
|
(void)sender_timestamp; (void)config; (void)reply;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* config, char* reply) {
|
bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* config, char* reply) {
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
bool handled = true;
|
bool handled = true;
|
||||||
if (memcmp(config, "snmp.community", 14) == 0) {
|
if (memcmp(config, "snmp.community", 14) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->snmp_community);
|
sprintf(reply, "> %s", _mqtt_prefs.snmp_community);
|
||||||
} else if (memcmp(config, "snmp", 4) == 0 && (config[4] == '\0' || config[4] == '\n' || config[4] == '\r')) {
|
} else if (memcmp(config, "snmp", 4) == 0 && (config[4] == '\0' || config[4] == '\n' || config[4] == '\r')) {
|
||||||
strcpy(reply, _prefs->snmp_enabled ? "> on" : "> off");
|
strcpy(reply, _mqtt_prefs.snmp_enabled ? "> on" : "> off");
|
||||||
|
} else if (memcmp(config, "radio.watchdog", 14) == 0) {
|
||||||
|
sprintf(reply, "> %d", (uint32_t)_mqtt_prefs.radio_watchdog_minutes);
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
} else if (memcmp(config, "mqtt.origin", 11) == 0) {
|
} else if (memcmp(config, "mqtt.origin", 11) == 0) {
|
||||||
char effective_origin[32];
|
char effective_origin[32];
|
||||||
MQTTBridge::getEffectiveMqttOrigin(_prefs, effective_origin, sizeof(effective_origin));
|
MQTTBridge::getEffectiveMqttOrigin(_prefs, &_mqtt_prefs, effective_origin, sizeof(effective_origin));
|
||||||
sprintf(reply, "> %s", effective_origin);
|
sprintf(reply, "> %s", effective_origin);
|
||||||
} else if (memcmp(config, "mqtt.iata", 9) == 0) {
|
} else if (memcmp(config, "mqtt.iata", 9) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_iata);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_iata);
|
||||||
} else if (memcmp(config, "mqtt.presets", 12) == 0 && (config[12] == '\0' || config[12] == ' ')) {
|
} else if (memcmp(config, "mqtt.presets", 12) == 0 && (config[12] == '\0' || config[12] == ' ')) {
|
||||||
int start = 0;
|
int start = 0;
|
||||||
if (config[12] == ' ') {
|
if (config[12] == ' ') {
|
||||||
@@ -664,19 +696,19 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
}
|
}
|
||||||
formatMQTTPresetListReply(reply, 160, start);
|
formatMQTTPresetListReply(reply, 160, start);
|
||||||
} else if (memcmp(config, "mqtt.status", 11) == 0) {
|
} else if (memcmp(config, "mqtt.status", 11) == 0) {
|
||||||
MQTTBridge::formatMqttStatusReply(reply, 160, _prefs);
|
MQTTBridge::formatMqttStatusReply(reply, 160, &_mqtt_prefs);
|
||||||
} else if (memcmp(config, "mqtt.packets", 12) == 0) {
|
} else if (memcmp(config, "mqtt.packets", 12) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_packets_enabled ? "on" : "off");
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_packets_enabled ? "on" : "off");
|
||||||
} else if (memcmp(config, "mqtt.raw", 8) == 0) {
|
} else if (memcmp(config, "mqtt.raw", 8) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_raw_enabled ? "on" : "off");
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_raw_enabled ? "on" : "off");
|
||||||
} else if (memcmp(config, "mqtt.tx", 7) == 0) {
|
} else if (memcmp(config, "mqtt.tx", 7) == 0) {
|
||||||
const char* tx_str = _prefs->mqtt_tx_enabled == 2 ? "advert" : (_prefs->mqtt_tx_enabled ? "on" : "off");
|
const char* tx_str = _mqtt_prefs.mqtt_tx_enabled == 2 ? "advert" : (_mqtt_prefs.mqtt_tx_enabled ? "on" : "off");
|
||||||
sprintf(reply, "> %s", tx_str);
|
sprintf(reply, "> %s", tx_str);
|
||||||
} else if (memcmp(config, "mqtt.rx", 7) == 0) {
|
} else if (memcmp(config, "mqtt.rx", 7) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_rx_enabled ? "on" : "off");
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_rx_enabled ? "on" : "off");
|
||||||
} else if (memcmp(config, "mqtt.interval", 13) == 0) {
|
} else if (memcmp(config, "mqtt.interval", 13) == 0) {
|
||||||
uint32_t minutes = (_prefs->mqtt_status_interval + 29999) / 60000;
|
uint32_t minutes = (_mqtt_prefs.mqtt_status_interval + 29999) / 60000;
|
||||||
sprintf(reply, "> %u minutes (%lu ms)", minutes, (unsigned long)_prefs->mqtt_status_interval);
|
sprintf(reply, "> %u minutes (%lu ms)", minutes, (unsigned long)_mqtt_prefs.mqtt_status_interval);
|
||||||
} else if (memcmp(config, "mqtt.ntp.diag", 13) == 0 && (config[13] == '\0' || config[13] == ' ')) {
|
} else if (memcmp(config, "mqtt.ntp.diag", 13) == 0 && (config[13] == '\0' || config[13] == ' ')) {
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
// Connectivity probe across all configured NTP servers; never updates the clock.
|
// Connectivity probe across all configured NTP servers; never updates the clock.
|
||||||
@@ -692,37 +724,37 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
strcpy(reply, "Error: not supported on this platform");
|
strcpy(reply, "Error: not supported on this platform");
|
||||||
#endif
|
#endif
|
||||||
} else if (memcmp(config, "mqtt.ntp", 8) == 0 && (config[8] == '\0' || config[8] == ' ')) {
|
} else if (memcmp(config, "mqtt.ntp", 8) == 0 && (config[8] == '\0' || config[8] == ' ')) {
|
||||||
sprintf(reply, "> %s", MQTTBridge::effectiveNtpPrimary(_prefs));
|
sprintf(reply, "> %s", MQTTBridge::effectiveNtpPrimary(&_mqtt_prefs));
|
||||||
} else if (config[0] == 'm' && config[1] == 'q' && config[2] == 't' && config[3] == 't' &&
|
} else if (config[0] == 'm' && config[1] == 'q' && config[2] == 't' && config[3] == 't' &&
|
||||||
config[4] >= '1' && config[4] <= ('0' + MAX_MQTT_SLOTS) && config[5] == '.') {
|
config[4] >= '1' && config[4] <= ('0' + MAX_MQTT_SLOTS) && config[5] == '.') {
|
||||||
// Slot-based commands: get mqtt1.preset, get mqtt1.server, etc.
|
// Slot-based commands: get mqtt1.preset, get mqtt1.server, etc.
|
||||||
int slot = config[4] - '1'; // 0-5
|
int slot = config[4] - '1'; // 0-5
|
||||||
const char* subcmd = &config[6];
|
const char* subcmd = &config[6];
|
||||||
if (memcmp(subcmd, "preset", 6) == 0) {
|
if (memcmp(subcmd, "preset", 6) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_slot_preset[slot]);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_preset[slot]);
|
||||||
} else if (memcmp(subcmd, "server", 6) == 0) {
|
} else if (memcmp(subcmd, "server", 6) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_slot_host[slot]);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_host[slot]);
|
||||||
} else if (memcmp(subcmd, "port", 4) == 0) {
|
} else if (memcmp(subcmd, "port", 4) == 0) {
|
||||||
sprintf(reply, "> %d", _prefs->mqtt_slot_port[slot]);
|
sprintf(reply, "> %d", _mqtt_prefs.mqtt_slot_port[slot]);
|
||||||
} else if (memcmp(subcmd, "username", 8) == 0) {
|
} else if (memcmp(subcmd, "username", 8) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_slot_username[slot]);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_username[slot]);
|
||||||
} else if (memcmp(subcmd, "password", 8) == 0) {
|
} else if (memcmp(subcmd, "password", 8) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_slot_password[slot]);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_password[slot]);
|
||||||
} else if (memcmp(subcmd, "token", 5) == 0) {
|
} else if (memcmp(subcmd, "token", 5) == 0) {
|
||||||
if (_prefs->mqtt_slot_token[slot][0] != '\0') {
|
if (_mqtt_prefs.mqtt_slot_token[slot][0] != '\0') {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_slot_token[slot]);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_token[slot]);
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "> (not set)");
|
strcpy(reply, "> (not set)");
|
||||||
}
|
}
|
||||||
} else if (memcmp(subcmd, "topic", 5) == 0) {
|
} else if (memcmp(subcmd, "topic", 5) == 0) {
|
||||||
if (_prefs->mqtt_slot_topic[slot][0] != '\0') {
|
if (_mqtt_prefs.mqtt_slot_topic[slot][0] != '\0') {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_slot_topic[slot]);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_topic[slot]);
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "> (default: meshcore/{iata}/{device}/{type})");
|
strcpy(reply, "> (default: meshcore/{iata}/{device}/{type})");
|
||||||
}
|
}
|
||||||
} else if (memcmp(subcmd, "audience", 8) == 0) {
|
} else if (memcmp(subcmd, "audience", 8) == 0) {
|
||||||
if (_prefs->mqtt_slot_audience[slot][0] != '\0') {
|
if (_mqtt_prefs.mqtt_slot_audience[slot][0] != '\0') {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_slot_audience[slot]);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_audience[slot]);
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "> (not set — custom slots use username/password auth)");
|
strcpy(reply, "> (not set — custom slots use username/password auth)");
|
||||||
}
|
}
|
||||||
@@ -732,9 +764,9 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
sprintf(reply, "??: %s", config);
|
sprintf(reply, "??: %s", config);
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "wifi.ssid", 9) == 0) {
|
} else if (memcmp(config, "wifi.ssid", 9) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->wifi_ssid);
|
sprintf(reply, "> %s", _mqtt_prefs.wifi_ssid);
|
||||||
} else if (memcmp(config, "wifi.pwd", 8) == 0) {
|
} else if (memcmp(config, "wifi.pwd", 8) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->wifi_password);
|
sprintf(reply, "> %s", _mqtt_prefs.wifi_password);
|
||||||
} else if (memcmp(config, "wifi.status", 11) == 0) {
|
} else if (memcmp(config, "wifi.status", 11) == 0) {
|
||||||
wl_status_t status = WiFi.status();
|
wl_status_t status = WiFi.status();
|
||||||
const char* status_str;
|
const char* status_str;
|
||||||
@@ -789,56 +821,61 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "wifi.powersave", 14) == 0) {
|
} else if (memcmp(config, "wifi.powersave", 14) == 0) {
|
||||||
uint8_t ps = _prefs->wifi_power_save;
|
uint8_t ps = _mqtt_prefs.wifi_power_save;
|
||||||
const char* ps_name = (ps == 1) ? "none" : (ps == 2) ? "max" : "min";
|
const char* ps_name = (ps == 1) ? "none" : (ps == 2) ? "max" : "min";
|
||||||
sprintf(reply, "> %s", ps_name);
|
sprintf(reply, "> %s", ps_name);
|
||||||
} else if (memcmp(config, "timezone", 8) == 0) {
|
} else if (memcmp(config, "timezone", 8) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->timezone_string);
|
sprintf(reply, "> %s", _mqtt_prefs.timezone_string);
|
||||||
} else if (memcmp(config, "timezone.offset", 15) == 0) {
|
} else if (memcmp(config, "timezone.offset", 15) == 0) {
|
||||||
sprintf(reply, "> %d", _prefs->timezone_offset);
|
sprintf(reply, "> %d", _mqtt_prefs.timezone_offset);
|
||||||
} else if (memcmp(config, "mqtt.analyzer.us", 17) == 0) {
|
} else if (memcmp(config, "mqtt.analyzer.us", 17) == 0) {
|
||||||
sprintf(reply, "> %s", strcmp(_prefs->mqtt_slot_preset[0], "analyzer-us") == 0 ? "on" : "off");
|
sprintf(reply, "> %s", strcmp(_mqtt_prefs.mqtt_slot_preset[0], "analyzer-us") == 0 ? "on" : "off");
|
||||||
} else if (memcmp(config, "mqtt.analyzer.eu", 17) == 0) {
|
} else if (memcmp(config, "mqtt.analyzer.eu", 17) == 0) {
|
||||||
sprintf(reply, "> %s", strcmp(_prefs->mqtt_slot_preset[1], "analyzer-eu") == 0 ? "on" : "off");
|
sprintf(reply, "> %s", strcmp(_mqtt_prefs.mqtt_slot_preset[1], "analyzer-eu") == 0 ? "on" : "off");
|
||||||
} else if (sender_timestamp == 0 && memcmp(config, "mqtt.owner", 10) == 0) {
|
} else if (sender_timestamp == 0 && memcmp(config, "mqtt.owner", 10) == 0) {
|
||||||
if (_prefs->mqtt_owner_public_key[0] != '\0') {
|
if (_mqtt_prefs.mqtt_owner_public_key[0] != '\0') {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_owner_public_key);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_owner_public_key);
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "> (not set)");
|
strcpy(reply, "> (not set)");
|
||||||
}
|
}
|
||||||
} else if (sender_timestamp == 0 && memcmp(config, "mqtt.email", 10) == 0) {
|
} else if (sender_timestamp == 0 && memcmp(config, "mqtt.email", 10) == 0) {
|
||||||
if (_prefs->mqtt_email[0] != '\0') {
|
if (_mqtt_prefs.mqtt_email[0] != '\0') {
|
||||||
sprintf(reply, "> %s", _prefs->mqtt_email);
|
sprintf(reply, "> %s", _mqtt_prefs.mqtt_email);
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "> (not set)");
|
strcpy(reply, "> (not set)");
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "mqtt.config.valid", 17) == 0) {
|
} else if (memcmp(config, "mqtt.config.valid", 17) == 0) {
|
||||||
bool valid = MQTTBridge::isConfigValid(_prefs);
|
bool valid = MQTTBridge::isConfigValid(&_mqtt_prefs);
|
||||||
sprintf(reply, "> %s", valid ? "valid" : "invalid");
|
sprintf(reply, "> %s", valid ? "valid" : "invalid");
|
||||||
#endif
|
#endif
|
||||||
} else if (memcmp(config, "alert.hashtag", 13) == 0) {
|
} else if (memcmp(config, "alert.hashtag", 13) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->alert_hashtag[0] ? _prefs->alert_hashtag : "(unset)");
|
sprintf(reply, "> %s", _mqtt_prefs.alert_hashtag[0] ? _mqtt_prefs.alert_hashtag : "(unset)");
|
||||||
} else if (sender_timestamp == 0 && memcmp(config, "alert.psk", 9) == 0) { // from serial command line only
|
} else if (sender_timestamp == 0 && memcmp(config, "alert.psk", 9) == 0) { // from serial command line only
|
||||||
sprintf(reply, "> %s", _prefs->alert_psk_hex[0] ? _prefs->alert_psk_hex : "(unset)");
|
sprintf(reply, "> %s", _mqtt_prefs.alert_psk_hex[0] ? _mqtt_prefs.alert_psk_hex : "(unset)");
|
||||||
} else if (memcmp(config, "alert.region", 12) == 0) {
|
} else if (memcmp(config, "alert.region", 12) == 0) {
|
||||||
sprintf(reply, "> %s", _prefs->alert_region[0] ? _prefs->alert_region : "(unset, using default scope)");
|
sprintf(reply, "> %s", _mqtt_prefs.alert_region[0] ? _mqtt_prefs.alert_region : "(unset, using default scope)");
|
||||||
} else if (memcmp(config, "alert.wifi", 10) == 0) {
|
} else if (memcmp(config, "alert.wifi", 10) == 0) {
|
||||||
sprintf(reply, "> %u min%s", (unsigned)_prefs->alert_wifi_minutes,
|
sprintf(reply, "> %u min%s", (unsigned)_mqtt_prefs.alert_wifi_minutes,
|
||||||
_prefs->alert_wifi_minutes == 0 ? " (disabled)" : "");
|
_mqtt_prefs.alert_wifi_minutes == 0 ? " (disabled)" : "");
|
||||||
} else if (memcmp(config, "alert.mqtt", 10) == 0) {
|
} else if (memcmp(config, "alert.mqtt", 10) == 0) {
|
||||||
sprintf(reply, "> %u min%s", (unsigned)_prefs->alert_mqtt_minutes,
|
sprintf(reply, "> %u min%s", (unsigned)_mqtt_prefs.alert_mqtt_minutes,
|
||||||
_prefs->alert_mqtt_minutes == 0 ? " (disabled)" : "");
|
_mqtt_prefs.alert_mqtt_minutes == 0 ? " (disabled)" : "");
|
||||||
} else if (memcmp(config, "alert.interval", 14) == 0) {
|
} else if (memcmp(config, "alert.interval", 14) == 0) {
|
||||||
sprintf(reply, "> %u min", (unsigned)_prefs->alert_min_interval_min);
|
sprintf(reply, "> %u min", (unsigned)_mqtt_prefs.alert_min_interval_min);
|
||||||
} else if (memcmp(config, "alert", 5) == 0 && (config[5] == 0 || config[5] == '\n' || config[5] == '\r')) {
|
} else if (memcmp(config, "alert", 5) == 0 && (config[5] == 0 || config[5] == '\n' || config[5] == '\r')) {
|
||||||
sprintf(reply, "> %s", _prefs->alert_enabled ? "on" : "off");
|
sprintf(reply, "> %s", _mqtt_prefs.alert_enabled ? "on" : "off");
|
||||||
} else {
|
} else {
|
||||||
handled = false;
|
handled = false;
|
||||||
}
|
}
|
||||||
return handled;
|
return handled;
|
||||||
|
#else
|
||||||
|
(void)sender_timestamp; (void)config; (void)reply;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
||||||
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
if (memcmp(command, "tls.bundletest ", 15) == 0) {
|
if (memcmp(command, "tls.bundletest ", 15) == 0) {
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
@@ -946,7 +983,7 @@ bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command,
|
|||||||
} else {
|
} else {
|
||||||
strcpy(text, "[test] alert channel ok");
|
strcpy(text, "[test] alert channel ok");
|
||||||
}
|
}
|
||||||
if (!_prefs->alert_psk_hex[0]) {
|
if (!_mqtt_prefs.alert_psk_hex[0]) {
|
||||||
strcpy(reply, "Error: alert channel not configured (set alert.psk or set alert.hashtag)");
|
strcpy(reply, "Error: alert channel not configured (set alert.psk or set alert.hashtag)");
|
||||||
} else {
|
} else {
|
||||||
bool ok = _callbacks->sendAlertText(text);
|
bool ok = _callbacks->sendAlertText(text);
|
||||||
@@ -955,4 +992,8 @@ bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
#else
|
||||||
|
(void)sender_timestamp; (void)command; (void)reply;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
@@ -93,6 +93,13 @@ static inline void applyMQTTDefaults(MQTTPrefs* prefs) {
|
|||||||
prefs->timezone_string[sizeof(prefs->timezone_string) - 1] = '\0';
|
prefs->timezone_string[sizeof(prefs->timezone_string) - 1] = '\0';
|
||||||
}
|
}
|
||||||
prefs->timezone_offset = MQTT_DEFAULT_TIMEZONE_OFFSET;
|
prefs->timezone_offset = MQTT_DEFAULT_TIMEZONE_OFFSET;
|
||||||
|
|
||||||
|
// Observer non-MQTT defaults (moved out of NodePrefs/MyMesh ctor in Phase 2).
|
||||||
|
strncpy(prefs->snmp_community, "public", sizeof(prefs->snmp_community) - 1);
|
||||||
|
prefs->radio_watchdog_minutes = 5;
|
||||||
|
prefs->alert_wifi_minutes = 30;
|
||||||
|
prefs->alert_mqtt_minutes = 240;
|
||||||
|
prefs->alert_min_interval_min = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // WITH_MQTT_BRIDGE
|
#endif // WITH_MQTT_BRIDGE
|
||||||
@@ -24,12 +24,12 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Effective MQTT origin: empty mqtt_origin follows node_name; otherwise mqtt_origin override (quotes stripped).
|
// Effective MQTT origin: empty mqtt_origin follows node_name; otherwise mqtt_origin override (quotes stripped).
|
||||||
static void applyEffectiveOrigin(const NodePrefs* prefs, char* dest, size_t dest_size) {
|
static void applyEffectiveOrigin(const NodePrefs* np, const MQTTPrefs* obs, char* dest, size_t dest_size) {
|
||||||
if (!prefs || !dest || dest_size == 0) return;
|
if (!np || !obs || !dest || dest_size == 0) return;
|
||||||
if (prefs->mqtt_origin[0] == '\0') {
|
if (obs->mqtt_origin[0] == '\0') {
|
||||||
strncpy(dest, prefs->node_name, dest_size - 1);
|
strncpy(dest, np->node_name, dest_size - 1);
|
||||||
} else {
|
} else {
|
||||||
strncpy(dest, prefs->mqtt_origin, dest_size - 1);
|
strncpy(dest, obs->mqtt_origin, dest_size - 1);
|
||||||
}
|
}
|
||||||
dest[dest_size - 1] = '\0';
|
dest[dest_size - 1] = '\0';
|
||||||
StrHelper::stripSurroundingQuotes(dest, dest_size);
|
StrHelper::stripSurroundingQuotes(dest, dest_size);
|
||||||
@@ -52,7 +52,7 @@ static bool ntpHostnameEquals(const char* a, const char* b) {
|
|||||||
return strcasecmp(a, b) == 0;
|
return strcasecmp(a, b) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fillNtpServerList(const NodePrefs* prefs, const char* servers[], int& count) {
|
static void fillNtpServerList(const MQTTPrefs* prefs, const char* servers[], int& count) {
|
||||||
count = 0;
|
count = 0;
|
||||||
if (prefs && prefs->mqtt_ntp_server[0] != '\0') {
|
if (prefs && prefs->mqtt_ntp_server[0] != '\0') {
|
||||||
servers[count++] = prefs->mqtt_ntp_server;
|
servers[count++] = prefs->mqtt_ntp_server;
|
||||||
@@ -72,31 +72,31 @@ static void fillNtpServerList(const NodePrefs* prefs, const char* servers[], int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* MQTTBridge::effectiveNtpPrimary(const NodePrefs* prefs) {
|
const char* MQTTBridge::effectiveNtpPrimary(const MQTTPrefs* obs) {
|
||||||
if (prefs && prefs->mqtt_ntp_server[0] != '\0') {
|
if (obs && obs->mqtt_ntp_server[0] != '\0') {
|
||||||
return prefs->mqtt_ntp_server;
|
return obs->mqtt_ntp_server;
|
||||||
}
|
}
|
||||||
return kNtpBuiltinFallbacks[0];
|
return kNtpBuiltinFallbacks[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTTBridge::refreshOriginFromPrefs() {
|
void MQTTBridge::refreshOriginFromPrefs() {
|
||||||
if (!_prefs) return;
|
if (!_prefs) return;
|
||||||
applyEffectiveOrigin(_prefs, _origin, sizeof(_origin));
|
applyEffectiveOrigin(_prefs, _obs, _origin, sizeof(_origin));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTTBridge::getEffectiveMqttOrigin(const NodePrefs* prefs, char* buf, size_t buf_size) {
|
void MQTTBridge::getEffectiveMqttOrigin(const NodePrefs* np, const MQTTPrefs* obs, char* buf, size_t buf_size) {
|
||||||
if (!buf || buf_size == 0) return;
|
if (!buf || buf_size == 0) return;
|
||||||
if (!prefs) {
|
if (!np || !obs) {
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
applyEffectiveOrigin(prefs, buf, buf_size);
|
applyEffectiveOrigin(np, obs, buf, buf_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to check if WiFi credentials are valid
|
// Helper function to check if WiFi credentials are valid
|
||||||
static bool isWiFiConfigValid(const NodePrefs* prefs) {
|
static bool isWiFiConfigValid(const MQTTPrefs* obs) {
|
||||||
// Check if WiFi SSID is configured (not empty)
|
// Check if WiFi SSID is configured (not empty)
|
||||||
if (strlen(prefs->wifi_ssid) == 0) {
|
if (!obs || strlen(obs->wifi_ssid) == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,13 +107,13 @@ static bool isWiFiConfigValid(const NodePrefs* prefs) {
|
|||||||
|
|
||||||
#ifdef WITH_MQTT_BRIDGE
|
#ifdef WITH_MQTT_BRIDGE
|
||||||
|
|
||||||
bool MQTTBridge::isConfigValid(const NodePrefs* prefs) {
|
bool MQTTBridge::isConfigValid(const MQTTPrefs* obs) {
|
||||||
if (!prefs || !isWiFiConfigValid(prefs)) return false;
|
if (!obs || !isWiFiConfigValid(obs)) return false;
|
||||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||||
const char* preset_name = prefs->mqtt_slot_preset[i];
|
const char* preset_name = obs->mqtt_slot_preset[i];
|
||||||
if (preset_name[0] == '\0' || strcmp(preset_name, MQTT_PRESET_NONE) == 0) continue;
|
if (preset_name[0] == '\0' || strcmp(preset_name, MQTT_PRESET_NONE) == 0) continue;
|
||||||
if (strcmp(preset_name, MQTT_PRESET_CUSTOM) == 0) {
|
if (strcmp(preset_name, MQTT_PRESET_CUSTOM) == 0) {
|
||||||
if (prefs->mqtt_slot_host[i][0] != '\0' && prefs->mqtt_slot_port[i] != 0) return true;
|
if (obs->mqtt_slot_host[i][0] != '\0' && obs->mqtt_slot_port[i] != 0) return true;
|
||||||
} else if (findMQTTPreset(preset_name) != nullptr) {
|
} else if (findMQTTPreset(preset_name) != nullptr) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -194,9 +194,9 @@ unsigned long MQTTBridge::getWifiConnectedAtMillis() {
|
|||||||
return s_wifi_connected_at;
|
return s_wifi_connected_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTTBridge::formatMqttStatusReply(char* buf, size_t bufsize, const NodePrefs* prefs) {
|
void MQTTBridge::formatMqttStatusReply(char* buf, size_t bufsize, const MQTTPrefs* obs) {
|
||||||
if (buf == nullptr || bufsize == 0) return;
|
if (buf == nullptr || bufsize == 0) return;
|
||||||
const char* msgs = (prefs->mqtt_status_enabled) ? "on" : "off";
|
const char* msgs = (obs && obs->mqtt_status_enabled) ? "on" : "off";
|
||||||
if (s_mqtt_bridge_instance == nullptr || !s_mqtt_bridge_instance->_initialized) {
|
if (s_mqtt_bridge_instance == nullptr || !s_mqtt_bridge_instance->_initialized) {
|
||||||
snprintf(buf, bufsize, "> msgs: %s (bridge not running)", msgs);
|
snprintf(buf, bufsize, "> msgs: %s (bridge not running)", msgs);
|
||||||
return;
|
return;
|
||||||
@@ -384,11 +384,12 @@ void MQTTBridge::formatSlotDiagReply(char* buf, size_t bufsize, int slot_index)
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Constructor
|
// Constructor
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc, mesh::LocalIdentity *identity)
|
MQTTBridge::MQTTBridge(NodePrefs *prefs, MQTTPrefs *obs, mesh::PacketManager *mgr, mesh::RTCClock *rtc, mesh::LocalIdentity *identity)
|
||||||
: BridgeBase(prefs, mgr, rtc),
|
: BridgeBase(prefs, mgr, rtc),
|
||||||
|
_obs(obs),
|
||||||
_queue_count(0),
|
_queue_count(0),
|
||||||
_last_status_publish(0), _last_status_retry(0), _status_interval(300000),
|
_last_status_publish(0), _last_status_retry(0), _status_interval(300000),
|
||||||
_ntp_client(_ntp_udp, effectiveNtpPrimary(prefs), 0, 60000), _last_ntp_sync(0), _ntp_synced(false), _ntp_sync_pending(false), _slots_setup_done(false), _max_active_slots(RUNTIME_MQTT_SLOTS),
|
_ntp_client(_ntp_udp, effectiveNtpPrimary(obs), 0, 60000), _last_ntp_sync(0), _ntp_synced(false), _ntp_sync_pending(false), _slots_setup_done(false), _max_active_slots(RUNTIME_MQTT_SLOTS),
|
||||||
_ntp_force_requested(false), _ntp_force_done(false), _ntp_force_result(false),
|
_ntp_force_requested(false), _ntp_force_done(false), _ntp_force_result(false),
|
||||||
_ntp_diag_requested(false), _ntp_diag_done(false), _ntp_diag_count(0),
|
_ntp_diag_requested(false), _ntp_diag_done(false), _ntp_diag_count(0),
|
||||||
// Default to UTC; setRules() will be called from syncTimeWithNTP when a
|
// Default to UTC; setRules() will be called from syncTimeWithNTP when a
|
||||||
@@ -539,14 +540,14 @@ void MQTTBridge::begin() {
|
|||||||
MQTT_DEBUG_PRINTLN("Max active slots: %d", _max_active_slots);
|
MQTT_DEBUG_PRINTLN("Max active slots: %d", _max_active_slots);
|
||||||
|
|
||||||
// Check if WiFi credentials are configured first
|
// Check if WiFi credentials are configured first
|
||||||
if (!isWiFiConfigValid(_prefs)) {
|
if (!isWiFiConfigValid(_obs)) {
|
||||||
MQTT_DEBUG_PRINTLN("MQTT Bridge initialization skipped - WiFi credentials not configured");
|
MQTT_DEBUG_PRINTLN("MQTT Bridge initialization skipped - WiFi credentials not configured");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshOriginFromPrefs();
|
refreshOriginFromPrefs();
|
||||||
|
|
||||||
strncpy(_iata, _prefs->mqtt_iata, sizeof(_iata) - 1);
|
strncpy(_iata, _obs->mqtt_iata, sizeof(_iata) - 1);
|
||||||
_iata[sizeof(_iata) - 1] = '\0';
|
_iata[sizeof(_iata) - 1] = '\0';
|
||||||
|
|
||||||
StrHelper::stripSurroundingQuotes(_iata, sizeof(_iata));
|
StrHelper::stripSurroundingQuotes(_iata, sizeof(_iata));
|
||||||
@@ -557,17 +558,17 @@ void MQTTBridge::begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update enabled flags from preferences
|
// Update enabled flags from preferences
|
||||||
_status_enabled = _prefs->mqtt_status_enabled;
|
_status_enabled = _obs->mqtt_status_enabled;
|
||||||
_packets_enabled = _prefs->mqtt_packets_enabled;
|
_packets_enabled = _obs->mqtt_packets_enabled;
|
||||||
_raw_enabled = _prefs->mqtt_raw_enabled;
|
_raw_enabled = _obs->mqtt_raw_enabled;
|
||||||
_rx_enabled = _prefs->mqtt_rx_enabled;
|
_rx_enabled = _obs->mqtt_rx_enabled;
|
||||||
_tx_mode = _prefs->mqtt_tx_enabled; // 0=off, 1=all, 2=advert
|
_tx_mode = _obs->mqtt_tx_enabled; // 0=off, 1=all, 2=advert
|
||||||
// Set status interval to 5 minutes (300000 ms), or use preference if set and valid
|
// Set status interval to 5 minutes (300000 ms), or use preference if set and valid
|
||||||
if (_prefs->mqtt_status_interval >= 1000 && _prefs->mqtt_status_interval <= 3600000) {
|
if (_obs->mqtt_status_interval >= 1000 && _obs->mqtt_status_interval <= 3600000) {
|
||||||
_status_interval = _prefs->mqtt_status_interval;
|
_status_interval = _obs->mqtt_status_interval;
|
||||||
} else {
|
} else {
|
||||||
// Invalid or uninitialized value - fix it in preferences and use default
|
// Invalid or uninitialized value - fix it in preferences and use default
|
||||||
_prefs->mqtt_status_interval = 300000; // Fix the preference value
|
_obs->mqtt_status_interval = 300000; // Fix the preference value
|
||||||
_status_interval = 300000; // 5 minutes default
|
_status_interval = 300000; // 5 minutes default
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,12 +579,12 @@ void MQTTBridge::begin() {
|
|||||||
|
|
||||||
// Apply slot presets from preferences
|
// Apply slot presets from preferences
|
||||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||||
const char* preset_name = _prefs->mqtt_slot_preset[i];
|
const char* preset_name = _obs->mqtt_slot_preset[i];
|
||||||
if (preset_name[0] != '\0' && strcmp(preset_name, MQTT_PRESET_NONE) != 0) {
|
if (preset_name[0] != '\0' && strcmp(preset_name, MQTT_PRESET_NONE) != 0) {
|
||||||
if (strcmp(preset_name, MQTT_PRESET_CUSTOM) == 0) {
|
if (strcmp(preset_name, MQTT_PRESET_CUSTOM) == 0) {
|
||||||
// Custom broker: copy host/port/username/password from prefs
|
// Custom broker: copy host/port/username/password from prefs
|
||||||
_slots[i].preset = nullptr;
|
_slots[i].preset = nullptr;
|
||||||
strncpy(_slots[i].host, _prefs->mqtt_slot_host[i], sizeof(_slots[i].host) - 1);
|
strncpy(_slots[i].host, _obs->mqtt_slot_host[i], sizeof(_slots[i].host) - 1);
|
||||||
_slots[i].host[sizeof(_slots[i].host) - 1] = '\0';
|
_slots[i].host[sizeof(_slots[i].host) - 1] = '\0';
|
||||||
if (strlen(_slots[i].host) == 0) {
|
if (strlen(_slots[i].host) == 0) {
|
||||||
MQTT_DEBUG_PRINTLN("MQTT%d: custom preset has no server configured, disabling", i + 1);
|
MQTT_DEBUG_PRINTLN("MQTT%d: custom preset has no server configured, disabling", i + 1);
|
||||||
@@ -591,12 +592,12 @@ void MQTTBridge::begin() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_slots[i].enabled = true;
|
_slots[i].enabled = true;
|
||||||
_slots[i].port = _prefs->mqtt_slot_port[i];
|
_slots[i].port = _obs->mqtt_slot_port[i];
|
||||||
strncpy(_slots[i].username, _prefs->mqtt_slot_username[i], sizeof(_slots[i].username) - 1);
|
strncpy(_slots[i].username, _obs->mqtt_slot_username[i], sizeof(_slots[i].username) - 1);
|
||||||
_slots[i].username[sizeof(_slots[i].username) - 1] = '\0';
|
_slots[i].username[sizeof(_slots[i].username) - 1] = '\0';
|
||||||
strncpy(_slots[i].password, _prefs->mqtt_slot_password[i], sizeof(_slots[i].password) - 1);
|
strncpy(_slots[i].password, _obs->mqtt_slot_password[i], sizeof(_slots[i].password) - 1);
|
||||||
_slots[i].password[sizeof(_slots[i].password) - 1] = '\0';
|
_slots[i].password[sizeof(_slots[i].password) - 1] = '\0';
|
||||||
strncpy(_slots[i].audience, _prefs->mqtt_slot_audience[i], sizeof(_slots[i].audience) - 1);
|
strncpy(_slots[i].audience, _obs->mqtt_slot_audience[i], sizeof(_slots[i].audience) - 1);
|
||||||
_slots[i].audience[sizeof(_slots[i].audience) - 1] = '\0';
|
_slots[i].audience[sizeof(_slots[i].audience) - 1] = '\0';
|
||||||
} else {
|
} else {
|
||||||
const MQTTPresetDef* preset = findMQTTPreset(preset_name);
|
const MQTTPresetDef* preset = findMQTTPreset(preset_name);
|
||||||
@@ -604,9 +605,9 @@ void MQTTBridge::begin() {
|
|||||||
_slots[i].enabled = true;
|
_slots[i].enabled = true;
|
||||||
_slots[i].preset = preset;
|
_slots[i].preset = preset;
|
||||||
if (mqttPresetNeedsSlotCredentials(preset)) {
|
if (mqttPresetNeedsSlotCredentials(preset)) {
|
||||||
strncpy(_slots[i].username, _prefs->mqtt_slot_username[i], sizeof(_slots[i].username) - 1);
|
strncpy(_slots[i].username, _obs->mqtt_slot_username[i], sizeof(_slots[i].username) - 1);
|
||||||
_slots[i].username[sizeof(_slots[i].username) - 1] = '\0';
|
_slots[i].username[sizeof(_slots[i].username) - 1] = '\0';
|
||||||
strncpy(_slots[i].password, _prefs->mqtt_slot_password[i], sizeof(_slots[i].password) - 1);
|
strncpy(_slots[i].password, _obs->mqtt_slot_password[i], sizeof(_slots[i].password) - 1);
|
||||||
_slots[i].password[sizeof(_slots[i].password) - 1] = '\0';
|
_slots[i].password[sizeof(_slots[i].password) - 1] = '\0';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -702,7 +703,7 @@ void MQTTBridge::begin() {
|
|||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.setAutoReconnect(true);
|
WiFi.setAutoReconnect(true);
|
||||||
WiFi.setAutoConnect(true);
|
WiFi.setAutoConnect(true);
|
||||||
WiFi.begin(_prefs->wifi_ssid, _prefs->wifi_password);
|
WiFi.begin(_obs->wifi_ssid, _obs->wifi_password);
|
||||||
|
|
||||||
// NOTE: Slot setup deferred until after NTP sync in loop()
|
// NOTE: Slot setup deferred until after NTP sync in loop()
|
||||||
#endif
|
#endif
|
||||||
@@ -845,7 +846,7 @@ void MQTTBridge::initializeWiFiInTask() {
|
|||||||
// When already connected, the deferred slot setup still fires in mqttTaskLoop()
|
// When already connected, the deferred slot setup still fires in mqttTaskLoop()
|
||||||
// because _ntp_synced persists across end() (only _slots_setup_done is reset).
|
// because _ntp_synced persists across end() (only _slots_setup_done is reset).
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
WiFi.begin(_prefs->wifi_ssid, _prefs->wifi_password);
|
WiFi.begin(_obs->wifi_ssid, _obs->wifi_password);
|
||||||
} else if (!_ntp_synced && !_ntp_sync_pending) {
|
} else if (!_ntp_synced && !_ntp_sync_pending) {
|
||||||
_ntp_sync_pending = true; // already connected but never synced — kick NTP now
|
_ntp_sync_pending = true; // already connected but never synced — kick NTP now
|
||||||
}
|
}
|
||||||
@@ -976,8 +977,8 @@ void MQTTBridge::mqttTaskLoop() {
|
|||||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||||
if (_slot_reconfigure_pending[i]) {
|
if (_slot_reconfigure_pending[i]) {
|
||||||
_slot_reconfigure_pending[i] = false;
|
_slot_reconfigure_pending[i] = false;
|
||||||
MQTT_DEBUG_PRINTLN("Applying deferred reconfigure for MQTT%d (preset: %s)", i + 1, _prefs->mqtt_slot_preset[i]);
|
MQTT_DEBUG_PRINTLN("Applying deferred reconfigure for MQTT%d (preset: %s)", i + 1, _obs->mqtt_slot_preset[i]);
|
||||||
applySlotPreset(i, _prefs->mqtt_slot_preset[i]);
|
applySlotPreset(i, _obs->mqtt_slot_preset[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -990,9 +991,9 @@ void MQTTBridge::mqttTaskLoop() {
|
|||||||
#ifdef WITH_SNMP
|
#ifdef WITH_SNMP
|
||||||
// SNMP agent loop — process incoming UDP requests
|
// SNMP agent loop — process incoming UDP requests
|
||||||
if (_snmp_agent) {
|
if (_snmp_agent) {
|
||||||
if (!_snmp_agent->isRunning() && WiFi.isConnected() && _prefs->snmp_enabled) {
|
if (!_snmp_agent->isRunning() && WiFi.isConnected() && _obs->snmp_enabled) {
|
||||||
_snmp_agent->begin(_prefs->snmp_community);
|
_snmp_agent->begin(_obs->snmp_community);
|
||||||
MQTT_DEBUG_PRINTLN("SNMP agent started on port 161 (community: %s)", _prefs->snmp_community);
|
MQTT_DEBUG_PRINTLN("SNMP agent started on port 161 (community: %s)", _obs->snmp_community);
|
||||||
}
|
}
|
||||||
if (_snmp_agent->isRunning()) {
|
if (_snmp_agent->isRunning()) {
|
||||||
// Update MQTT stats from this core
|
// Update MQTT stats from this core
|
||||||
@@ -1626,8 +1627,8 @@ bool MQTTBridge::createSlotAuthToken(int index) {
|
|||||||
// Prepare owner key
|
// Prepare owner key
|
||||||
const char* owner_key = nullptr;
|
const char* owner_key = nullptr;
|
||||||
char owner_key_uppercase[65];
|
char owner_key_uppercase[65];
|
||||||
if (_prefs->mqtt_owner_public_key[0] != '\0') {
|
if (_obs->mqtt_owner_public_key[0] != '\0') {
|
||||||
strncpy(owner_key_uppercase, _prefs->mqtt_owner_public_key, sizeof(owner_key_uppercase) - 1);
|
strncpy(owner_key_uppercase, _obs->mqtt_owner_public_key, sizeof(owner_key_uppercase) - 1);
|
||||||
owner_key_uppercase[sizeof(owner_key_uppercase) - 1] = '\0';
|
owner_key_uppercase[sizeof(owner_key_uppercase) - 1] = '\0';
|
||||||
for (int i = 0; owner_key_uppercase[i]; i++) {
|
for (int i = 0; owner_key_uppercase[i]; i++) {
|
||||||
owner_key_uppercase[i] = toupper(owner_key_uppercase[i]);
|
owner_key_uppercase[i] = toupper(owner_key_uppercase[i]);
|
||||||
@@ -1637,7 +1638,7 @@ bool MQTTBridge::createSlotAuthToken(int index) {
|
|||||||
|
|
||||||
char client_version[64];
|
char client_version[64];
|
||||||
getClientVersion(client_version, sizeof(client_version));
|
getClientVersion(client_version, sizeof(client_version));
|
||||||
const char* email = (_prefs->mqtt_email[0] != '\0') ? _prefs->mqtt_email : nullptr;
|
const char* email = (_obs->mqtt_email[0] != '\0') ? _obs->mqtt_email : nullptr;
|
||||||
|
|
||||||
unsigned long current_time = time(nullptr);
|
unsigned long current_time = time(nullptr);
|
||||||
// Stagger token expiry per slot to avoid simultaneous renewal/reconnect
|
// Stagger token expiry per slot to avoid simultaneous renewal/reconnect
|
||||||
@@ -1712,7 +1713,7 @@ bool MQTTBridge::publishToAllSlots(const char* topic, const char* payload, bool
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
bool MQTTBridge::substituteTopicTemplate(const char* tmpl, MQTTMessageType type, int slot_index, char* buf, size_t buf_size) {
|
bool MQTTBridge::substituteTopicTemplate(const char* tmpl, MQTTMessageType type, int slot_index, char* buf, size_t buf_size) {
|
||||||
const char* type_str = (type == MSG_STATUS) ? "status" : (type == MSG_PACKETS) ? "packets" : "raw";
|
const char* type_str = (type == MSG_STATUS) ? "status" : (type == MSG_PACKETS) ? "packets" : "raw";
|
||||||
const char* token = _prefs->mqtt_slot_token[slot_index];
|
const char* token = _obs->mqtt_slot_token[slot_index];
|
||||||
|
|
||||||
size_t out = 0;
|
size_t out = 0;
|
||||||
const char* p = tmpl;
|
const char* p = tmpl;
|
||||||
@@ -1762,7 +1763,7 @@ bool MQTTBridge::buildTopicForSlot(int index, MQTTMessageType type, char* topic_
|
|||||||
if (slot.preset->topic_style == MQTT_TOPIC_MESHRANK) {
|
if (slot.preset->topic_style == MQTT_TOPIC_MESHRANK) {
|
||||||
// MeshRank: packets only, uses per-slot token in topic path
|
// MeshRank: packets only, uses per-slot token in topic path
|
||||||
if (type != MSG_PACKETS) return false;
|
if (type != MSG_PACKETS) return false;
|
||||||
const char* token = _prefs->mqtt_slot_token[index];
|
const char* token = _obs->mqtt_slot_token[index];
|
||||||
if (!token || token[0] == '\0') return false;
|
if (!token || token[0] == '\0') return false;
|
||||||
snprintf(topic_buf, buf_size, "meshrank/uplink/%s/%s/packets", token, _device_id);
|
snprintf(topic_buf, buf_size, "meshrank/uplink/%s/%s/packets", token, _device_id);
|
||||||
return true;
|
return true;
|
||||||
@@ -1775,8 +1776,8 @@ bool MQTTBridge::buildTopicForSlot(int index, MQTTMessageType type, char* topic_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom slots: use topic template if set, otherwise default meshcore format
|
// Custom slots: use topic template if set, otherwise default meshcore format
|
||||||
if (_prefs->mqtt_slot_topic[index][0] != '\0') {
|
if (_obs->mqtt_slot_topic[index][0] != '\0') {
|
||||||
return substituteTopicTemplate(_prefs->mqtt_slot_topic[index], type, index, topic_buf, buf_size);
|
return substituteTopicTemplate(_obs->mqtt_slot_topic[index], type, index, topic_buf, buf_size);
|
||||||
}
|
}
|
||||||
// Default: meshcore format
|
// Default: meshcore format
|
||||||
if (!isIATAValid()) return false;
|
if (!isIATAValid()) return false;
|
||||||
@@ -1936,9 +1937,9 @@ void MQTTBridge::applySlotPreset(int slot_index, const char* preset_name) {
|
|||||||
slot.enabled = true;
|
slot.enabled = true;
|
||||||
slot.preset = preset;
|
slot.preset = preset;
|
||||||
if (mqttPresetNeedsSlotCredentials(preset)) {
|
if (mqttPresetNeedsSlotCredentials(preset)) {
|
||||||
strncpy(slot.username, _prefs->mqtt_slot_username[slot_index], sizeof(slot.username) - 1);
|
strncpy(slot.username, _obs->mqtt_slot_username[slot_index], sizeof(slot.username) - 1);
|
||||||
slot.username[sizeof(slot.username) - 1] = '\0';
|
slot.username[sizeof(slot.username) - 1] = '\0';
|
||||||
strncpy(slot.password, _prefs->mqtt_slot_password[slot_index], sizeof(slot.password) - 1);
|
strncpy(slot.password, _obs->mqtt_slot_password[slot_index], sizeof(slot.password) - 1);
|
||||||
slot.password[sizeof(slot.password) - 1] = '\0';
|
slot.password[sizeof(slot.password) - 1] = '\0';
|
||||||
}
|
}
|
||||||
if (_initialized) {
|
if (_initialized) {
|
||||||
@@ -1972,7 +1973,7 @@ void MQTTBridge::setSlotCustomBroker(int slot_index, const char* host, uint16_t
|
|||||||
|
|
||||||
void MQTTBridge::checkConfigurationMismatch() {
|
void MQTTBridge::checkConfigurationMismatch() {
|
||||||
// Warn if packets are enabled but both rx and tx are off — nothing will be published
|
// Warn if packets are enabled but both rx and tx are off — nothing will be published
|
||||||
if (_prefs->mqtt_packets_enabled && !_prefs->mqtt_rx_enabled && _prefs->mqtt_tx_enabled == 0) {
|
if (_obs->mqtt_packets_enabled && !_obs->mqtt_rx_enabled && _obs->mqtt_tx_enabled == 0) {
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
if (_last_config_warning == 0 || (now - _last_config_warning > CONFIG_WARNING_INTERVAL)) {
|
if (_last_config_warning == 0 || (now - _last_config_warning > CONFIG_WARNING_INTERVAL)) {
|
||||||
MQTT_DEBUG_PRINTLN("MQTT: Both mqtt.rx and mqtt.tx are off — no packets will be published. Run 'set mqtt.rx on' or 'set mqtt.tx on' to fix.");
|
MQTT_DEBUG_PRINTLN("MQTT: Both mqtt.rx and mqtt.tx are off — no packets will be published. Run 'set mqtt.rx on' or 'set mqtt.tx on' to fix.");
|
||||||
@@ -2010,7 +2011,7 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
|
|||||||
_wifi_reconnect_backoff_attempt = 0;
|
_wifi_reconnect_backoff_attempt = 0;
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
wifi_ps_type_t ps_mode;
|
wifi_ps_type_t ps_mode;
|
||||||
uint8_t ps_pref = _prefs->wifi_power_save;
|
uint8_t ps_pref = _obs->wifi_power_save;
|
||||||
if (ps_pref == 1) {
|
if (ps_pref == 1) {
|
||||||
ps_mode = WIFI_PS_NONE;
|
ps_mode = WIFI_PS_NONE;
|
||||||
} else if (ps_pref == 2) {
|
} else if (ps_pref == 2) {
|
||||||
@@ -2054,7 +2055,7 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
|
|||||||
_wifi_reconnect_backoff_attempt++;
|
_wifi_reconnect_backoff_attempt++;
|
||||||
}
|
}
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
WiFi.begin(_prefs->wifi_ssid, _prefs->wifi_password);
|
WiFi.begin(_obs->wifi_ssid, _obs->wifi_password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_last_wifi_status = current_wifi_status;
|
_last_wifi_status = current_wifi_status;
|
||||||
@@ -2063,7 +2064,7 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MQTTBridge::isReady() const {
|
bool MQTTBridge::isReady() const {
|
||||||
return _initialized && isWiFiConfigValid(_prefs);
|
return _initialized && isWiFiConfigValid(_obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MQTTBridge::isIATAValid() const {
|
bool MQTTBridge::isIATAValid() const {
|
||||||
@@ -2081,7 +2082,7 @@ bool MQTTBridge::isSlotReady(int index, char* reason_buf, size_t reason_size) co
|
|||||||
|
|
||||||
if (slot.preset) {
|
if (slot.preset) {
|
||||||
if (slot.preset->topic_style == MQTT_TOPIC_MESHRANK) {
|
if (slot.preset->topic_style == MQTT_TOPIC_MESHRANK) {
|
||||||
if (_prefs->mqtt_slot_token[index][0] == '\0') {
|
if (_obs->mqtt_slot_token[index][0] == '\0') {
|
||||||
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.token <your_token>", index + 1);
|
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.token <your_token>", index + 1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2092,18 +2093,18 @@ bool MQTTBridge::isSlotReady(int index, char* reason_buf, size_t reason_size) co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mqttPresetNeedsSlotCredentials(slot.preset)) {
|
if (mqttPresetNeedsSlotCredentials(slot.preset)) {
|
||||||
if (_prefs->mqtt_slot_username[index][0] == '\0') {
|
if (_obs->mqtt_slot_username[index][0] == '\0') {
|
||||||
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.username <user>", index + 1);
|
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.username <user>", index + 1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (_prefs->mqtt_slot_password[index][0] == '\0') {
|
if (_obs->mqtt_slot_password[index][0] == '\0') {
|
||||||
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.password <pass>", index + 1);
|
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.password <pass>", index + 1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Custom slot without a topic template uses meshcore format, needs IATA
|
// Custom slot without a topic template uses meshcore format, needs IATA
|
||||||
if (_prefs->mqtt_slot_topic[index][0] == '\0' && !isIATAValid()) {
|
if (_obs->mqtt_slot_topic[index][0] == '\0' && !isIATAValid()) {
|
||||||
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt.iata <airport_code> or set mqtt%d.topic <template>", index + 1);
|
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt.iata <airport_code> or set mqtt%d.topic <template>", index + 1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2154,7 +2155,7 @@ void MQTTBridge::loop() {
|
|||||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||||
if (_slot_reconfigure_pending[i]) {
|
if (_slot_reconfigure_pending[i]) {
|
||||||
_slot_reconfigure_pending[i] = false;
|
_slot_reconfigure_pending[i] = false;
|
||||||
applySlotPreset(i, _prefs->mqtt_slot_preset[i]);
|
applySlotPreset(i, _obs->mqtt_slot_preset[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2237,7 +2238,7 @@ void MQTTBridge::loop() {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void MQTTBridge::onPacketReceived(mesh::Packet *packet) {
|
void MQTTBridge::onPacketReceived(mesh::Packet *packet) {
|
||||||
if (!_initialized || !_prefs->mqtt_packets_enabled || !_prefs->mqtt_rx_enabled) return;
|
if (!_initialized || !_obs->mqtt_packets_enabled || !_obs->mqtt_rx_enabled) return;
|
||||||
|
|
||||||
// Check if we have any enabled slots to send to
|
// Check if we have any enabled slots to send to
|
||||||
bool has_valid_slots = false;
|
bool has_valid_slots = false;
|
||||||
@@ -2254,8 +2255,8 @@ void MQTTBridge::onPacketReceived(mesh::Packet *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MQTTBridge::sendPacket(mesh::Packet *packet) {
|
void MQTTBridge::sendPacket(mesh::Packet *packet) {
|
||||||
uint8_t tx_mode = _prefs->mqtt_tx_enabled; // Read live from prefs (no restart needed)
|
uint8_t tx_mode = _obs->mqtt_tx_enabled; // Read live from prefs (no restart needed)
|
||||||
if (!_initialized || !_prefs->mqtt_packets_enabled || tx_mode == 0) return;
|
if (!_initialized || !_obs->mqtt_packets_enabled || tx_mode == 0) return;
|
||||||
|
|
||||||
// Advert mode: only queue self-originated advert packets
|
// Advert mode: only queue self-originated advert packets
|
||||||
if (tx_mode == 2) {
|
if (tx_mode == 2) {
|
||||||
@@ -2910,7 +2911,7 @@ void MQTTBridge::refreshNTP() {
|
|||||||
// Lightweight periodic refresh: just restart SNTP which runs async in the background.
|
// Lightweight periodic refresh: just restart SNTP which runs async in the background.
|
||||||
// No blocking DNS, no UDP sockets, no retry loops on the MQTT task loop.
|
// No blocking DNS, no UDP sockets, no retry loops on the MQTT task loop.
|
||||||
// The heavy syncTimeWithNTP() is only used for initial sync and WiFi reconnect recovery.
|
// The heavy syncTimeWithNTP() is only used for initial sync and WiFi reconnect recovery.
|
||||||
configTime(0, 0, effectiveNtpPrimary(_prefs));
|
configTime(0, 0, effectiveNtpPrimary(_obs));
|
||||||
_last_ntp_sync = millis();
|
_last_ntp_sync = millis();
|
||||||
MQTT_DEBUG_PRINTLN("NTP refresh triggered (async SNTP)");
|
MQTT_DEBUG_PRINTLN("NTP refresh triggered (async SNTP)");
|
||||||
}
|
}
|
||||||
@@ -2939,10 +2940,10 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) {
|
|||||||
if (primary_only) {
|
if (primary_only) {
|
||||||
// Validation path (e.g. set mqtt.ntp): test only the configured primary so a
|
// Validation path (e.g. set mqtt.ntp): test only the configured primary so a
|
||||||
// typo fails fast instead of walking the entire fallback list.
|
// typo fails fast instead of walking the entire fallback list.
|
||||||
servers[0] = effectiveNtpPrimary(_prefs);
|
servers[0] = effectiveNtpPrimary(_obs);
|
||||||
server_count = 1;
|
server_count = 1;
|
||||||
} else {
|
} else {
|
||||||
fillNtpServerList(_prefs, servers, server_count);
|
fillNtpServerList(_obs, servers, server_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ntp_ok = false;
|
bool ntp_ok = false;
|
||||||
@@ -3041,15 +3042,15 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) {
|
|||||||
// Reuses the inline _timezone_storage via setRules() instead of
|
// Reuses the inline _timezone_storage via setRules() instead of
|
||||||
// deleting/newing a Timezone, which was a per-change heap alloc pair.
|
// deleting/newing a Timezone, which was a per-change heap alloc pair.
|
||||||
static char last_timezone[64] = "";
|
static char last_timezone[64] = "";
|
||||||
if (strcmp(_prefs->timezone_string, last_timezone) != 0) {
|
if (strcmp(_obs->timezone_string, last_timezone) != 0) {
|
||||||
TimeChangeRule dst_rule, std_rule;
|
TimeChangeRule dst_rule, std_rule;
|
||||||
if (!timezoneRulesFromString(_prefs->timezone_string, dst_rule, std_rule)) {
|
if (!timezoneRulesFromString(_obs->timezone_string, dst_rule, std_rule)) {
|
||||||
TimeChangeRule utc = {"UTC", Last, Sun, Mar, 0, 0};
|
TimeChangeRule utc = {"UTC", Last, Sun, Mar, 0, 0};
|
||||||
dst_rule = utc;
|
dst_rule = utc;
|
||||||
std_rule = utc;
|
std_rule = utc;
|
||||||
}
|
}
|
||||||
_timezone_storage.setRules(dst_rule, std_rule);
|
_timezone_storage.setRules(dst_rule, std_rule);
|
||||||
strncpy(last_timezone, _prefs->timezone_string, sizeof(last_timezone) - 1);
|
strncpy(last_timezone, _obs->timezone_string, sizeof(last_timezone) - 1);
|
||||||
last_timezone[sizeof(last_timezone) - 1] = '\0';
|
last_timezone[sizeof(last_timezone) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3089,7 +3090,7 @@ bool MQTTBridge::requestForcedNtpSync(uint32_t timeout_ms) {
|
|||||||
void MQTTBridge::runNtpDiagProbe() {
|
void MQTTBridge::runNtpDiagProbe() {
|
||||||
const char* servers[kMaxNtpServers];
|
const char* servers[kMaxNtpServers];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
fillNtpServerList(_prefs, servers, count);
|
fillNtpServerList(_obs, servers, count);
|
||||||
|
|
||||||
_ntp_client.begin();
|
_ntp_client.begin();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
|
|||||||
@@ -375,8 +375,12 @@ private:
|
|||||||
void logMemoryStatus();
|
void logMemoryStatus();
|
||||||
void refreshOriginFromPrefs();
|
void refreshOriginFromPrefs();
|
||||||
|
|
||||||
|
// Observer config (MQTT/WiFi/timezone/SNMP/alert), persisted to /mqtt_prefs.
|
||||||
|
// _prefs (held by BridgeBase) still provides upstream fields (freq/sf/node_name…).
|
||||||
|
MQTTPrefs* _obs = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc, mesh::LocalIdentity *identity);
|
MQTTBridge(NodePrefs *prefs, MQTTPrefs *obs, mesh::PacketManager *mgr, mesh::RTCClock *rtc, mesh::LocalIdentity *identity);
|
||||||
|
|
||||||
void begin() override;
|
void begin() override;
|
||||||
void end() override;
|
void end() override;
|
||||||
@@ -439,8 +443,8 @@ public:
|
|||||||
const char* getSlotPresetName(int slot_index) const;
|
const char* getSlotPresetName(int slot_index) const;
|
||||||
static int getRuntimeSlotCount() { return RUNTIME_MQTT_SLOTS; }
|
static int getRuntimeSlotCount() { return RUNTIME_MQTT_SLOTS; }
|
||||||
/** Resolved origin for MQTT JSON: node_name when mqtt_origin is empty, else mqtt_origin (with quote stripping). */
|
/** Resolved origin for MQTT JSON: node_name when mqtt_origin is empty, else mqtt_origin (with quote stripping). */
|
||||||
static void getEffectiveMqttOrigin(const NodePrefs* prefs, char* buf, size_t buf_size);
|
static void getEffectiveMqttOrigin(const NodePrefs* np, const MQTTPrefs* obs, char* buf, size_t buf_size);
|
||||||
static const char* effectiveNtpPrimary(const NodePrefs* prefs);
|
static const char* effectiveNtpPrimary(const MQTTPrefs* obs);
|
||||||
/** Sync system clock via NTP. force=true bypasses the 5s post-sync rate limit.
|
/** Sync system clock via NTP. force=true bypasses the 5s post-sync rate limit.
|
||||||
* primary_only=true tests just the effective primary server (no fallback walk) so a
|
* primary_only=true tests just the effective primary server (no fallback walk) so a
|
||||||
* mistyped hostname fails fast instead of blocking through the whole fallback list.
|
* mistyped hostname fails fast instead of blocking through the whole fallback list.
|
||||||
@@ -459,9 +463,9 @@ public:
|
|||||||
* summary in reply; verbose=false fills reply with a compact "<server> ok|fail" list
|
* summary in reply; verbose=false fills reply with a compact "<server> ok|fail" list
|
||||||
* (for LoRa). Returns false if the bridge is not running. */
|
* (for LoRa). Returns false if the bridge is not running. */
|
||||||
bool ntpDiag(char* reply, size_t reply_size, bool verbose);
|
bool ntpDiag(char* reply, size_t reply_size, bool verbose);
|
||||||
static void formatMqttStatusReply(char* buf, size_t bufsize, const NodePrefs* prefs);
|
static void formatMqttStatusReply(char* buf, size_t bufsize, const MQTTPrefs* obs);
|
||||||
/** True when WiFi is set and at least one MQTT slot can run (preset + custom host if needed). */
|
/** True when WiFi is set and at least one MQTT slot can run (preset + custom host if needed). */
|
||||||
static bool isConfigValid(const NodePrefs* prefs);
|
static bool isConfigValid(const MQTTPrefs* obs);
|
||||||
static void formatSlotDiagReply(char* buf, size_t bufsize, int slot_index);
|
static void formatSlotDiagReply(char* buf, size_t bufsize, int slot_index);
|
||||||
static uint8_t getLastWifiDisconnectReason();
|
static uint8_t getLastWifiDisconnectReason();
|
||||||
static unsigned long getLastWifiDisconnectTime();
|
static unsigned long getLastWifiDisconnectTime();
|
||||||
|
|||||||
Reference in new issue
Block a user