From ce9679a45f236fa6fcd6555418cd31a79e3ecee2 Mon Sep 17 00:00:00 2001 From: Claas Date: Fri, 28 Aug 2026 23:01:02 +0200 Subject: [PATCH] Join the Wi-Fi network again after losing the link Joining happened once, during start up, and the control handle was dropped straight after. Nothing could rejoin because nothing still owned the handle that joining needs. That held up until the access point went away. The mesh access point this controller reaches the network through is switched off overnight, and the controller never came back: it sat transmitting to an access point that was not there for five days, while the fans, the button and the LEDs carried on working perfectly. Only the network was gone, and nothing on the device could say so, because saying so is what the network is for. So keep the handle and give it to a routine that watches the link and joins again when it drops. The MQTT session already reconnects when the broker goes away; this is the same idea one layer down, and without it that reconnect loop retries forever over a radio link that can never work again. Joining also waits between attempts now. Retrying as fast as the chip can answer is harmless for the one attempt at start up and a busy loop for the whole night once it can run whenever an access point is off. Co-Authored-By: Claude Opus 5 --- fan-controller/src/configuration.rs | 8 +++++ fan-controller/src/task.rs | 49 ++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/fan-controller/src/configuration.rs b/fan-controller/src/configuration.rs index ee879c9..2595c89 100644 --- a/fan-controller/src/configuration.rs +++ b/fan-controller/src/configuration.rs @@ -49,6 +49,14 @@ const _: () = { core::assert!(seconds == 60); }; +/// How often the Wi-Fi link is checked once it is up, to notice the access point going away. +pub(crate) const WIFI_LINK_CHECK_INTERVAL: Duration = Duration::from_secs(5); + +/// How long to wait between attempts to join the Wi-Fi network. Without it a network that is not +/// there is retried as fast as the chip can answer, which is a busy loop for as long as the +/// access point stays off. +pub(crate) const WIFI_JOIN_RETRY_DELAY: Duration = Duration::from_secs(5); + /// The timeout not to be confused with the keep alive interval is used for packets that require a /// response packet from the broker. If the client does not receive a response within the timeout /// the client will stop waiting for a response which can lead to a disconnect or retry in some cases. diff --git a/fan-controller/src/task.rs b/fan-controller/src/task.rs index f3e4fb2..832f665 100644 --- a/fan-controller/src/task.rs +++ b/fan-controller/src/task.rs @@ -529,6 +529,7 @@ pub(super) async fn set_up_network_stack( join_wifi_network(&mut control).await; info!("[Network] Joined wifi network"); + // Wait for DHCP info!("[Network] Waiting for DHCP"); while !stack.is_config_up() { @@ -548,9 +549,44 @@ pub(super) async fn set_up_network_stack( stack.wait_config_up().await; info!("[Network] Stack is up"); + // Hand the control handle on rather than dropping it, so the link can be rebuilt later + unwrap!(spawner.spawn(rejoin_wifi_routine(control, stack))); + stack } +/// Joins the Wi-Fi network again whenever the link goes down. +/// +/// Joining once at start up is enough right up until the access point goes away. The mesh access +/// point this controller reached the network through is switched off overnight, and nothing ever +/// brought the link back: the fans, the button and the LEDs carried on working perfectly while +/// the controller sat unreachable for days, transmitting to an access point that was not there. +/// +/// The MQTT session already reconnects when the broker goes away. This is the same idea one layer +/// down, and without it that reconnect loop retries forever over a radio link that can never work +/// again. +#[embassy_executor::task] +async fn rejoin_wifi_routine( + mut control: Control<'static>, + stack: &'static Stack>, +) { + loop { + // Nothing to do for as long as the link holds + while stack.is_link_up() { + Timer::after(configuration::WIFI_LINK_CHECK_INTERVAL).await; + } + + warn!("[Network] Wi-Fi link is down. Joining again"); + join_wifi_network(&mut control).await; + info!("[Network] Joined wifi network again"); + + // An address has to be picked up again before anything can use the stack + info!("[Network] Waiting for the stack to come back up"); + stack.wait_config_up().await; + info!("[Network] Stack is up again"); + } +} + async fn join_wifi_network(control: &mut Control<'_>) { loop { info!("[Join Wifi] Attempting to join Wi-Fi network"); @@ -559,10 +595,15 @@ async fn join_wifi_network(control: &mut Control<'_>) { .await { Ok(_) => break, - Err(error) => info!( - "[Join Wifi] Error joining Wi-Fi network with status: {}", - error.status - ), + Err(error) => { + info!( + "[Join Wifi] Error joining Wi-Fi network with status: {}", + error.status + ); + // An access point that is switched off for the night is not going to answer any + // sooner for being asked continuously + Timer::after(configuration::WIFI_JOIN_RETRY_DELAY).await; + } } } } -- 2.51.2