diff --git a/src/net/nl_wifi_sm.c b/src/net/nl_wifi_sm.c index 381bd90..e606d49 100644 --- a/src/net/nl_wifi_sm.c +++ b/src/net/nl_wifi_sm.c @@ -40,7 +40,15 @@ static void arm_ap_stop(nl_wifi_sm_t *sm, uint32_t at) static void fail_test(nl_wifi_sm_t *sm, nl_wifi_error_t err, uint32_t now, nl_wifi_actions_t *a) { sm->error = err; - if (sm->have_creds) { + if (sm->standalone) { + /* Zurueck in den EIGENEN AP. Der offene Onboarding-AP waere hier doppelt falsch: + * er verloere Name und Passwort des Nutzers, und open_access() machte die Lampe + * ohne Token flashbar. Ein Tippfehler im WLAN-Passwort darf sie nicht entsichern. */ + sm->state = NL_WIFI_ST_STANDALONE; + sm->ap_up = true; + a->disconnect = true; + a->start_ap = true; + } else if (sm->have_creds) { sm->state = NL_WIFI_ST_APSTA_FALLBACK; sm->since = now; sm->backoff = NL_WIFI_BACKOFF_MIN_MS; @@ -114,6 +122,10 @@ nl_wifi_actions_t nl_wifi_sm_event(nl_wifi_sm_t *sm, nl_wifi_event_t ev, nl_wifi case NL_WIFI_ST_ONBOARD_TEST: if (ev == NL_WIFI_EV_CONNECTED) { a.save_test_creds = true; + if (sm->standalone) { + sm->standalone = false; + a.clear_standalone = true; + } sm->have_creds = true; sm->error = NL_WIFI_ERR_NONE; sm->state = NL_WIFI_ST_STA_CONNECTED; @@ -195,12 +207,71 @@ nl_wifi_actions_t nl_wifi_sm_onboard(nl_wifi_sm_t *sm, uint32_t now) sm->deadline = now + NL_WIFI_ONBOARD_TIMEOUT_MS; sm->retry_armed = false; sm->ap_stop_armed = false; + if (sm->pulse_armed) { + /* Der Startpuls gehoert nicht in den Test: sonst feuerte spaeter ein pulse_off, das zu + * keinem Puls mehr gehoert. Hier abraeumen; pulsing auch, damit er nicht haengen bleibt. */ + sm->pulse_armed = false; + sm->pulsing = false; + a.pulse_off = true; + } a.disconnect = true; /* M1: drop any stale station attempt so its events don't count */ a.connect = true; a.use_test_creds = true; return a; } +nl_wifi_actions_t nl_wifi_sm_standalone(nl_wifi_sm_t *sm, bool pulse, uint32_t now) +{ + nl_wifi_actions_t a = none(); + + sm->standalone = true; + sm->state = NL_WIFI_ST_STANDALONE; + sm->error = NL_WIFI_ERR_NONE; + sm->retry_armed = false; + sm->ap_stop_armed = false; + sm->awaiting = false; + a.disconnect = true; /* eine laufende Stationsverbindung aufgeben */ + if (!sm->ap_up) { + sm->ap_up = true; + a.start_ap = true; + } + if (pulse) { + sm->pulsing = true; + sm->pulse_armed = true; + sm->pulse_stop_at = now + NL_WIFI_STANDALONE_PULSE_MS; + a.pulse_on = true; + } + return a; +} + +nl_wifi_actions_t nl_wifi_sm_leave_standalone(nl_wifi_sm_t *sm, bool have_creds, uint32_t now) +{ + nl_wifi_actions_t a = none(); + + sm->standalone = false; + sm->have_creds = have_creds; + sm->pulse_armed = false; + if (have_creds) { + begin_connecting(sm, now); + a.connect = true; + sm->awaiting = true; + sm->connect_at = now; + if (sm->pulsing) { + sm->pulsing = false; + a.pulse_off = true; + } + } else { + /* Ohne Zugangsdaten bliebe die Lampe sonst unerreichbar: der offene + * Onboarding-AP ist hier der gewollte Ausweg. */ + sm->state = NL_WIFI_ST_AP_ONBOARDING; + sm->ap_up = true; + sm->pulsing = true; + a.start_ap = true; + a.pulse_on = true; + } + return a; +} + bool nl_wifi_sm_open_access(const nl_wifi_sm_t *sm) { /* !standalone is NOT redundant: during an onboarding test started from standalone mode diff --git a/src/net/nl_wifi_sm.h b/src/net/nl_wifi_sm.h index 817d987..17c7639 100644 --- a/src/net/nl_wifi_sm.h +++ b/src/net/nl_wifi_sm.h @@ -35,6 +35,7 @@ typedef struct { bool disconnect; /* drop a stray/failed station attempt (before any connect in the same set) */ bool pulse_on; bool pulse_off; + bool clear_standalone; /* Onboarding gelungen: den Standalone-Modus dauerhaft loeschen */ } nl_wifi_actions_t; typedef struct { @@ -61,6 +62,11 @@ nl_wifi_actions_t nl_wifi_sm_start(nl_wifi_sm_t *sm, bool have_creds, bool stand nl_wifi_actions_t nl_wifi_sm_event(nl_wifi_sm_t *sm, nl_wifi_event_t ev, nl_wifi_error_t err, uint32_t now); nl_wifi_actions_t nl_wifi_sm_tick(nl_wifi_sm_t *sm, uint32_t now); nl_wifi_actions_t nl_wifi_sm_onboard(nl_wifi_sm_t *sm, uint32_t now); +/* Schaltet zur Laufzeit in den Standalone-Modus (aus der API). */ +nl_wifi_actions_t nl_wifi_sm_standalone(nl_wifi_sm_t *sm, bool pulse, uint32_t now); +/* Nimmt ihn zurueck: mit gespeicherten Zugangsdaten ins WLAN, sonst in den + offenen Onboarding-AP. */ +nl_wifi_actions_t nl_wifi_sm_leave_standalone(nl_wifi_sm_t *sm, bool have_creds, uint32_t now); bool nl_wifi_sm_open_access(const nl_wifi_sm_t *sm); const char *nl_wifi_sm_mode(const nl_wifi_sm_t *sm); const char *nl_wifi_error_name(nl_wifi_error_t e); diff --git a/test/test_wifi_sm.c b/test/test_wifi_sm.c index baab467..c75a1be 100644 --- a/test/test_wifi_sm.c +++ b/test/test_wifi_sm.c @@ -282,6 +282,106 @@ static void test_standalone_without_pulse_stays_dark(void) TEST_ASSERT_FALSE(nl_wifi_sm_tick(&sm, NL_WIFI_STANDALONE_PULSE_MS).pulse_off); } +/* Erfolg: die Lampe wechselt ins WLAN UND verlaesst den Standalone-Modus dauerhaft. + * Ohne clear_standalone staende sie nach dem naechsten Neustart wieder im eigenen AP. */ +static void test_onboarding_from_standalone_clears_the_mode(void) +{ + nl_wifi_sm_start(&sm, false, true, false, 0); + nl_wifi_sm_onboard(&sm, 1000); + a = nl_wifi_sm_event(&sm, NL_WIFI_EV_CONNECTED, NL_WIFI_ERR_NONE, 2000); + TEST_ASSERT_TRUE(a.save_test_creds); + TEST_ASSERT_TRUE(a.clear_standalone); + TEST_ASSERT_FALSE(sm.standalone); + TEST_ASSERT_EQUAL(NL_WIFI_ST_STA_CONNECTED, sm.state); +} + +/* Fehlschlag: zurueck in den EIGENEN AP, nicht in den offenen Onboarding-AP. Sonst + * verloere ein Tippfehler im WLAN-Passwort den vom Nutzer gesetzten AP-Namen samt + * Passwort und entsicherte die Lampe, weil open_access() dann wieder true waere. */ +static void test_failed_onboarding_returns_to_standalone(void) +{ + nl_wifi_sm_start(&sm, false, true, false, 0); + nl_wifi_sm_onboard(&sm, 1000); + a = nl_wifi_sm_event(&sm, NL_WIFI_EV_JOIN_FAILED, NL_WIFI_ERR_AUTH, 2000); + TEST_ASSERT_EQUAL(NL_WIFI_ST_STANDALONE, sm.state); + TEST_ASSERT_TRUE(sm.standalone); + TEST_ASSERT_FALSE(nl_wifi_sm_open_access(&sm)); + TEST_ASSERT_FALSE(a.clear_standalone); + TEST_ASSERT_EQUAL(NL_WIFI_ERR_AUTH, sm.error); +} + +/* Auch der Zeitablauf des Tests fuehrt zurueck in den eigenen AP. */ +static void test_onboarding_timeout_returns_to_standalone(void) +{ + nl_wifi_sm_start(&sm, false, true, false, 0); + nl_wifi_sm_onboard(&sm, 1000); + nl_wifi_sm_tick(&sm, 1000 + NL_WIFI_ONBOARD_TIMEOUT_MS); + TEST_ASSERT_EQUAL(NL_WIFI_ST_STANDALONE, sm.state); + TEST_ASSERT_EQUAL(NL_WIFI_ERR_TIMEOUT, sm.error); +} + +/* Der bisherige Weg bleibt unberuehrt: ohne Standalone und ohne gespeicherte Daten + * landet ein gescheiterter Test weiterhin im offenen Onboarding-AP. */ +static void test_failed_onboarding_without_standalone_opens_ap(void) +{ + nl_wifi_sm_start(&sm, false, false, false, 0); + nl_wifi_sm_onboard(&sm, 1000); + nl_wifi_sm_event(&sm, NL_WIFI_EV_JOIN_FAILED, NL_WIFI_ERR_AUTH, 2000); + TEST_ASSERT_EQUAL(NL_WIFI_ST_AP_ONBOARDING, sm.state); + TEST_ASSERT_TRUE(nl_wifi_sm_open_access(&sm)); +} + +/* Aus dem laufenden Stationsbetrieb in den eigenen AP. */ +static void test_switching_to_standalone_at_runtime(void) +{ + nl_wifi_sm_start(&sm, true, false, false, 0); + nl_wifi_sm_event(&sm, NL_WIFI_EV_CONNECTED, NL_WIFI_ERR_NONE, 1000); + a = nl_wifi_sm_standalone(&sm, true, 2000); + TEST_ASSERT_TRUE(a.disconnect); + TEST_ASSERT_TRUE(a.start_ap); + TEST_ASSERT_TRUE(a.pulse_on); + TEST_ASSERT_EQUAL(NL_WIFI_ST_STANDALONE, sm.state); + TEST_ASSERT_TRUE(sm.standalone); + TEST_ASSERT_TRUE(nl_wifi_sm_tick(&sm, 2000 + NL_WIFI_STANDALONE_PULSE_MS).pulse_off); +} + +/* Zuruecknehmen MIT gespeicherten Zugangsdaten: ab ins WLAN, nicht in den offenen AP. */ +static void test_leaving_standalone_with_credentials_connects(void) +{ + nl_wifi_sm_start(&sm, true, true, false, 0); + a = nl_wifi_sm_leave_standalone(&sm, true, 1000); + TEST_ASSERT_TRUE(a.connect); + TEST_ASSERT_FALSE(sm.standalone); + TEST_ASSERT_EQUAL(NL_WIFI_ST_STA_CONNECTING, sm.state); +} + +/* Zuruecknehmen OHNE Zugangsdaten: offener Onboarding-AP, damit man wieder herankommt. */ +static void test_leaving_standalone_without_credentials_opens_onboarding(void) +{ + nl_wifi_sm_start(&sm, false, true, false, 0); + a = nl_wifi_sm_leave_standalone(&sm, false, 1000); + TEST_ASSERT_TRUE(a.start_ap); + TEST_ASSERT_TRUE(a.pulse_on); + TEST_ASSERT_EQUAL(NL_WIFI_ST_AP_ONBOARDING, sm.state); + TEST_ASSERT_TRUE(nl_wifi_sm_open_access(&sm)); +} + +/* Laeuft beim Start eines Onboarding-Tests noch der Startpuls, raeumt onboard() ihn ab: + * pulse_off sofort, danach kein verwaister pulse_off mehr -- weder nach einem + * gescheiterten noch nach einem gelungenen Test. */ +static void test_onboard_cancels_running_start_pulse(void) +{ + nl_wifi_sm_start(&sm, false, true, true, 0); + a = nl_wifi_sm_onboard(&sm, 1000); + TEST_ASSERT_TRUE(a.pulse_off); + TEST_ASSERT_FALSE(sm.pulse_armed); + TEST_ASSERT_FALSE(sm.pulsing); + TEST_ASSERT_FALSE(nl_wifi_sm_tick(&sm, NL_WIFI_STANDALONE_PULSE_MS).pulse_off); + a = nl_wifi_sm_event(&sm, NL_WIFI_EV_CONNECTED, NL_WIFI_ERR_NONE, 6000); + TEST_ASSERT_FALSE(a.pulse_off); + TEST_ASSERT_FALSE(nl_wifi_sm_tick(&sm, 20000).pulse_off); +} + int main(void) { UNITY_BEGIN(); @@ -306,5 +406,13 @@ int main(void) RUN_TEST(test_standalone_drops_a_stray_connect); RUN_TEST(test_standalone_pulse_stops_by_itself); RUN_TEST(test_standalone_without_pulse_stays_dark); + RUN_TEST(test_onboarding_from_standalone_clears_the_mode); + RUN_TEST(test_failed_onboarding_returns_to_standalone); + RUN_TEST(test_onboarding_timeout_returns_to_standalone); + RUN_TEST(test_failed_onboarding_without_standalone_opens_ap); + RUN_TEST(test_switching_to_standalone_at_runtime); + RUN_TEST(test_leaving_standalone_with_credentials_connects); + RUN_TEST(test_leaving_standalone_without_credentials_opens_onboarding); + RUN_TEST(test_onboard_cancels_running_start_pulse); return UNITY_END(); }