Unify device controls and restore the WiFi console

This commit is contained in:
mikecarper committed 2026-09-08 18:36:23 -07:00
1 parent 3746f9d5f4
commit 4c8aad5b16
44 files changed
+3573 -2948

No files matched your search

+31
View File
@@ -521,6 +521,37 @@ inline bool isUf2ResetCommand(const char* command) {
return matchNoArgCommand(command, "uf2reset") == NoArgCommandMatch::Exact;
}
// Shared by Companion and infrastructure logging controls. The optional suffix
// requests a restart only when changing USB descriptors actually needs one.
inline bool parseLoggingToggle(const char* value, bool& enabled, bool& reboot) {
if (value == nullptr) return false;
if (strcmp(value, "on") == 0 || strcmp(value, "on reboot") == 0) {
enabled = true;
reboot = value[2] != 0;
return true;
}
if (strcmp(value, "off") == 0 || strcmp(value, "off reboot") == 0) {
enabled = false;
reboot = value[3] != 0;
return true;
}
return false;
}
inline bool parseLoggingOutput(const char* value, bool& usb, bool& wifi) {
if (value == nullptr) return false;
if (strcmp(value, "off") == 0) { usb = false; wifi = false; }
else if (strcmp(value, "usb") == 0) { usb = true; wifi = false; }
else if (strcmp(value, "wifi") == 0) { usb = false; wifi = true; }
else if (strcmp(value, "both") == 0) { usb = true; wifi = true; }
else return false;
return true;
}
inline const char* loggingOutputName(bool usb, bool wifi) {
return usb ? (wifi ? "both" : "usb") : (wifi ? "wifi" : "off");
}
inline void formatUnknownSetting(char* reply, size_t capacity,
const char* setting) {
if (reply == nullptr || capacity == 0) return;
+34 -53
View File
@@ -2861,41 +2861,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
strcpy(reply, "Can't find GPS");
}
#endif
} else if (memcmp(command, "powersaving on", 14) == 0) {
#if defined(NRF52_PLATFORM)
if (sender_timestamp == 0 || _board->isUsbDataConnected()) {
strcpy(reply, "Error: USB serial connected");
} else {
_prefs->powersaving_enabled = 1;
_sensors->setPowerSavingEnabled(true);
savePrefs();
strcpy(reply, "on - Immediate effect");
}
#elif defined(ESP32) && !defined(WITH_BRIDGE)
if (sender_timestamp == 0 || _board->isUsbDataConnected()) {
strcpy(reply, "Error: USB serial connected");
} else {
_prefs->powersaving_enabled = 1;
_sensors->setPowerSavingEnabled(true);
savePrefs();
strcpy(reply, "on - After 2 minutes");
}
#elif defined(WITH_BRIDGE)
strcpy(reply, "Bridge not supported");
#else
strcpy(reply, "Board not supported");
#endif
} else if (memcmp(command, "powersaving off", 15) == 0) {
_prefs->powersaving_enabled = 0;
_sensors->setPowerSavingEnabled(false);
savePrefs();
strcpy(reply, "off");
} else if (memcmp(command, "powersaving", 11) == 0) {
if (_prefs->powersaving_enabled) {
strcpy(reply, "on");
} else {
strcpy(reply, "off");
}
} else if (strcmp(command, "powersaving on") == 0 || strcmp(command, "powersaving off") == 0) {
char canonical[24];
snprintf(canonical, sizeof(canonical), "set %s", command);
handleSetCmd(sender_timestamp, canonical, reply);
} else if (strcmp(command, "powersaving") == 0) {
char canonical[] = "get powersaving";
handleGetCmd(sender_timestamp, canonical, reply);
} else if (memcmp(command, "sensor", 6) == 0) {
// I2C
#if ENV_HAS_SECONDARY_I2C
@@ -3122,6 +3094,18 @@ bool CommonCLI::handleSdCardGetCmd(const char* config, char* reply) {
void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* reply) {
const char* config = &command[4];
#if ENV_INCLUDE_GPS == 1
if (strncmp(config, "gps ", 4) == 0) {
if (strcmp(config + 4, "on") != 0 && strcmp(config + 4, "off") != 0) {
strcpy(reply, "Error: use set gps on|off");
} else {
char gps_command[8];
snprintf(gps_command, sizeof(gps_command), "%s", config);
handleCommand(sender_timestamp, gps_command, reply);
}
return;
}
#endif
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
if (strncmp(config, "espnow.channel", 14) == 0
&& (config[14] == 0 || config[14] == ' ' || config[14] == '\t')) {
@@ -3155,8 +3139,8 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
&& (config[11] == 0 || config[11] == ' ' || config[11] == '\t')) {
const char* value = &config[11];
while (*value == ' ' || *value == '\t') value++;
if (strcmp(value, "on") == 0 || strcmp(value, "off") == 0) {
const bool enabled = strcmp(value, "on") == 0;
bool enabled = false, reboot_if_needed = false;
if (mesh::cli::parseLoggingToggle(value, enabled, reboot_if_needed)) {
_prefs->usb_logging_enabled = enabled ? 1 : 0;
mesh::setUsbLoggingEnabled(enabled);
savePrefs();
@@ -3174,19 +3158,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
while (*value == ' ' || *value == '\t') value++;
bool usb_enabled;
bool wifi_enabled;
if (strcmp(value, "off") == 0) {
usb_enabled = false;
wifi_enabled = false;
} else if (strcmp(value, "usb") == 0) {
usb_enabled = true;
wifi_enabled = false;
} else if (strcmp(value, "wifi") == 0) {
usb_enabled = false;
wifi_enabled = true;
} else if (strcmp(value, "both") == 0) {
usb_enabled = true;
wifi_enabled = true;
} else {
if (!mesh::cli::parseLoggingOutput(value, usb_enabled, wifi_enabled)) {
strcpy(reply, "Error: usage set logging.output off|usb|wifi|both");
return;
}
@@ -4329,6 +4301,17 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* reply) {
const char* config = &command[4];
if (strcmp(config, "powersaving") == 0) {
snprintf(reply, 160, "> %s", _prefs->powersaving_enabled ? "on" : "off");
return;
}
#if ENV_INCLUDE_GPS == 1
if (strcmp(config, "gps") == 0) {
char gps_command[] = "gps";
handleCommand(sender_timestamp, gps_command, reply);
return;
}
#endif
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
if (strcmp(config, "espnow.channel") == 0) {
const uint8_t saved = mesh::wifi::loadConfiguredEspNowChannel();
@@ -4353,9 +4336,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
if (strcmp(config, "logging.output") == 0) {
const bool usb_enabled = mesh::isUsbLoggingEnabled();
const bool wifi_enabled = _prefs->bridge_enabled != 0;
const char* mode = usb_enabled
? (wifi_enabled ? "both" : "usb")
: (wifi_enabled ? "wifi" : "off");
const char* mode = mesh::cli::loggingOutputName(usb_enabled, wifi_enabled);
snprintf(reply, 160, "> %s", mode);
return;
}
+19
View File
@@ -182,6 +182,17 @@ static void formatMQTTPresetListReply(char* reply, size_t reply_size, int start)
bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* config, char* reply) {
#ifdef WITH_MQTT_BRIDGE
bool handled = true;
if (strncmp(config, "mqtt.enabled ", 13) == 0) {
const char* value = config + 13;
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
strcpy(reply, "Error: use set mqtt.enabled on|off");
} else {
char canonical[24];
snprintf(canonical, sizeof(canonical), "set bridge.enabled %s", value);
handleSetCmd(sender_timestamp, canonical, reply);
}
return true;
}
const auto restart_observer_bridge = [this]() {
return mesh::cli::restartBridgeIfEnabled(
_prefs->bridge_enabled != 0,
@@ -871,6 +882,14 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* config, char* reply) {
#ifdef WITH_MQTT_BRIDGE
bool handled = true;
if (strcmp(config, "mqtt.enabled") == 0) {
snprintf(reply, 160, "> %s", _prefs->bridge_enabled ? "on" : "off");
return true;
}
if (strcmp(config, "mqtt.running") == 0) {
snprintf(reply, 160, "> %s", _callbacks->isBridgeRunning() ? "on" : "off");
return true;
}
if (memcmp(config, "snmp.community", 14) == 0) {
sprintf(reply, "> %s", _mqtt_prefs.snmp_community);
} else if (memcmp(config, "snmp", 4) == 0 && (config[4] == '\0' || config[4] == '\n' || config[4] == '\r')) {
+16
View File
@@ -637,6 +637,22 @@ bool CompanionMqttSetupPortal::loadStoredConfig(MQTTPrefs& prefs) {
return true;
}
bool CompanionMqttSetupPortal::loadEnabled() {
Preferences nvs;
if (!nvs.begin(NVS_NAMESPACE, false)) return true;
const bool enabled = !nvs.isKey("enabled") || nvs.getBool("enabled", true);
nvs.end();
return enabled;
}
bool CompanionMqttSetupPortal::saveEnabled(bool enabled) {
Preferences nvs;
if (!nvs.begin(NVS_NAMESPACE, false)) return false;
const bool saved = nvs.putBool("enabled", enabled) == sizeof(bool);
nvs.end();
return saved;
}
bool CompanionMqttSetupPortal::saveStoredConfig(const MQTTPrefs& prefs) {
Preferences nvs;
if (!nvs.begin(NVS_NAMESPACE, false)) return false;
+2
View File
@@ -21,6 +21,8 @@ public:
static bool loadStoredConfig(MQTTPrefs& prefs);
static bool saveStoredConfig(const MQTTPrefs& prefs);
static bool loadEnabled();
static bool saveEnabled(bool enabled);
private:
volatile bool _active;
+9
View File
@@ -2,6 +2,7 @@
#include <stddef.h> // size_t / NULL for the reply classifiers below
#include <stdint.h>
#include <string.h>
// Fork-owned, dependency-free spec for the WebConfig "config batch / reboot /
// stop" decision + timing core, plus its host tests (test/test_webconfig_batch/).
@@ -320,6 +321,14 @@ static inline bool cliWriteSucceeded(const char* reply) {
return reply != NULL && reply[0] == 'O' && reply[1] == 'K';
}
// CommonCLI echoes a newly saved password instead of returning "OK". Mask
// that success before sending it over HTTP, while preserving unsupported-role
// errors from roles such as Companion which have no admin password.
static inline bool cliPasswordChanged(const char* reply) {
return cliWriteSucceeded(reply)
|| (reply != NULL && strncmp(reply, "password now:", 13) == 0);
}
// --------------------------------------------------------------------------
// Reboot fire (.cpp:262-265) and isRebootPending (.cpp:70-74).
// --------------------------------------------------------------------------
+1 -1
View File
@@ -26,7 +26,7 @@ static const char* const WC_ALLOWED_SET_KEYS[] = {
"flood.max", "flood.max.advert", "flood.max.unscoped", "loop.detect",
// MQTTPrefs (WiFi / MQTT / misc observer)
"wifi.ssid", "wifi.pwd", "wifi.powersave", "espnow.channel",
"mqtt.origin", "mqtt.iata", "mqtt.status", "mqtt.packets", "mqtt.raw",
"mqtt.enabled", "mqtt.origin", "mqtt.iata", "mqtt.status", "mqtt.packets", "mqtt.raw",
"mqtt.tx", "mqtt.rx", "mqtt.interval", "mqtt.neighbors", "mqtt.neighbors.interval",
"mqtt.ntp", "mqtt.owner", "mqtt.email",
"timezone", "timezone.offset", "snmp", "snmp.community",
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -1303,7 +1303,7 @@ void WebConfigServer::drainBatch(uint32_t now) {
}
if (e.reply[0] == 0) strcpy(e.reply, "(no reply)");
const bool set_admin_pwd = wcCliEchoesSecret(e.cmd);
if (set_admin_pwd) {
if (set_admin_pwd && WebConfigBatch::cliPasswordChanged(e.reply)) {
strcpy(e.reply, "OK");
_admin_pwd_set = true;
}
@@ -1817,6 +1817,7 @@ void WebConfigServer::handleConfigGet(AsyncWebServerRequest* req) {
MQTTPrefs* obs = static_cast<MQTTPrefs*>(_mqtt_prefs);
if (obs) {
JsonObject mqtt = doc.createNestedObject("mqtt");
mqtt["enabled"] = node.mqtt_enabled;
mqtt["origin"] = (const char*)obs->mqtt_origin;
mqtt["iata"] = (const char*)obs->mqtt_iata;
mqtt["status"] = (bool)obs->mqtt_status_enabled;
+1
View File
@@ -117,6 +117,7 @@ public:
uint32_t rx_ps_rx_us;
uint32_t rx_ps_sleep_us;
uint8_t power_saving;
bool mqtt_enabled;
uint8_t repeat;
uint16_t advert_interval;
uint8_t flood_advert_interval;