Make Indicator WiFi setup state actionable

This commit is contained in:
mikecarper
2026-08-31 20:54:56 -07:00
parent 3bd738c1c9
commit 8ce1deacfe
4 changed files with 76 additions and 9 deletions
+7
View File
@@ -45,6 +45,7 @@ enum class CompanionWiFiPowerSaveResult : uint8_t {
// from the main loop so a button callback never tears down an active server.
bool toggleCompanionWiFi();
bool isCompanionWiFiEnabled();
bool hasCompanionWiFiCredentials();
// Display-facing station state. Arduino's cached WL status can briefly lag the
// ESP-IDF association record, so accept either source while the AP link is
// live. Unlike localIP(), the driver record is cleared on a real disconnect.
@@ -55,6 +56,7 @@ enum class CompanionWiFiDisplayState : uint8_t {
Setup,
Off,
Ready,
NotConfigured,
Connecting,
};
@@ -64,6 +66,11 @@ enum class CompanionWiFiDisplayState : uint8_t {
void noteCompanionWiFiDisplayState(CompanionWiFiDisplayState state);
void formatCompanionWiFiDisplayStatus(char* reply, size_t reply_size);
// A display callback only queues this request. The main loop owns the actual
// WebConfig/WiFi transition so touch handling never tears down network services
// from the UI task.
void requestCompanionWiFiSetup();
// Reload saved credentials after a text-terminal update. The reconnect is
// deferred so the command reply can leave USB/TCP before WiFi is restarted.
void scheduleCompanionWiFiCredentialReload();
+20 -1
View File
@@ -818,6 +818,7 @@ void halt() {
static bool companion_wifi_active = false;
static bool companion_wifi_disable_in_progress = false;
static bool companion_wifi_services_stopped = false;
static volatile bool companion_wifi_setup_requested = false;
static bool companion_wifi_credential_reload_pending = false;
static unsigned long companion_wifi_credential_reload_at = 0;
static bool companion_wifi_power_save_loaded = false;
@@ -1149,6 +1150,10 @@ void halt() {
return companion_wifi_requested;
}
bool hasCompanionWiFiCredentials() {
return companion_wifi_has_credentials;
}
bool isCompanionWiFiConnected() {
// The display describes the station link, not the lifecycle of the
// Companion TCP services. A valid station association remains the source
@@ -1167,7 +1172,8 @@ void halt() {
void formatCompanionWiFiDisplayStatus(char* reply, size_t reply_size) {
if (reply == nullptr || reply_size == 0) return;
static const char* const names[] = {
"not-rendered", "setup", "off", "ready", "connecting",
"not-rendered", "setup", "off", "ready", "not-configured",
"connecting",
};
uint8_t state = static_cast<uint8_t>(companion_wifi_display_state);
if (state >= sizeof(names) / sizeof(names[0])) state = 0;
@@ -1186,6 +1192,10 @@ void halt() {
WiFi.localIP().toString().c_str());
}
void requestCompanionWiFiSetup() {
companion_wifi_setup_requested = true;
}
bool toggleCompanionWiFi() {
#if defined(COMPANION_EXCLUSIVE_WIFI_BLE)
const CompanionTransportMode current = getCompanionTransportMode();
@@ -2156,6 +2166,15 @@ void loop() {
#if defined(ESP32) && defined(WIFI_SSID)
#ifdef WITH_WEBCONFIG
the_mesh.serviceWebConfig();
if (companion_wifi_setup_requested) {
companion_wifi_setup_requested = false;
if (companion_wifi_requested) {
char web_reply[160];
if (!the_mesh.startWebConfig(true, web_reply)) {
WIFI_DEBUG_PRINTLN("Display WiFi setup request: %s", web_reply);
}
}
}
#endif
serviceCompanionWiFiCredentialReload();
serviceCompanionWiFiState();
+41 -8
View File
@@ -125,13 +125,16 @@ static void drawCompanionWiFiSetupPage(DisplayDriver& display) {
setup_ssid, sizeof(setup_ssid), setup_ip, sizeof(setup_ip));
const bool wifi_enabled = isCompanionWiFiEnabled();
const bool wifi_connected = isCompanionWiFiConnected();
const bool wifi_configured = hasCompanionWiFiCredentials();
const CompanionWiFiDisplayState state = setup_active
? CompanionWiFiDisplayState::Setup
: !wifi_enabled
? CompanionWiFiDisplayState::Off
: wifi_connected
? CompanionWiFiDisplayState::Ready
: CompanionWiFiDisplayState::Connecting;
: !wifi_configured
? CompanionWiFiDisplayState::NotConfigured
: CompanionWiFiDisplayState::Connecting;
static CompanionWiFiDisplayState previous_state =
CompanionWiFiDisplayState::NotRendered;
@@ -189,6 +192,12 @@ static void drawCompanionWiFiSetupPage(DisplayDriver& display) {
display.drawTextEllipsized(4, 60, display.width() - 8, ssid.c_str());
const String ip = WiFi.localIP().toString();
display.drawTextCentered(display.width() / 2, 95, ip.c_str());
} else if (!wifi_configured) {
display.setTextSize(3);
display.drawTextCentered(display.width() / 2, 45, "WIFI SETUP");
display.setTextSize(2);
display.setColor(UIColor::secondary_txt);
display.drawTextCentered(display.width() / 2, 90, "TAP TO START");
} else {
display.setTextSize(3);
display.drawTextCentered(display.width() / 2, 45, "WIFI");
@@ -558,12 +567,16 @@ public:
"tap center: inbox");
#ifdef WIFI_SSID
if (isCompanionWiFiEnabled()) {
if (!isCompanionWiFiEnabled()) {
strcpy(tmp, "WiFi: OFF");
} else if (isCompanionWiFiConnected()) {
IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d",
ip[0], ip[1], ip[2], ip[3]);
} else if (hasCompanionWiFiCredentials()) {
strcpy(tmp, "WiFi: CONNECTING");
} else {
strcpy(tmp, "WiFi: OFF");
strcpy(tmp, "WiFi: SETUP");
}
display.setTextSize(1);
mesh::ui::drawTextCenteredEllipsized(
@@ -607,14 +620,20 @@ public:
mesh::ui::drawTextCenteredEllipsized(
display, layout.pairing, layout.pairing_label_y, "TAP");
#ifdef WIFI_SSID
if (isCompanionWiFiEnabled()) {
if (!isCompanionWiFiEnabled()) {
strcpy(tmp, "OFF");
display.setTextSize(3);
} else if (isCompanionWiFiConnected()) {
IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d",
ip[0], ip[1], ip[2], ip[3]);
display.setTextSize(1);
} else {
strcpy(tmp, "OFF");
} else if (hasCompanionWiFiCredentials()) {
strcpy(tmp, "WAIT");
display.setTextSize(3);
} else {
strcpy(tmp, "SETUP");
display.setTextSize(2);
}
mesh::ui::drawTextCenteredEllipsized(
display, layout.pairing, layout.pairing_value_y, tmp);
@@ -677,12 +696,16 @@ public:
#endif
#ifdef WIFI_SSID
if (isCompanionWiFiEnabled()) {
if (!isCompanionWiFiEnabled()) {
strcpy(tmp, "WiFi: OFF");
} else if (isCompanionWiFiConnected()) {
IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d",
ip[0], ip[1], ip[2], ip[3]);
} else if (hasCompanionWiFiCredentials()) {
strcpy(tmp, "WiFi: CONNECTING");
} else {
strcpy(tmp, "WiFi: OFF");
strcpy(tmp, "WiFi: SETUP");
}
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 54, tmp);
@@ -1026,6 +1049,16 @@ public:
_task->showMessages();
return true;
}
#endif
#if UI_WIFI_SETUP_HOME_PAGE == 1
if (c == KEY_ENTER && _page == HomePage::WIFI_SETUP) {
if (isCompanionWiFiEnabled() && !isCompanionWiFiConnected()
&& !WebConfigServer::getSetupInfo(nullptr, 0, nullptr, 0)) {
requestCompanionWiFiSetup();
_task->showAlert("Starting WiFi setup", 1000);
}
return true;
}
#endif
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
_task->notify(UIEventType::ack);
+8
View File
@@ -177,6 +177,9 @@ int main() {
self.assertIn("if (_page == HomePage::WIFI_SETUP) return 1000;", ui)
self.assertIn("wifi_setup_active && !_wifi_setup_was_active", ui)
self.assertIn("isCompanionWiFiConnected()", page)
self.assertIn("hasCompanionWiFiCredentials()", page)
self.assertIn("CompanionWiFiDisplayState::NotConfigured", page)
self.assertIn('"TAP TO START"', page)
self.assertIn("wifi_connected != _wifi_was_connected", ui)
self.assertIn("CompanionWiFiDisplayState::Ready", page)
self.assertIn("display.clear()", page)
@@ -191,6 +194,9 @@ int main() {
mesh = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text()
self.assertIn('strcmp(command, "get display.wifi")', mesh)
self.assertIn("_task->gotoHomeScreen();", ui)
self.assertIn("requestCompanionWiFiSetup();", ui)
self.assertIn('strcpy(tmp, "WiFi: SETUP");', ui)
self.assertIn('strcpy(tmp, "SETUP");', ui)
main = MAIN.read_text(encoding="utf-8")
loop = main[main.index("\nvoid loop()") :]
@@ -199,6 +205,8 @@ int main() {
self.assertIn("&& UI_WIFI_SETUP_HOME_PAGE != 1", display_dispatch)
self.assertIn("renderCompanionSetupDisplay();", display_dispatch)
self.assertIn("#else\n ui_task.loop();", display_dispatch)
self.assertIn('"not-configured"', main)
self.assertIn("companion_wifi_setup_requested", loop)
def test_wifi_qr_payload_is_standard_and_escaped(self):
setup = WIFI_QR_PAYLOAD.read_text(encoding="utf-8")