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; + } } } }