diff --git a/src/main.cpp b/src/main.cpp index 6a024ca..2f1de85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -212,22 +212,16 @@ void setup() { return; } - // Attempt to join saved network + // Attempt to join saved network — uses the shared attempt_wifi() so the + // fast-fail-on-WL_CONNECT_FAILED behavior covers boot-with-stored-creds + // too, not just the captive-portal submission path. Serial.println("[wifi] connecting to ssid=" + ssid); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid.c_str(), pass.c_str()); - uint32_t start = millis(); - while (WiFi.status() != WL_CONNECTED) { - if (millis() - start > WIFI_TIMEOUT_MS) break; - delay(200); - } - - if (WiFi.status() == WL_CONNECTED) { + if (attempt_wifi(ssid, pass)) { Serial.println("[wifi] connected ip=" + WiFi.localIP().toString()); normal_operation(mac); // normal_operation calls deep_sleep — never returns } else { - Serial.println("[wifi] failed after " + String(WIFI_TIMEOUT_MS) + " ms — entering provisioning"); + Serial.println("[wifi] failed — entering provisioning"); enter_provisioning(mac); } } diff --git a/src/operation.h b/src/operation.h index 80979c7..d369507 100644 --- a/src/operation.h +++ b/src/operation.h @@ -80,11 +80,17 @@ inline bool attempt_wifi(const char* ssid, const char* pass) { WiFi.mode(WIFI_STA); WiFi.begin(ssid, pass); uint32_t start = millis(); - while (WiFi.status() != WL_CONNECTED) { + while (true) { + int s = WiFi.status(); + if (s == WL_CONNECTED) return true; + // Bail the moment the radio reports a terminal failure — bad PSK + // surfaces as WL_CONNECT_FAILED and missing SSID as WL_NO_SSID_AVAIL + // within a few seconds. Without this the user stares at the yellow + // Step 1/2 for the full WIFI_TIMEOUT_MS before the red retry repaints. + if (s == WL_CONNECT_FAILED || s == WL_NO_SSID_AVAIL) return false; if (millis() - start > WIFI_TIMEOUT_MS) return false; delay(200); } - return true; } // ── Reset button hold detection ─────────────────────────────────────────────── diff --git a/test/mocks/WiFi.h b/test/mocks/WiFi.h index c763ac5..db8791d 100644 --- a/test/mocks/WiFi.h +++ b/test/mocks/WiFi.h @@ -2,7 +2,9 @@ #include "Arduino.h" #define WIFI_STA 0 #define WIFI_AP 1 -#define WL_CONNECTED 3 +#define WL_NO_SSID_AVAIL 1 +#define WL_CONNECTED 3 +#define WL_CONNECT_FAILED 4 extern int g_wifi_status; diff --git a/test/test_provisioning/test_main.cpp b/test/test_provisioning/test_main.cpp index cab2bb3..c55a808 100644 --- a/test/test_provisioning/test_main.cpp +++ b/test/test_provisioning/test_main.cpp @@ -40,10 +40,32 @@ void test_fw14_attempt_wifi_returns_true_on_connect() { // millis() auto-increments by 10 on each call; after enough iterations the // elapsed time exceeds WIFI_TIMEOUT_MS (30000 ms). void test_fw15_attempt_wifi_returns_false_on_timeout() { - g_wifi_status = 0; // never WL_CONNECTED + g_wifi_status = 6; // WL_DISCONNECTED — never connects, never terminal-fails g_millis_value = 0; bool result = attempt_wifi("myssid", "mypass"); TEST_ASSERT_FALSE(result); + // Sanity: we actually waited the full timeout, not bailed early + TEST_ASSERT_GREATER_THAN(WIFI_TIMEOUT_MS, g_millis_value); +} + +// ── FW-15a: attempt_wifi bails fast on WL_CONNECT_FAILED (bad PSK) ──────────── +// Without fast-fail the user would wait the full 30 s before the red retry +// screen repaints; with it, failure surfaces in seconds. +void test_fw15a_attempt_wifi_returns_false_on_auth_fail() { + g_wifi_status = WL_CONNECT_FAILED; + g_millis_value = 0; + bool result = attempt_wifi("myssid", "wrongpass"); + TEST_ASSERT_FALSE(result); + TEST_ASSERT_LESS_THAN(WIFI_TIMEOUT_MS, g_millis_value); +} + +// ── FW-15b: attempt_wifi bails fast on WL_NO_SSID_AVAIL (network not visible) ─ +void test_fw15b_attempt_wifi_returns_false_on_no_ssid() { + g_wifi_status = WL_NO_SSID_AVAIL; + g_millis_value = 0; + bool result = attempt_wifi("notthere", "anypass"); + TEST_ASSERT_FALSE(result); + TEST_ASSERT_LESS_THAN(WIFI_TIMEOUT_MS, g_millis_value); } // ── FW-16: loop() state-machine (WiFi-credential submission path) ───────────── @@ -77,6 +99,8 @@ int main(int argc, char** argv) { UNITY_BEGIN(); RUN_TEST(test_fw14_attempt_wifi_returns_true_on_connect); RUN_TEST(test_fw15_attempt_wifi_returns_false_on_timeout); + RUN_TEST(test_fw15a_attempt_wifi_returns_false_on_auth_fail); + RUN_TEST(test_fw15b_attempt_wifi_returns_false_on_no_ssid); RUN_TEST(test_fw16_loop_state_machine_deferred); RUN_TEST(test_fw17_reset_button_held_returns_true); RUN_TEST(test_fw18_reset_button_not_pressed_returns_false);