mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-07-12 13:08:48 +00:00
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:
@@ -951,21 +951,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_prefs.agc_reset_interval = 7; // 28 seconds (secs/4) — prevents AGC drift on long-running observers
|
||||
#endif
|
||||
_prefs.radio_watchdog_minutes = 5; // 5 minutes default
|
||||
|
||||
// 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
|
||||
// Observer defaults (radio_watchdog, alert.*, snmp.*) moved to applyMQTTDefaults()
|
||||
// in MQTTDefaults.h — they live in /mqtt_prefs now, not NodePrefs.
|
||||
|
||||
// bridge defaults
|
||||
_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));
|
||||
|
||||
// SNMP defaults
|
||||
_prefs.snmp_enabled = 0;
|
||||
StrHelper::strncpy(_prefs.snmp_community, "public", sizeof(_prefs.snmp_community));
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
|
||||
// MQTT slot/IATA/timezone defaults come from /mqtt_prefs via loadPrefs (see MQTTDefaults.h)
|
||||
_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));
|
||||
// MQTT/WiFi/timezone/radio_watchdog defaults live in /mqtt_prefs now (see applyMQTTDefaults).
|
||||
|
||||
_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) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// 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
|
||||
if (bridge) {
|
||||
// Set device public key for MQTT topics
|
||||
@@ -1065,7 +1043,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
// Set stats sources for automatic stats collection
|
||||
bridge->setStatsSources(this, _radio, _cli.getBoard(), _ms);
|
||||
#ifdef WITH_SNMP
|
||||
if (_prefs.snmp_enabled) {
|
||||
if (_cli.getObserverPrefs()->snmp_enabled) {
|
||||
_snmp_agent.setNodeName(_prefs.node_name);
|
||||
_snmp_agent.setFirmwareVersion(getFirmwareVer());
|
||||
bridge->setSNMPAgent(&_snmp_agent);
|
||||
@@ -1082,8 +1060,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
// Passing `this` as the callbacks lets the reporter resolve a TransportKey
|
||||
// scope (alert.region override, falling back to default_scope) so alert
|
||||
// floods ride the same scope as adverts/channel messages.
|
||||
_alerter.begin(&_prefs, this, this);
|
||||
#if defined(WITH_MQTT_BRIDGE)
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_alerter.begin(&_prefs, _cli.getObserverPrefs(), this, this);
|
||||
_alerter.setBridge(bridge);
|
||||
#endif
|
||||
|
||||
@@ -1118,12 +1096,15 @@ bool MyMesh::resolveAlertScope(TransportKey& dest) {
|
||||
// RegionMap so the operator can name a region that doesn't exist yet
|
||||
// without polluting region_map state — we just silently fall through
|
||||
// to default_scope on miss.
|
||||
if (_prefs.alert_region[0]) {
|
||||
auto r = region_map.findByNamePrefix(_prefs.alert_region);
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
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()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!default_scope.isNull()) {
|
||||
dest = default_scope;
|
||||
return true;
|
||||
@@ -1473,7 +1454,9 @@ void MyMesh::loop() {
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_alerter.onLoop(now);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_SNMP
|
||||
// Push radio stats to SNMP agent every 2 seconds
|
||||
|
||||
@@ -132,7 +132,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
#ifdef WITH_SNMP
|
||||
MeshSNMPAgent _snmp_agent;
|
||||
#endif
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
AlertReporter _alerter;
|
||||
#endif
|
||||
|
||||
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);
|
||||
@@ -168,9 +170,11 @@ protected:
|
||||
int getAGCResetInterval() const override {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
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 {
|
||||
return _prefs.multi_acks;
|
||||
}
|
||||
@@ -215,8 +219,10 @@ public:
|
||||
// CommonCLICallbacks
|
||||
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(); }
|
||||
bool sendAlertText(const char* text) override { return _alerter.sendText(text); }
|
||||
#endif
|
||||
bool resolveAlertScope(TransportKey& dest) override;
|
||||
bool formatFileSystem() override;
|
||||
void sendSelfAdvertisement(int delay_millis, bool flood) override;
|
||||
@@ -253,7 +259,7 @@ public:
|
||||
void setBridgeState(bool enable) override {
|
||||
if (!bridge) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
bridge = new MQTTBridge(&_prefs, _mgr, getRTCClock(), &self_id);
|
||||
bridge = new MQTTBridge(&_prefs, _cli.getObserverPrefs(), _mgr, getRTCClock(), &self_id);
|
||||
#endif
|
||||
if (!bridge) return;
|
||||
}
|
||||
@@ -303,8 +309,12 @@ public:
|
||||
}
|
||||
|
||||
void restartBridgeSlot(int slot) override {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
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
|
||||
|
||||
@@ -681,15 +681,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
|
||||
// Alert channel defaults (same as repeater; off by default and unconfigured).
|
||||
// Operator must pick `set alert.psk` or `set alert.hashtag` before alerts fire.
|
||||
_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;
|
||||
// Observer defaults (alert.*, etc.) moved to applyMQTTDefaults() — they live
|
||||
// in /mqtt_prefs now, not NodePrefs.
|
||||
|
||||
// bridge defaults (same as repeater)
|
||||
_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_channel = 1; // channel 1
|
||||
|
||||
// MQTT slot/IATA/timezone defaults come from /mqtt_prefs via loadPrefs (see MQTTDefaults.h)
|
||||
_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));
|
||||
// MQTT/WiFi/timezone defaults live in /mqtt_prefs now (see applyMQTTDefaults).
|
||||
|
||||
next_post_idx = 0;
|
||||
next_client_idx = 0;
|
||||
@@ -757,7 +745,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (_prefs.bridge_enabled) {
|
||||
// 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) {
|
||||
// Set device public key for MQTT topics
|
||||
char device_id[65];
|
||||
@@ -787,8 +775,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
// Passing `this` as the callbacks lets the reporter resolve a TransportKey
|
||||
// scope (alert.region override, falling back to default_scope) so alert
|
||||
// floods ride the same scope as adverts/channel messages.
|
||||
_alerter.begin(&_prefs, this, this);
|
||||
#if defined(WITH_MQTT_BRIDGE)
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_alerter.begin(&_prefs, _cli.getObserverPrefs(), this, this);
|
||||
_alerter.setBridge(bridge);
|
||||
#endif
|
||||
}
|
||||
@@ -806,12 +794,15 @@ void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3
|
||||
|
||||
bool MyMesh::resolveAlertScope(TransportKey& dest) {
|
||||
// Same resolution policy as simple_repeater: alert.region > default_scope.
|
||||
if (_prefs.alert_region[0]) {
|
||||
auto r = region_map.findByNamePrefix(_prefs.alert_region);
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
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()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!default_scope.isNull()) {
|
||||
dest = default_scope;
|
||||
return true;
|
||||
@@ -1131,5 +1122,7 @@ void MyMesh::loop() {
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_alerter.onLoop(now);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -126,7 +126,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
MQTTBridge* bridge;
|
||||
#endif
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
AlertReporter _alerter;
|
||||
#endif
|
||||
|
||||
void addPost(ClientInfo* client, const char* postData);
|
||||
void pushPostToClient(ClientInfo* client, PostInfo& post);
|
||||
@@ -201,8 +203,10 @@ public:
|
||||
// CommonCLICallbacks
|
||||
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(); }
|
||||
bool sendAlertText(const char* text) override { return _alerter.sendText(text); }
|
||||
#endif
|
||||
bool resolveAlertScope(TransportKey& dest) override;
|
||||
bool formatFileSystem() override;
|
||||
void sendSelfAdvertisement(int delay_millis, bool flood) override;
|
||||
@@ -241,7 +245,7 @@ public:
|
||||
void setBridgeState(bool enable) override {
|
||||
if (!bridge) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
bridge = new MQTTBridge(&_prefs, _mgr, getRTCClock(), &self_id);
|
||||
bridge = new MQTTBridge(&_prefs, _cli.getObserverPrefs(), _mgr, getRTCClock(), &self_id);
|
||||
#endif
|
||||
if (!bridge) return;
|
||||
}
|
||||
@@ -289,8 +293,12 @@ public:
|
||||
}
|
||||
|
||||
void restartBridgeSlot(int slot) override {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
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 {
|
||||
|
||||
+6
-4
@@ -39,9 +39,11 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
|
||||
return 4000; // 4 seconds
|
||||
}
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
uint32_t Dispatcher::getRadioWatchdogMillis() const {
|
||||
return RADIO_WATCHDOG_MS;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Dispatcher::loop() {
|
||||
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.
|
||||
// Use a composite "last radio activity" timestamp: the most recent of any valid RX,
|
||||
// any ISR event (even CRC errors), or any successful TX. This prevents false firings
|
||||
// in quiet mesh environments where packets may be more than RADIO_WATCHDOG_MS apart,
|
||||
// while still catching a truly stuck radio (PSRAM starvation → missed ISR → no activity).
|
||||
// Observer-only feature (gated behind WITH_MQTT_BRIDGE); configured via the
|
||||
// MQTTPrefs radio_watchdog_minutes setting.
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
{
|
||||
const uint32_t watchdog_ms = getRadioWatchdogMillis();
|
||||
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 (_radio->isSendComplete()) {
|
||||
|
||||
+3
-1
@@ -180,7 +180,9 @@ protected:
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() 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:
|
||||
void begin();
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
#define ALERT_DEBUG_PRINTLN(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
AlertReporter::AlertReporter()
|
||||
: _prefs(nullptr), _mesh(nullptr), _callbacks(nullptr),
|
||||
: _prefs(nullptr), _obs(nullptr), _mesh(nullptr), _callbacks(nullptr),
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_bridge(nullptr),
|
||||
#endif
|
||||
@@ -38,18 +39,18 @@ AlertReporter::AlertReporter()
|
||||
#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;
|
||||
_obs = obs;
|
||||
_mesh = mesh;
|
||||
_callbacks = callbacks;
|
||||
onConfigChanged();
|
||||
}
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
void AlertReporter::setBridge(MQTTBridge* bridge) {
|
||||
_bridge = bridge;
|
||||
}
|
||||
#endif
|
||||
#endif // WITH_MQTT_BRIDGE (AlertReporter methods, part 1)
|
||||
|
||||
// Channels banned as fault-alert destinations. Fault alerts are noisy
|
||||
// operator-infrastructure messages; routing them to community channels would
|
||||
@@ -97,6 +98,7 @@ const char* alertReporterBannedChannelMatchHex(const char* psk_hex) {
|
||||
return alertReporterBannedChannelMatch(secret);
|
||||
}
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
bool AlertReporter::resolveChannel(mesh::GroupChannel& out) const {
|
||||
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
|
||||
// are not used anywhere in MeshCore practice and not represented in the
|
||||
// banned table either.
|
||||
const char* psk = _prefs->alert_psk_hex;
|
||||
const char* psk = _obs->alert_psk_hex;
|
||||
if (strlen(psk) != 32) return false;
|
||||
|
||||
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) {
|
||||
if (!_prefs || !_prefs->alert_enabled) return;
|
||||
if (!_prefs || !_obs->alert_enabled) return;
|
||||
if (!_mesh) return;
|
||||
|
||||
// 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
|
||||
// tweak shouldn't be able to drag the floor below 1 hour and let a
|
||||
// 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;
|
||||
unsigned long min_interval_ms = (unsigned long)cfg_min * 60000UL;
|
||||
|
||||
// -------- WiFi fault --------
|
||||
if (_prefs->alert_wifi_minutes > 0) {
|
||||
if (_obs->alert_wifi_minutes > 0) {
|
||||
unsigned long wifi_disc_ms = MQTTBridge::getLastWifiDisconnectTime();
|
||||
unsigned long wifi_conn_ms = MQTTBridge::getWifiConnectedAtMillis();
|
||||
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 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_down && down_ms >= thresh_ms &&
|
||||
@@ -263,10 +265,10 @@ void AlertReporter::onLoop(unsigned long now_ms) {
|
||||
}
|
||||
|
||||
// -------- MQTT slot faults --------
|
||||
if (_prefs->alert_mqtt_minutes > 0 && _bridge != nullptr) {
|
||||
if (_obs->alert_mqtt_minutes > 0 && _bridge != nullptr) {
|
||||
int n = MQTTBridge::getRuntimeSlotCount();
|
||||
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++) {
|
||||
Fault& f = _mqtt[i];
|
||||
@@ -311,3 +313,4 @@ void AlertReporter::onLoop(unsigned long now_ms) {
|
||||
(void)now_ms;
|
||||
#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
|
||||
* reporter still supports manual `alert test` sends.
|
||||
*/
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
class AlertReporter {
|
||||
public:
|
||||
AlertReporter();
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
* it to resolve a TransportKey scope for outgoing alert floods (so the
|
||||
* 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
|
||||
/** Bridge can be (re)created lazily; pass nullptr to detach. */
|
||||
@@ -98,6 +99,7 @@ private:
|
||||
};
|
||||
|
||||
NodePrefs* _prefs;
|
||||
MQTTPrefs* _obs;
|
||||
mesh::Mesh* _mesh;
|
||||
CommonCLICallbacks* _callbacks;
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
@@ -107,3 +109,4 @@ private:
|
||||
#endif
|
||||
unsigned long _next_check_ms;
|
||||
};
|
||||
#endif // WITH_MQTT_BRIDGE
|
||||
|
||||
+26
-231
@@ -18,20 +18,6 @@
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
#include "bridges/MQTTBridge.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
|
||||
|
||||
// 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
|
||||
}
|
||||
#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);
|
||||
// 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
|
||||
// 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) {
|
||||
@@ -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->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// MQTT settings - skip reading from main prefs file (now stored separately)
|
||||
// For backward compatibility, we'll skip these bytes if they exist in old files
|
||||
// The actual MQTT prefs will be loaded from /mqtt_prefs in loadMQTTPrefs()
|
||||
// Skip MQTT fields for file format compatibility (whether MQTT bridge is enabled or not)
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
size_t mqtt_fields_size = getMQTTFieldsSize(_prefs);
|
||||
#else
|
||||
// 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;
|
||||
// MQTT/observer settings are no longer stored in /com_prefs — they live in
|
||||
// /mqtt_prefs (loaded by loadMQTTPrefs). The legacy zero-gap and trailing
|
||||
// snmp/alert block that older firmware wrote here are gone; on upgrade those
|
||||
// bytes are simply not read, so rx_boosted_gain/flood_max_* (which followed the
|
||||
// gap) reset once and self-heal on the next save.
|
||||
if (file.available() >= (int)sizeof(_prefs->rx_boosted_gain)) {
|
||||
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain));
|
||||
}
|
||||
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
|
||||
{
|
||||
// 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->flood_max_unscoped)) {
|
||||
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped));
|
||||
}
|
||||
if (file.available() >= (int)sizeof(_prefs->radio_watchdog_minutes)) {
|
||||
file.read((uint8_t *)&_prefs->radio_watchdog_minutes, sizeof(_prefs->radio_watchdog_minutes)); // 318
|
||||
if (file.available() >= (int)sizeof(_prefs->flood_max_advert)) {
|
||||
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
|
||||
_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->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();
|
||||
}
|
||||
@@ -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->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// MQTT settings - no longer saved here (stored in separate /mqtt_prefs file)
|
||||
// Write zeros/padding to maintain file format compatibility
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
size_t mqtt_fields_size = getMQTTFieldsSize(_prefs);
|
||||
#else
|
||||
// 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));
|
||||
// MQTT/observer settings are stored in /mqtt_prefs, not here. No zero-gap is
|
||||
// written anymore — /com_prefs holds only the (non-observer) fields below.
|
||||
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain));
|
||||
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped));
|
||||
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert));
|
||||
|
||||
file.close();
|
||||
}
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Save MQTT preferences to separate file
|
||||
syncNodePrefsToMQTTPrefs(); // Sync any changes from NodePrefs to MQTTPrefs
|
||||
// Observer config (MQTT/WiFi/timezone/SNMP/alert) is persisted separately. The
|
||||
// observer CLI writes _mqtt_prefs directly, so no NodePrefs->MQTTPrefs sync runs.
|
||||
saveMQTTPrefs(fs);
|
||||
#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
|
||||
|
||||
#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;
|
||||
savePrefs();
|
||||
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) {
|
||||
_prefs->multi_acks = atoi(&config[11]);
|
||||
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;
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (_prefs->bridge_pkt_src == 1) {
|
||||
_prefs->mqtt_rx_enabled = 1;
|
||||
_prefs->mqtt_tx_enabled = 0;
|
||||
_mqtt_prefs.mqtt_rx_enabled = 1;
|
||||
_mqtt_prefs.mqtt_tx_enabled = 0;
|
||||
} else {
|
||||
_prefs->mqtt_rx_enabled = 0;
|
||||
_prefs->mqtt_tx_enabled = 1;
|
||||
_mqtt_prefs.mqtt_rx_enabled = 0;
|
||||
_mqtt_prefs.mqtt_tx_enabled = 1;
|
||||
}
|
||||
#endif
|
||||
savePrefs();
|
||||
@@ -1226,8 +1023,6 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
|
||||
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
|
||||
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) {
|
||||
sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks);
|
||||
} 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;
|
||||
float adc_multiplier;
|
||||
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;
|
||||
|
||||
// SNMP settings (optional, only used when WITH_SNMP is defined)
|
||||
uint8_t snmp_enabled; // boolean: 0=off, 1=on
|
||||
char snmp_community[24]; // community string (default "public")
|
||||
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];
|
||||
// NOTE: observer settings (MQTT/WiFi/timezone/SNMP/alert) were moved out of
|
||||
// NodePrefs into MQTTPrefs (persisted to /mqtt_prefs) so this struct stays
|
||||
// aligned with upstream. See struct MQTTPrefs below.
|
||||
};
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
@@ -206,6 +147,20 @@ struct MQTTPrefs {
|
||||
// --- Appended fields (added after initial 6-slot migration) ---
|
||||
uint8_t mqtt_rx_enabled; // Enable RX packet uplinking (default: on)
|
||||
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.
|
||||
@@ -360,8 +315,6 @@ class CommonCLI {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
void loadMQTTPrefs(FILESYSTEM* fs);
|
||||
void saveMQTTPrefs(FILESYSTEM* fs);
|
||||
void syncMQTTPrefsToNodePrefs();
|
||||
void syncNodePrefsToMQTTPrefs();
|
||||
#endif
|
||||
|
||||
void handleRegionCmd(char* command, char* reply);
|
||||
@@ -387,4 +340,10 @@ public:
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
mesh::MainBoard* getBoard() { return _board; }
|
||||
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
|
||||
|
||||
bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* config, char* reply) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
bool handled = true;
|
||||
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();
|
||||
strcpy(reply, "OK - restart to apply");
|
||||
} 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();
|
||||
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
|
||||
} else if (strcmp(config, "mqtt.origin") == 0) {
|
||||
_prefs->mqtt_origin[0] = '\0';
|
||||
_mqtt_prefs.mqtt_origin[0] = '\0';
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.origin ", 12) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_origin, &config[12], sizeof(_prefs->mqtt_origin));
|
||||
StrHelper::stripSurroundingQuotes(_prefs->mqtt_origin, sizeof(_prefs->mqtt_origin));
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_origin, &config[12], sizeof(_mqtt_prefs.mqtt_origin));
|
||||
StrHelper::stripSurroundingQuotes(_mqtt_prefs.mqtt_origin, sizeof(_mqtt_prefs.mqtt_origin));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.iata ", 10) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_iata, &config[10], sizeof(_prefs->mqtt_iata));
|
||||
for (int i = 0; _prefs->mqtt_iata[i]; i++) {
|
||||
_prefs->mqtt_iata[i] = toupper(_prefs->mqtt_iata[i]);
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_iata, &config[10], sizeof(_mqtt_prefs.mqtt_iata));
|
||||
for (int i = 0; _mqtt_prefs.mqtt_iata[i]; i++) {
|
||||
_mqtt_prefs.mqtt_iata[i] = toupper(_mqtt_prefs.mqtt_iata[i]);
|
||||
}
|
||||
savePrefs();
|
||||
_callbacks->restartBridge();
|
||||
strcpy(reply, "OK");
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.tx ", 8) == 0) {
|
||||
if (memcmp(&config[8], "advert", 6) == 0) {
|
||||
_prefs->mqtt_tx_enabled = 2;
|
||||
_mqtt_prefs.mqtt_tx_enabled = 2;
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.interval ", 14) == 0) {
|
||||
uint32_t minutes = _atoi(&config[14]);
|
||||
if (minutes >= 1 && minutes <= 60) {
|
||||
_prefs->mqtt_status_interval = minutes * 60000;
|
||||
_mqtt_prefs.mqtt_status_interval = minutes * 60000;
|
||||
savePrefs();
|
||||
_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 {
|
||||
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");
|
||||
} else {
|
||||
if (clearing) {
|
||||
_prefs->mqtt_ntp_server[0] = '\0';
|
||||
_mqtt_prefs.mqtt_ntp_server[0] = '\0';
|
||||
} 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();
|
||||
#ifdef ESP_PLATFORM
|
||||
@@ -251,11 +276,11 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
#endif
|
||||
}
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "wifi.powersave ", 15) == 0) {
|
||||
@@ -275,7 +300,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
if (!valid) {
|
||||
strcpy(reply, "Error: must be none, min, or max");
|
||||
} else {
|
||||
_prefs->wifi_power_save = ps_value;
|
||||
_mqtt_prefs.wifi_power_save = ps_value;
|
||||
savePrefs();
|
||||
#ifdef ESP_PLATFORM
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
@@ -298,13 +323,13 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
#endif
|
||||
}
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "timezone.offset ", 16) == 0) {
|
||||
int8_t offset = _atoi(&config[16]);
|
||||
if (offset >= -12 && offset <= 14) {
|
||||
_prefs->timezone_offset = offset;
|
||||
_mqtt_prefs.timezone_offset = offset;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
@@ -325,7 +350,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
int dup_slot = -1;
|
||||
if (findMQTTPreset(preset_name) != nullptr) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
@@ -334,19 +359,19 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
if (dup_slot >= 0) {
|
||||
sprintf(reply, "Error: preset '%s' is already assigned to slot %d", preset_name, dup_slot + 1);
|
||||
} 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();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
// Check if the slot has everything it needs to connect
|
||||
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);
|
||||
} 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);
|
||||
} else if (p && mqttPresetNeedsSlotCredentials(p) &&
|
||||
(_prefs->mqtt_slot_username[slot][0] == '\0' ||
|
||||
_prefs->mqtt_slot_password[slot][0] == '\0')) {
|
||||
(_mqtt_prefs.mqtt_slot_username[slot][0] == '\0' ||
|
||||
_mqtt_prefs.mqtt_slot_password[slot][0] == '\0')) {
|
||||
sprintf(reply,
|
||||
"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);
|
||||
@@ -358,54 +383,54 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
strcpy(reply, "Error: unknown preset. Use 'get mqtt.presets'");
|
||||
}
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(subcmd, "port ", 5) == 0) {
|
||||
int port = atoi(&subcmd[5]);
|
||||
if (port > 0 && port <= 65535) {
|
||||
_prefs->mqtt_slot_port[slot] = port;
|
||||
_mqtt_prefs.mqtt_slot_port[slot] = port;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: port must be between 1 and 65535");
|
||||
}
|
||||
} 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();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
strcpy(reply, "OK");
|
||||
} 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();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
strcpy(reply, "OK");
|
||||
} 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();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
sprintf(reply, "OK - slot %d token set", slot + 1);
|
||||
} 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");
|
||||
} 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();
|
||||
_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) {
|
||||
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();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
if (_prefs->mqtt_slot_audience[slot][0] != '\0') {
|
||||
sprintf(reply, "OK - slot %d JWT audience: %s", slot + 1, _prefs->mqtt_slot_audience[slot]);
|
||||
if (_mqtt_prefs.mqtt_slot_audience[slot][0] != '\0') {
|
||||
sprintf(reply, "OK - slot %d JWT audience: %s", slot + 1, _mqtt_prefs.mqtt_slot_audience[slot]);
|
||||
} else {
|
||||
sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1);
|
||||
}
|
||||
} else if (memcmp(subcmd, "audience", 8) == 0 && subcmd[8] == '\0') {
|
||||
// "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();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
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) {
|
||||
const int slot = 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 {
|
||||
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();
|
||||
_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) {
|
||||
const int slot = 1;
|
||||
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 {
|
||||
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();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
@@ -446,7 +471,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
}
|
||||
}
|
||||
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();
|
||||
strcpy(reply, "OK");
|
||||
} 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)");
|
||||
}
|
||||
} 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();
|
||||
strcpy(reply, "OK");
|
||||
#endif
|
||||
@@ -464,12 +489,12 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
// set alert on|off
|
||||
const char* val = &config[6];
|
||||
if (memcmp(val, "on", 2) == 0 && (val[2] == 0 || val[2] == ' ')) {
|
||||
_prefs->alert_enabled = 1;
|
||||
_mqtt_prefs.alert_enabled = 1;
|
||||
savePrefs();
|
||||
_callbacks->onAlertConfigChanged();
|
||||
strcpy(reply, "OK - alerts on");
|
||||
} else if (memcmp(val, "off", 3) == 0 && (val[3] == 0 || val[3] == ' ')) {
|
||||
_prefs->alert_enabled = 0;
|
||||
_mqtt_prefs.alert_enabled = 0;
|
||||
savePrefs();
|
||||
_callbacks->onAlertConfigChanged();
|
||||
strcpy(reply, "OK - alerts off");
|
||||
@@ -483,8 +508,8 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
while (*val == ' ') val++;
|
||||
size_t len = strlen(val);
|
||||
if (len == 0) {
|
||||
_prefs->alert_psk_hex[0] = '\0';
|
||||
_prefs->alert_hashtag[0] = '\0';
|
||||
_mqtt_prefs.alert_psk_hex[0] = '\0';
|
||||
_mqtt_prefs.alert_hashtag[0] = '\0';
|
||||
savePrefs();
|
||||
_callbacks->onAlertConfigChanged();
|
||||
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.
|
||||
sprintf(reply, "Error: refusing banned channel '%s'; pick a private key or hashtag", banned);
|
||||
} 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
|
||||
// hashtag name is no longer accurate provenance — drop it.
|
||||
_prefs->alert_hashtag[0] = '\0';
|
||||
_mqtt_prefs.alert_hashtag[0] = '\0';
|
||||
savePrefs();
|
||||
_callbacks->onAlertConfigChanged();
|
||||
strcpy(reply, "OK - alert.psk updated");
|
||||
@@ -530,8 +555,8 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
while (*val == ' ') val++;
|
||||
size_t in_len = strlen(val);
|
||||
if (in_len == 0) {
|
||||
_prefs->alert_psk_hex[0] = '\0';
|
||||
_prefs->alert_hashtag[0] = '\0';
|
||||
_mqtt_prefs.alert_psk_hex[0] = '\0';
|
||||
_mqtt_prefs.alert_hashtag[0] = '\0';
|
||||
savePrefs();
|
||||
_callbacks->onAlertConfigChanged();
|
||||
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
|
||||
// derivation in docs/companion_protocol.md). Accept the user typing
|
||||
// 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;
|
||||
if (need >= sizeof(hashtag)) {
|
||||
strcpy(reply, "Error: hashtag too long");
|
||||
@@ -566,11 +591,11 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
} else {
|
||||
char hex[33];
|
||||
mesh::Utils::toHex(hex, digest, 16);
|
||||
StrHelper::strncpy(_prefs->alert_hashtag, hashtag, sizeof(_prefs->alert_hashtag));
|
||||
StrHelper::strncpy(_prefs->alert_psk_hex, hex, sizeof(_prefs->alert_psk_hex));
|
||||
StrHelper::strncpy(_mqtt_prefs.alert_hashtag, hashtag, sizeof(_mqtt_prefs.alert_hashtag));
|
||||
StrHelper::strncpy(_mqtt_prefs.alert_psk_hex, hex, sizeof(_mqtt_prefs.alert_psk_hex));
|
||||
savePrefs();
|
||||
_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++;
|
||||
size_t len = strlen(val);
|
||||
if (len == 0) {
|
||||
_prefs->alert_region[0] = '\0';
|
||||
_mqtt_prefs.alert_region[0] = '\0';
|
||||
savePrefs();
|
||||
_callbacks->onAlertConfigChanged();
|
||||
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");
|
||||
} else {
|
||||
StrHelper::strncpy(_prefs->alert_region, val, sizeof(_prefs->alert_region));
|
||||
StrHelper::stripSurroundingQuotes(_prefs->alert_region, sizeof(_prefs->alert_region));
|
||||
StrHelper::strncpy(_mqtt_prefs.alert_region, val, sizeof(_mqtt_prefs.alert_region));
|
||||
StrHelper::stripSurroundingQuotes(_mqtt_prefs.alert_region, sizeof(_mqtt_prefs.alert_region));
|
||||
savePrefs();
|
||||
_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) {
|
||||
int mins = (int)_atoi(&config[11]);
|
||||
if (mins < 0 || mins > 1440) {
|
||||
strcpy(reply, "Error: alert.wifi must be 0-1440 minutes (0=off)");
|
||||
} else {
|
||||
_prefs->alert_wifi_minutes = (uint16_t)mins;
|
||||
_mqtt_prefs.alert_wifi_minutes = (uint16_t)mins;
|
||||
savePrefs();
|
||||
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) {
|
||||
strcpy(reply, "Error: alert.mqtt must be 0-10080 minutes (0=off)");
|
||||
} else {
|
||||
_prefs->alert_mqtt_minutes = (uint16_t)mins;
|
||||
_mqtt_prefs.alert_mqtt_minutes = (uint16_t)mins;
|
||||
savePrefs();
|
||||
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) {
|
||||
strcpy(reply, "Error: alert.interval must be 60-10080 minutes");
|
||||
} else {
|
||||
_prefs->alert_min_interval_min = (uint16_t)mins;
|
||||
_mqtt_prefs.alert_min_interval_min = (uint16_t)mins;
|
||||
savePrefs();
|
||||
sprintf(reply, "OK - alert.interval %d min", mins);
|
||||
}
|
||||
@@ -631,21 +656,28 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
handled = false;
|
||||
}
|
||||
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) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
bool handled = true;
|
||||
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')) {
|
||||
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
|
||||
} else if (memcmp(config, "mqtt.origin", 11) == 0) {
|
||||
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);
|
||||
} 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] == ' ')) {
|
||||
int start = 0;
|
||||
if (config[12] == ' ') {
|
||||
@@ -664,19 +696,19 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
|
||||
}
|
||||
formatMQTTPresetListReply(reply, 160, start);
|
||||
} 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) {
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
} 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) {
|
||||
uint32_t minutes = (_prefs->mqtt_status_interval + 29999) / 60000;
|
||||
sprintf(reply, "> %u minutes (%lu ms)", minutes, (unsigned long)_prefs->mqtt_status_interval);
|
||||
uint32_t minutes = (_mqtt_prefs.mqtt_status_interval + 29999) / 60000;
|
||||
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] == ' ')) {
|
||||
#ifdef ESP_PLATFORM
|
||||
// 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");
|
||||
#endif
|
||||
} 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' &&
|
||||
config[4] >= '1' && config[4] <= ('0' + MAX_MQTT_SLOTS) && config[5] == '.') {
|
||||
// Slot-based commands: get mqtt1.preset, get mqtt1.server, etc.
|
||||
int slot = config[4] - '1'; // 0-5
|
||||
const char* subcmd = &config[6];
|
||||
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) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_slot_host[slot]);
|
||||
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_host[slot]);
|
||||
} 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) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_slot_username[slot]);
|
||||
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_username[slot]);
|
||||
} 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) {
|
||||
if (_prefs->mqtt_slot_token[slot][0] != '\0') {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_slot_token[slot]);
|
||||
if (_mqtt_prefs.mqtt_slot_token[slot][0] != '\0') {
|
||||
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_token[slot]);
|
||||
} else {
|
||||
strcpy(reply, "> (not set)");
|
||||
}
|
||||
} else if (memcmp(subcmd, "topic", 5) == 0) {
|
||||
if (_prefs->mqtt_slot_topic[slot][0] != '\0') {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_slot_topic[slot]);
|
||||
if (_mqtt_prefs.mqtt_slot_topic[slot][0] != '\0') {
|
||||
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_topic[slot]);
|
||||
} else {
|
||||
strcpy(reply, "> (default: meshcore/{iata}/{device}/{type})");
|
||||
}
|
||||
} else if (memcmp(subcmd, "audience", 8) == 0) {
|
||||
if (_prefs->mqtt_slot_audience[slot][0] != '\0') {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_slot_audience[slot]);
|
||||
if (_mqtt_prefs.mqtt_slot_audience[slot][0] != '\0') {
|
||||
sprintf(reply, "> %s", _mqtt_prefs.mqtt_slot_audience[slot]);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
sprintf(reply, "> %s", _prefs->wifi_password);
|
||||
sprintf(reply, "> %s", _mqtt_prefs.wifi_password);
|
||||
} else if (memcmp(config, "wifi.status", 11) == 0) {
|
||||
wl_status_t status = WiFi.status();
|
||||
const char* status_str;
|
||||
@@ -789,56 +821,61 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
|
||||
#endif
|
||||
}
|
||||
} 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";
|
||||
sprintf(reply, "> %s", ps_name);
|
||||
} 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) {
|
||||
sprintf(reply, "> %d", _prefs->timezone_offset);
|
||||
sprintf(reply, "> %d", _mqtt_prefs.timezone_offset);
|
||||
} 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) {
|
||||
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) {
|
||||
if (_prefs->mqtt_owner_public_key[0] != '\0') {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_owner_public_key);
|
||||
if (_mqtt_prefs.mqtt_owner_public_key[0] != '\0') {
|
||||
sprintf(reply, "> %s", _mqtt_prefs.mqtt_owner_public_key);
|
||||
} else {
|
||||
strcpy(reply, "> (not set)");
|
||||
}
|
||||
} else if (sender_timestamp == 0 && memcmp(config, "mqtt.email", 10) == 0) {
|
||||
if (_prefs->mqtt_email[0] != '\0') {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_email);
|
||||
if (_mqtt_prefs.mqtt_email[0] != '\0') {
|
||||
sprintf(reply, "> %s", _mqtt_prefs.mqtt_email);
|
||||
} else {
|
||||
strcpy(reply, "> (not set)");
|
||||
}
|
||||
} 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");
|
||||
#endif
|
||||
} 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
|
||||
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) {
|
||||
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) {
|
||||
sprintf(reply, "> %u min%s", (unsigned)_prefs->alert_wifi_minutes,
|
||||
_prefs->alert_wifi_minutes == 0 ? " (disabled)" : "");
|
||||
sprintf(reply, "> %u min%s", (unsigned)_mqtt_prefs.alert_wifi_minutes,
|
||||
_mqtt_prefs.alert_wifi_minutes == 0 ? " (disabled)" : "");
|
||||
} else if (memcmp(config, "alert.mqtt", 10) == 0) {
|
||||
sprintf(reply, "> %u min%s", (unsigned)_prefs->alert_mqtt_minutes,
|
||||
_prefs->alert_mqtt_minutes == 0 ? " (disabled)" : "");
|
||||
sprintf(reply, "> %u min%s", (unsigned)_mqtt_prefs.alert_mqtt_minutes,
|
||||
_mqtt_prefs.alert_mqtt_minutes == 0 ? " (disabled)" : "");
|
||||
} 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')) {
|
||||
sprintf(reply, "> %s", _prefs->alert_enabled ? "on" : "off");
|
||||
sprintf(reply, "> %s", _mqtt_prefs.alert_enabled ? "on" : "off");
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
return handled;
|
||||
#else
|
||||
(void)sender_timestamp; (void)config; (void)reply;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (memcmp(command, "tls.bundletest ", 15) == 0) {
|
||||
#ifdef ESP_PLATFORM
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
@@ -946,7 +983,7 @@ bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command,
|
||||
} else {
|
||||
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)");
|
||||
} else {
|
||||
bool ok = _callbacks->sendAlertText(text);
|
||||
@@ -955,4 +992,8 @@ bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command,
|
||||
return true;
|
||||
}
|
||||
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_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
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
#endif
|
||||
|
||||
// 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) {
|
||||
if (!prefs || !dest || dest_size == 0) return;
|
||||
if (prefs->mqtt_origin[0] == '\0') {
|
||||
strncpy(dest, prefs->node_name, dest_size - 1);
|
||||
static void applyEffectiveOrigin(const NodePrefs* np, const MQTTPrefs* obs, char* dest, size_t dest_size) {
|
||||
if (!np || !obs || !dest || dest_size == 0) return;
|
||||
if (obs->mqtt_origin[0] == '\0') {
|
||||
strncpy(dest, np->node_name, dest_size - 1);
|
||||
} else {
|
||||
strncpy(dest, prefs->mqtt_origin, dest_size - 1);
|
||||
strncpy(dest, obs->mqtt_origin, dest_size - 1);
|
||||
}
|
||||
dest[dest_size - 1] = '\0';
|
||||
StrHelper::stripSurroundingQuotes(dest, dest_size);
|
||||
@@ -52,7 +52,7 @@ static bool ntpHostnameEquals(const char* a, const char* b) {
|
||||
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;
|
||||
if (prefs && prefs->mqtt_ntp_server[0] != '\0') {
|
||||
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) {
|
||||
if (prefs && prefs->mqtt_ntp_server[0] != '\0') {
|
||||
return prefs->mqtt_ntp_server;
|
||||
const char* MQTTBridge::effectiveNtpPrimary(const MQTTPrefs* obs) {
|
||||
if (obs && obs->mqtt_ntp_server[0] != '\0') {
|
||||
return obs->mqtt_ntp_server;
|
||||
}
|
||||
return kNtpBuiltinFallbacks[0];
|
||||
}
|
||||
|
||||
void MQTTBridge::refreshOriginFromPrefs() {
|
||||
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 (!prefs) {
|
||||
if (!np || !obs) {
|
||||
buf[0] = '\0';
|
||||
return;
|
||||
}
|
||||
applyEffectiveOrigin(prefs, buf, buf_size);
|
||||
applyEffectiveOrigin(np, obs, buf, buf_size);
|
||||
}
|
||||
|
||||
// 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)
|
||||
if (strlen(prefs->wifi_ssid) == 0) {
|
||||
if (!obs || strlen(obs->wifi_ssid) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -107,13 +107,13 @@ static bool isWiFiConfigValid(const NodePrefs* prefs) {
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
|
||||
bool MQTTBridge::isConfigValid(const NodePrefs* prefs) {
|
||||
if (!prefs || !isWiFiConfigValid(prefs)) return false;
|
||||
bool MQTTBridge::isConfigValid(const MQTTPrefs* obs) {
|
||||
if (!obs || !isWiFiConfigValid(obs)) return false;
|
||||
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 (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) {
|
||||
return true;
|
||||
}
|
||||
@@ -194,9 +194,9 @@ unsigned long MQTTBridge::getWifiConnectedAtMillis() {
|
||||
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;
|
||||
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) {
|
||||
snprintf(buf, bufsize, "> msgs: %s (bridge not running)", msgs);
|
||||
return;
|
||||
@@ -384,11 +384,12 @@ void MQTTBridge::formatSlotDiagReply(char* buf, size_t bufsize, int slot_index)
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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),
|
||||
_obs(obs),
|
||||
_queue_count(0),
|
||||
_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_diag_requested(false), _ntp_diag_done(false), _ntp_diag_count(0),
|
||||
// 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);
|
||||
|
||||
// Check if WiFi credentials are configured first
|
||||
if (!isWiFiConfigValid(_prefs)) {
|
||||
if (!isWiFiConfigValid(_obs)) {
|
||||
MQTT_DEBUG_PRINTLN("MQTT Bridge initialization skipped - WiFi credentials not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
refreshOriginFromPrefs();
|
||||
|
||||
strncpy(_iata, _prefs->mqtt_iata, sizeof(_iata) - 1);
|
||||
strncpy(_iata, _obs->mqtt_iata, sizeof(_iata) - 1);
|
||||
_iata[sizeof(_iata) - 1] = '\0';
|
||||
|
||||
StrHelper::stripSurroundingQuotes(_iata, sizeof(_iata));
|
||||
@@ -557,17 +558,17 @@ void MQTTBridge::begin() {
|
||||
}
|
||||
|
||||
// Update enabled flags from preferences
|
||||
_status_enabled = _prefs->mqtt_status_enabled;
|
||||
_packets_enabled = _prefs->mqtt_packets_enabled;
|
||||
_raw_enabled = _prefs->mqtt_raw_enabled;
|
||||
_rx_enabled = _prefs->mqtt_rx_enabled;
|
||||
_tx_mode = _prefs->mqtt_tx_enabled; // 0=off, 1=all, 2=advert
|
||||
_status_enabled = _obs->mqtt_status_enabled;
|
||||
_packets_enabled = _obs->mqtt_packets_enabled;
|
||||
_raw_enabled = _obs->mqtt_raw_enabled;
|
||||
_rx_enabled = _obs->mqtt_rx_enabled;
|
||||
_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
|
||||
if (_prefs->mqtt_status_interval >= 1000 && _prefs->mqtt_status_interval <= 3600000) {
|
||||
_status_interval = _prefs->mqtt_status_interval;
|
||||
if (_obs->mqtt_status_interval >= 1000 && _obs->mqtt_status_interval <= 3600000) {
|
||||
_status_interval = _obs->mqtt_status_interval;
|
||||
} else {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -578,12 +579,12 @@ void MQTTBridge::begin() {
|
||||
|
||||
// Apply slot presets from preferences
|
||||
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 (strcmp(preset_name, MQTT_PRESET_CUSTOM) == 0) {
|
||||
// Custom broker: copy host/port/username/password from prefs
|
||||
_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';
|
||||
if (strlen(_slots[i].host) == 0) {
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d: custom preset has no server configured, disabling", i + 1);
|
||||
@@ -591,12 +592,12 @@ void MQTTBridge::begin() {
|
||||
continue;
|
||||
}
|
||||
_slots[i].enabled = true;
|
||||
_slots[i].port = _prefs->mqtt_slot_port[i];
|
||||
strncpy(_slots[i].username, _prefs->mqtt_slot_username[i], sizeof(_slots[i].username) - 1);
|
||||
_slots[i].port = _obs->mqtt_slot_port[i];
|
||||
strncpy(_slots[i].username, _obs->mqtt_slot_username[i], sizeof(_slots[i].username) - 1);
|
||||
_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';
|
||||
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';
|
||||
} else {
|
||||
const MQTTPresetDef* preset = findMQTTPreset(preset_name);
|
||||
@@ -604,9 +605,9 @@ void MQTTBridge::begin() {
|
||||
_slots[i].enabled = true;
|
||||
_slots[i].preset = 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';
|
||||
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';
|
||||
}
|
||||
} else {
|
||||
@@ -702,7 +703,7 @@ void MQTTBridge::begin() {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.setAutoReconnect(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()
|
||||
#endif
|
||||
@@ -845,7 +846,7 @@ void MQTTBridge::initializeWiFiInTask() {
|
||||
// When already connected, the deferred slot setup still fires in mqttTaskLoop()
|
||||
// because _ntp_synced persists across end() (only _slots_setup_done is reset).
|
||||
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) {
|
||||
_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++) {
|
||||
if (_slot_reconfigure_pending[i]) {
|
||||
_slot_reconfigure_pending[i] = false;
|
||||
MQTT_DEBUG_PRINTLN("Applying deferred reconfigure for MQTT%d (preset: %s)", i + 1, _prefs->mqtt_slot_preset[i]);
|
||||
applySlotPreset(i, _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, _obs->mqtt_slot_preset[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -990,9 +991,9 @@ void MQTTBridge::mqttTaskLoop() {
|
||||
#ifdef WITH_SNMP
|
||||
// SNMP agent loop — process incoming UDP requests
|
||||
if (_snmp_agent) {
|
||||
if (!_snmp_agent->isRunning() && WiFi.isConnected() && _prefs->snmp_enabled) {
|
||||
_snmp_agent->begin(_prefs->snmp_community);
|
||||
MQTT_DEBUG_PRINTLN("SNMP agent started on port 161 (community: %s)", _prefs->snmp_community);
|
||||
if (!_snmp_agent->isRunning() && WiFi.isConnected() && _obs->snmp_enabled) {
|
||||
_snmp_agent->begin(_obs->snmp_community);
|
||||
MQTT_DEBUG_PRINTLN("SNMP agent started on port 161 (community: %s)", _obs->snmp_community);
|
||||
}
|
||||
if (_snmp_agent->isRunning()) {
|
||||
// Update MQTT stats from this core
|
||||
@@ -1626,8 +1627,8 @@ bool MQTTBridge::createSlotAuthToken(int index) {
|
||||
// Prepare owner key
|
||||
const char* owner_key = nullptr;
|
||||
char owner_key_uppercase[65];
|
||||
if (_prefs->mqtt_owner_public_key[0] != '\0') {
|
||||
strncpy(owner_key_uppercase, _prefs->mqtt_owner_public_key, sizeof(owner_key_uppercase) - 1);
|
||||
if (_obs->mqtt_owner_public_key[0] != '\0') {
|
||||
strncpy(owner_key_uppercase, _obs->mqtt_owner_public_key, sizeof(owner_key_uppercase) - 1);
|
||||
owner_key_uppercase[sizeof(owner_key_uppercase) - 1] = '\0';
|
||||
for (int i = 0; owner_key_uppercase[i]; i++) {
|
||||
owner_key_uppercase[i] = toupper(owner_key_uppercase[i]);
|
||||
@@ -1637,7 +1638,7 @@ bool MQTTBridge::createSlotAuthToken(int index) {
|
||||
|
||||
char client_version[64];
|
||||
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);
|
||||
// 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) {
|
||||
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;
|
||||
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) {
|
||||
// MeshRank: packets only, uses per-slot token in topic path
|
||||
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;
|
||||
snprintf(topic_buf, buf_size, "meshrank/uplink/%s/%s/packets", token, _device_id);
|
||||
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
|
||||
if (_prefs->mqtt_slot_topic[index][0] != '\0') {
|
||||
return substituteTopicTemplate(_prefs->mqtt_slot_topic[index], type, index, topic_buf, buf_size);
|
||||
if (_obs->mqtt_slot_topic[index][0] != '\0') {
|
||||
return substituteTopicTemplate(_obs->mqtt_slot_topic[index], type, index, topic_buf, buf_size);
|
||||
}
|
||||
// Default: meshcore format
|
||||
if (!isIATAValid()) return false;
|
||||
@@ -1936,9 +1937,9 @@ void MQTTBridge::applySlotPreset(int slot_index, const char* preset_name) {
|
||||
slot.enabled = true;
|
||||
slot.preset = 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';
|
||||
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';
|
||||
}
|
||||
if (_initialized) {
|
||||
@@ -1972,7 +1973,7 @@ void MQTTBridge::setSlotCustomBroker(int slot_index, const char* host, uint16_t
|
||||
|
||||
void MQTTBridge::checkConfigurationMismatch() {
|
||||
// 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();
|
||||
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.");
|
||||
@@ -2010,7 +2011,7 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
|
||||
_wifi_reconnect_backoff_attempt = 0;
|
||||
#ifdef ESP_PLATFORM
|
||||
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) {
|
||||
ps_mode = WIFI_PS_NONE;
|
||||
} else if (ps_pref == 2) {
|
||||
@@ -2054,7 +2055,7 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
|
||||
_wifi_reconnect_backoff_attempt++;
|
||||
}
|
||||
WiFi.disconnect();
|
||||
WiFi.begin(_prefs->wifi_ssid, _prefs->wifi_password);
|
||||
WiFi.begin(_obs->wifi_ssid, _obs->wifi_password);
|
||||
}
|
||||
}
|
||||
_last_wifi_status = current_wifi_status;
|
||||
@@ -2063,7 +2064,7 @@ bool MQTTBridge::handleWiFiConnection(unsigned long now) {
|
||||
}
|
||||
|
||||
bool MQTTBridge::isReady() const {
|
||||
return _initialized && isWiFiConfigValid(_prefs);
|
||||
return _initialized && isWiFiConfigValid(_obs);
|
||||
}
|
||||
|
||||
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->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);
|
||||
return false;
|
||||
}
|
||||
@@ -2092,18 +2093,18 @@ bool MQTTBridge::isSlotReady(int index, char* reason_buf, size_t reason_size) co
|
||||
}
|
||||
}
|
||||
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);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 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);
|
||||
return false;
|
||||
}
|
||||
@@ -2154,7 +2155,7 @@ void MQTTBridge::loop() {
|
||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) {
|
||||
if (_slot_reconfigure_pending[i]) {
|
||||
_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) {
|
||||
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
|
||||
bool has_valid_slots = false;
|
||||
@@ -2254,8 +2255,8 @@ void MQTTBridge::onPacketReceived(mesh::Packet *packet) {
|
||||
}
|
||||
|
||||
void MQTTBridge::sendPacket(mesh::Packet *packet) {
|
||||
uint8_t tx_mode = _prefs->mqtt_tx_enabled; // Read live from prefs (no restart needed)
|
||||
if (!_initialized || !_prefs->mqtt_packets_enabled || tx_mode == 0) return;
|
||||
uint8_t tx_mode = _obs->mqtt_tx_enabled; // Read live from prefs (no restart needed)
|
||||
if (!_initialized || !_obs->mqtt_packets_enabled || tx_mode == 0) return;
|
||||
|
||||
// Advert mode: only queue self-originated advert packets
|
||||
if (tx_mode == 2) {
|
||||
@@ -2910,7 +2911,7 @@ void MQTTBridge::refreshNTP() {
|
||||
// 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.
|
||||
// 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();
|
||||
MQTT_DEBUG_PRINTLN("NTP refresh triggered (async SNTP)");
|
||||
}
|
||||
@@ -2939,10 +2940,10 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) {
|
||||
if (primary_only) {
|
||||
// Validation path (e.g. set mqtt.ntp): test only the configured primary so a
|
||||
// typo fails fast instead of walking the entire fallback list.
|
||||
servers[0] = effectiveNtpPrimary(_prefs);
|
||||
servers[0] = effectiveNtpPrimary(_obs);
|
||||
server_count = 1;
|
||||
} else {
|
||||
fillNtpServerList(_prefs, servers, server_count);
|
||||
fillNtpServerList(_obs, servers, server_count);
|
||||
}
|
||||
|
||||
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
|
||||
// deleting/newing a Timezone, which was a per-change heap alloc pair.
|
||||
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;
|
||||
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};
|
||||
dst_rule = utc;
|
||||
std_rule = utc;
|
||||
}
|
||||
_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';
|
||||
}
|
||||
|
||||
@@ -3089,7 +3090,7 @@ bool MQTTBridge::requestForcedNtpSync(uint32_t timeout_ms) {
|
||||
void MQTTBridge::runNtpDiagProbe() {
|
||||
const char* servers[kMaxNtpServers];
|
||||
int count = 0;
|
||||
fillNtpServerList(_prefs, servers, count);
|
||||
fillNtpServerList(_obs, servers, count);
|
||||
|
||||
_ntp_client.begin();
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
@@ -375,8 +375,12 @@ private:
|
||||
void logMemoryStatus();
|
||||
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:
|
||||
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 end() override;
|
||||
@@ -439,8 +443,8 @@ public:
|
||||
const char* getSlotPresetName(int slot_index) const;
|
||||
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). */
|
||||
static void getEffectiveMqttOrigin(const NodePrefs* prefs, char* buf, size_t buf_size);
|
||||
static const char* effectiveNtpPrimary(const NodePrefs* prefs);
|
||||
static void getEffectiveMqttOrigin(const NodePrefs* np, const MQTTPrefs* obs, char* buf, size_t buf_size);
|
||||
static const char* effectiveNtpPrimary(const MQTTPrefs* obs);
|
||||
/** 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
|
||||
* 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
|
||||
* (for LoRa). Returns false if the bridge is not running. */
|
||||
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). */
|
||||
static bool isConfigValid(const NodePrefs* prefs);
|
||||
static bool isConfigValid(const MQTTPrefs* obs);
|
||||
static void formatSlotDiagReply(char* buf, size_t bufsize, int slot_index);
|
||||
static uint8_t getLastWifiDisconnectReason();
|
||||
static unsigned long getLastWifiDisconnectTime();
|
||||
|
||||
Reference in New Issue
Block a user