Scope preference saves to changed settings

This commit is contained in:
mikecarper
2026-08-06 15:33:40 -07:00
parent 63e7b2ce8e
commit df2dd41512
11 changed files with 1491 additions and 1390 deletions
+49 -48
View File
@@ -1,7 +1,8 @@
// CommonCLI_Observer.cpp - fork-owned observer/MQTT/WiFi/timezone/alert/SNMP CLI
// command handling, split out of CommonCLI.cpp so the upstream-tracked file carries
// only two small delegation hooks. These are CommonCLI member functions, so they
// retain full access to _prefs/_callbacks/_board/savePrefs() with no re-plumbing.
// retain full access to _prefs/_callbacks/_board/saveObserverPrefs() with no
// re-plumbing.
//
// Behavior is intentionally identical to the previously-inlined branches. NOTE:
// the entire body of each set/get handler here is compiled under WITH_MQTT_BRIDGE
@@ -171,11 +172,11 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
if (memcmp(config, "snmp.community ", 15) == 0) {
if (valueTooLong(&config[15], sizeof(_mqtt_prefs.snmp_community), reply, "snmp.community")) return true;
StrHelper::strncpy(_mqtt_prefs.snmp_community, &config[15], sizeof(_mqtt_prefs.snmp_community));
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK - restart to apply");
} else if (memcmp(config, "snmp ", 5) == 0) {
_mqtt_prefs.snmp_enabled = memcmp(&config[5], "on", 2) == 0;
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK - restart to apply");
} else if (memcmp(config, "radio.watchdog ", 15) == 0) {
const char* val = &config[15];
@@ -193,7 +194,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
strcpy(reply, "Error: radio.watchdog must be 0-120 minutes");
} else {
_mqtt_prefs.radio_watchdog_minutes = (uint8_t)mins;
savePrefs();
saveObserverPrefs();
if (mins == 0) {
strcpy(reply, "OK - radio watchdog disabled");
} else {
@@ -204,13 +205,13 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
#ifdef WITH_MQTT_BRIDGE
} else if (strcmp(config, "mqtt.origin") == 0) {
_mqtt_prefs.mqtt_origin[0] = '\0';
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.origin ", 12) == 0) {
if (valueTooLong(&config[12], sizeof(_mqtt_prefs.mqtt_origin), reply, "origin")) return true;
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();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.iata ", 10) == 0) {
const char* iata = &config[10];
@@ -219,7 +220,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
// Empty clears the region code (meshcore-topic publishing stays disabled
// until one is set). This keeps the pre-existing "clear IATA" capability.
_mqtt_prefs.mqtt_iata[0] = '\0';
savePrefs();
saveObserverPrefs();
_callbacks->restartBridge();
strcpy(reply, "OK - IATA cleared");
} else {
@@ -232,22 +233,22 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
for (int i = 0; _mqtt_prefs.mqtt_iata[i]; i++) {
_mqtt_prefs.mqtt_iata[i] = toupper(_mqtt_prefs.mqtt_iata[i]);
}
savePrefs();
saveObserverPrefs();
_callbacks->restartBridge();
strcpy(reply, "OK");
}
}
} else if (memcmp(config, "mqtt.status ", 12) == 0) {
_mqtt_prefs.mqtt_status_enabled = memcmp(&config[12], "on", 2) == 0;
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.packets ", 13) == 0) {
_mqtt_prefs.mqtt_packets_enabled = memcmp(&config[13], "on", 2) == 0;
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.raw ", 9) == 0) {
_mqtt_prefs.mqtt_raw_enabled = memcmp(&config[9], "on", 2) == 0;
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.tx ", 8) == 0) {
if (memcmp(&config[8], "advert", 6) == 0) {
@@ -255,17 +256,17 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else {
_mqtt_prefs.mqtt_tx_enabled = memcmp(&config[8], "on", 2) == 0 ? 1 : 0;
}
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.rx ", 8) == 0) {
_mqtt_prefs.mqtt_rx_enabled = memcmp(&config[8], "on", 2) == 0 ? 1 : 0;
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.interval ", 14) == 0) {
uint32_t minutes = _atoi(&config[14]);
if (minutes >= 1 && minutes <= 60) {
_mqtt_prefs.mqtt_status_interval = minutes * 60000;
savePrefs();
saveObserverPrefs();
_callbacks->restartBridge();
sprintf(reply, "OK - interval set to %u minutes (%lu ms), bridge restarted", minutes, (unsigned long)_mqtt_prefs.mqtt_status_interval);
} else {
@@ -278,7 +279,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
uint32_t hours = _atoi(&config[24]);
if (hours >= MQTT_NEIGHBORS_MIN_INTERVAL_HOURS && hours <= MQTT_NEIGHBORS_MAX_INTERVAL_HOURS) {
_mqtt_prefs.mqtt_neighbors_interval = hours * 3600000UL;
savePrefs();
saveObserverPrefs();
sprintf(reply, "OK - neighbors interval set to %u hours (%lu ms)", (unsigned)hours,
(unsigned long)_mqtt_prefs.mqtt_neighbors_interval);
} else {
@@ -288,7 +289,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
// The mesh loop reads this live, so no bridge restart is needed; enabling it
// triggers a discovery on the next eligible loop pass.
_mqtt_prefs.mqtt_neighbors_enabled = memcmp(&config[15], "on", 2) == 0;
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
#elif defined(WITH_MQTT_BRIDGE)
} else if (memcmp(config, "mqtt.neighbors.interval ", 24) == 0 ||
@@ -307,7 +308,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else {
StrHelper::strncpy(_mqtt_prefs.mqtt_ntp_server, host, sizeof(_mqtt_prefs.mqtt_ntp_server));
}
savePrefs();
saveObserverPrefs();
#ifdef ESP_PLATFORM
// Queue a sync on the MQTT task (Core 0) but do NOT block: this handler
// runs on the Arduino loop task, shared with mesh/radio processing and the
@@ -331,14 +332,14 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
const char* value = config[9] == 0 ? config + 9 : config + 10;
if (valueTooLong(value, sizeof(_mqtt_prefs.wifi_ssid), reply, "wifi.ssid")) return true;
StrHelper::strncpy(_mqtt_prefs.wifi_ssid, value, sizeof(_mqtt_prefs.wifi_ssid));
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "wifi.pwd ", 9) == 0
|| strcmp(config, "wifi.pwd") == 0) {
const char* value = config[8] == 0 ? config + 8 : config + 9;
if (valueTooLong(value, sizeof(_mqtt_prefs.wifi_password), reply, "wifi.pwd")) return true;
StrHelper::strncpy(_mqtt_prefs.wifi_password, value, sizeof(_mqtt_prefs.wifi_password));
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "wifi.powersave ", 15) == 0) {
const char* value = &config[15];
@@ -358,7 +359,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
strcpy(reply, "Error: must be none, min, or max");
} else {
_mqtt_prefs.wifi_power_save = ps_value;
savePrefs();
saveObserverPrefs();
#ifdef ESP_PLATFORM
if (WiFi.status() == WL_CONNECTED) {
wifi_ps_type_t ps_mode = (ps_value == 1) ? WIFI_PS_NONE :
@@ -382,13 +383,13 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else if (memcmp(config, "timezone ", 9) == 0) {
if (valueTooLong(&config[9], sizeof(_mqtt_prefs.timezone_string), reply, "timezone")) return true;
StrHelper::strncpy(_mqtt_prefs.timezone_string, &config[9], sizeof(_mqtt_prefs.timezone_string));
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "timezone.offset ", 16) == 0) {
int8_t offset = _atoi(&config[16]);
if (offset >= -12 && offset <= 14) {
_mqtt_prefs.timezone_offset = offset;
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: timezone offset must be between -12 and +14");
@@ -418,7 +419,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
sprintf(reply, "Error: preset '%s' is already assigned to slot %d", preset_name, dup_slot + 1);
} else {
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], preset_name, sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
// Check if the slot has everything it needs to connect
const MQTTPresetDef* p = findMQTTPreset(preset_name);
@@ -477,7 +478,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else if (memcmp(subcmd, "server ", 7) == 0) {
if (valueTooLong(&subcmd[7], sizeof(_mqtt_prefs.mqtt_slot_host[slot]), reply, "server")) return true;
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_host[slot], &subcmd[7], sizeof(_mqtt_prefs.mqtt_slot_host[slot]));
savePrefs();
saveObserverPrefs();
// Reconfigure the slot so the new host reaches the live connection (other
// custom-slot setters do the same; without it the change only applies on
// the next reboot/bridge restart).
@@ -487,7 +488,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
int port = atoi(&subcmd[5]);
if (port > 0 && port <= 65535) {
_mqtt_prefs.mqtt_slot_port[slot] = port;
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
strcpy(reply, "OK");
} else {
@@ -496,19 +497,19 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else if (memcmp(subcmd, "username ", 9) == 0) {
if (valueTooLong(&subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_username[slot]), reply, "username")) return true;
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_username[slot], &subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_username[slot]));
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
strcpy(reply, "OK");
} else if (memcmp(subcmd, "password ", 9) == 0) {
if (valueTooLong(&subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_password[slot]), reply, "password")) return true;
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_password[slot], &subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_password[slot]));
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
strcpy(reply, "OK");
} else if (memcmp(subcmd, "token ", 6) == 0) {
if (valueTooLong(&subcmd[6], sizeof(_mqtt_prefs.mqtt_slot_token[slot]), reply, "token")) return true;
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_token[slot], &subcmd[6], sizeof(_mqtt_prefs.mqtt_slot_token[slot]));
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
sprintf(reply, "OK - slot %d token set", slot + 1);
} else if (memcmp(subcmd, "topic ", 6) == 0) {
@@ -518,14 +519,14 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
return true;
} else {
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_topic[slot], &subcmd[6], sizeof(_mqtt_prefs.mqtt_slot_topic[slot]));
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
sprintf(reply, "OK - slot %d topic: %s", slot + 1, _mqtt_prefs.mqtt_slot_topic[slot]);
}
} else if (memcmp(subcmd, "audience ", 9) == 0) {
if (valueTooLong(&subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_audience[slot]), reply, "audience")) return true;
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_audience[slot], &subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_audience[slot]));
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(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]);
@@ -535,7 +536,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else if (memcmp(subcmd, "audience", 8) == 0 && subcmd[8] == '\0') {
// "set mqttN.audience" with no value - clear the audience
_mqtt_prefs.mqtt_slot_audience[slot][0] = '\0';
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1);
} else {
@@ -548,7 +549,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else {
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], MQTT_PRESET_NONE, sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
}
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
strcpy(reply, "OK");
} else if (memcmp(config, "mqtt.analyzer.eu ", 17) == 0) {
@@ -558,12 +559,12 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else {
StrHelper::strncpy(_mqtt_prefs.mqtt_slot_preset[slot], MQTT_PRESET_NONE, sizeof(_mqtt_prefs.mqtt_slot_preset[slot]));
}
savePrefs();
saveObserverPrefs();
_callbacks->restartBridgeSlot(slot);
strcpy(reply, "OK");
} else if (strcmp(config, "mqtt.owner") == 0 || strcmp(config, "mqtt.owner ") == 0) {
_mqtt_prefs.mqtt_owner_public_key[0] = '\0';
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK - owner cleared");
} else if (memcmp(config, "mqtt.owner ", 11) == 0) {
const char* owner_key = &config[11];
@@ -571,11 +572,11 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
// Owner key is optional -- empty clears it (previously this errored, so a
// set key could never be removed via the portal/CLI).
_mqtt_prefs.mqtt_owner_public_key[0] = '\0';
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK - owner key cleared");
} else if (mqttOwnerKeyValid(owner_key)) {
StrHelper::strncpy(_mqtt_prefs.mqtt_owner_public_key, owner_key, sizeof(_mqtt_prefs.mqtt_owner_public_key));
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: public key must be 64 hex characters (32 bytes)");
@@ -583,7 +584,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else if (memcmp(config, "mqtt.email ", 11) == 0) {
if (valueTooLong(&config[11], sizeof(_mqtt_prefs.mqtt_email), reply, "email")) return true;
StrHelper::strncpy(_mqtt_prefs.mqtt_email, &config[11], sizeof(_mqtt_prefs.mqtt_email));
savePrefs();
saveObserverPrefs();
strcpy(reply, "OK");
#endif
} else if (memcmp(config, "alert ", 6) == 0) {
@@ -591,12 +592,12 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
const char* val = &config[6];
if (memcmp(val, "on", 2) == 0 && (val[2] == 0 || val[2] == ' ')) {
_mqtt_prefs.alert_enabled = 1;
savePrefs();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
strcpy(reply, "OK - alerts on");
} else if (memcmp(val, "off", 3) == 0 && (val[3] == 0 || val[3] == ' ')) {
_mqtt_prefs.alert_enabled = 0;
savePrefs();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
strcpy(reply, "OK - alerts off");
} else {
@@ -611,7 +612,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
if (len == 0) {
_mqtt_prefs.alert_psk_hex[0] = '\0';
_mqtt_prefs.alert_hashtag[0] = '\0';
savePrefs();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
strcpy(reply, "OK - alert.psk cleared (alerts disabled until configured)");
} else if (val[0] == '#') {
@@ -645,7 +646,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
// The new PSK is operator-supplied, so any previously-derived
// hashtag name is no longer accurate provenance - drop it.
_mqtt_prefs.alert_hashtag[0] = '\0';
savePrefs();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
strcpy(reply, "OK - alert.psk updated");
}
@@ -658,7 +659,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
if (in_len == 0) {
_mqtt_prefs.alert_psk_hex[0] = '\0';
_mqtt_prefs.alert_hashtag[0] = '\0';
savePrefs();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
strcpy(reply, "OK - alert.hashtag cleared (alerts disabled until configured)");
} else {
@@ -694,7 +695,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
mesh::Utils::toHex(hex, digest, 16);
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();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
sprintf(reply, "OK - alert.hashtag: %s", _mqtt_prefs.alert_hashtag);
}
@@ -712,7 +713,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
size_t len = strlen(val);
if (len == 0) {
_mqtt_prefs.alert_region[0] = '\0';
savePrefs();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
strcpy(reply, "OK - alert.region cleared (using default scope)");
} else if (len >= sizeof(_mqtt_prefs.alert_region)) {
@@ -720,7 +721,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
} else {
StrHelper::strncpy(_mqtt_prefs.alert_region, val, sizeof(_mqtt_prefs.alert_region));
StrHelper::stripSurroundingQuotes(_mqtt_prefs.alert_region, sizeof(_mqtt_prefs.alert_region));
savePrefs();
saveObserverPrefs();
_callbacks->onAlertConfigChanged();
sprintf(reply, "OK - alert.region: %s", _mqtt_prefs.alert_region);
}
@@ -730,7 +731,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
strcpy(reply, "Error: alert.wifi must be 0-1440 minutes (0=off)");
} else {
_mqtt_prefs.alert_wifi_minutes = (uint16_t)mins;
savePrefs();
saveObserverPrefs();
sprintf(reply, "OK - alert.wifi %d min%s", mins, mins == 0 ? " (disabled)" : "");
}
} else if (memcmp(config, "alert.mqtt ", 11) == 0) {
@@ -739,7 +740,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
strcpy(reply, "Error: alert.mqtt must be 0-10080 minutes (0=off)");
} else {
_mqtt_prefs.alert_mqtt_minutes = (uint16_t)mins;
savePrefs();
saveObserverPrefs();
sprintf(reply, "OK - alert.mqtt %d min%s", mins, mins == 0 ? " (disabled)" : "");
}
} else if (memcmp(config, "alert.interval ", 15) == 0) {
@@ -750,7 +751,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
strcpy(reply, "Error: alert.interval must be 60-10080 minutes");
} else {
_mqtt_prefs.alert_min_interval_min = (uint16_t)mins;
savePrefs();
saveObserverPrefs();
sprintf(reply, "OK - alert.interval %d min", mins);
}
} else {