From cd4d0cc683532cfecb8befff1f8801cff4818962 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sun, 17 May 2026 12:19:22 +0200 Subject: [PATCH 1/7] refactor: create network module - move NetworkMode and SntpMode enums --- Software/MainBoard/rust/src/main.rs | 41 +++++++++----------------- Software/MainBoard/rust/src/network.rs | 19 ++++++++++++ 2 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 Software/MainBoard/rust/src/network.rs diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 33abb63..4bd67f7 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -68,6 +68,7 @@ mod fat_error; mod hal; mod log; mod mqtt; +mod network; mod plant_state; mod tank; mod webserver; @@ -121,21 +122,7 @@ pub struct PumpResult { overcurrent_ma: Option, } -#[derive(Serialize, Debug, PartialEq)] -enum SntpMode { - Offline, - Sync { current: DateTime }, -} -#[derive(Serialize, Debug, PartialEq)] -enum NetworkMode { - Wifi { - sntp: SntpMode, - mqtt: bool, - ip_address: String, - }, - Offline, -} async fn safe_main(spawner: Spawner) -> FatResult<()> { info!("Startup Rust"); @@ -235,10 +222,10 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { info!("No wifi configured"); //the current sensors require this amount to stabilize, in the case of Wi-Fi this is already handled due to connect timings; Timer::after_millis(100).await; - NetworkMode::Offline + network::NetworkMode::OFFLINE }; - if matches!(network_mode, NetworkMode::Offline) && to_config { + if matches!(network_mode, network::NetworkMode::OFFLINE) && to_config { info!("Could not connect to station and config mode forced, switching to ap mode!"); let res = { @@ -271,7 +258,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { timezone_time ); - if let NetworkMode::Wifi { ref ip_address, .. } = network_mode { + if let network::NetworkMode::WIFI { ref ip_address, .. } = network_mode { publish_firmware_info(&mut board, version, ip_address, &timezone_time.to_rfc3339()).await; publish_battery_state(&mut board).await.unwrap_or_else(|e| { error!("Error publishing battery state {e}"); @@ -281,15 +268,15 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { log( LogMessage::StartupInfo, - matches!(network_mode, NetworkMode::Wifi { .. }) as u32, + matches!(network_mode, network::NetworkMode::WIFI { .. }) as u32, matches!( network_mode, - NetworkMode::Wifi { - sntp: SntpMode::Sync { .. }, + network::NetworkMode::WIFI { + sntp: network::SntpMode::SYNC { .. }, .. } ) as u32, - matches!(network_mode, NetworkMode::Wifi { mqtt: true, .. }) + matches!(network_mode, network::NetworkMode::WIFI { mqtt: true, .. }) .to_string() .as_str(), "", @@ -941,13 +928,13 @@ async fn try_connect_wifi_sntp_mqtt( board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>, stack_store: &mut OptionLock>, spawner: Spawner, -) -> NetworkMode { +) -> network::NetworkMode { let nw_conf = &board.board_hal.get_config().network.clone(); match board.board_hal.get_esp().wifi(nw_conf, spawner).await { Ok(stack) => { stack_store.replace(stack); - let sntp_mode: SntpMode = match board.board_hal.get_esp().sntp(1000 * 10, stack).await { + let sntp_mode: network::SntpMode = match board.board_hal.get_esp().sntp(1000 * 10, stack).await { Ok(new_time) => { info!("Using time from sntp {}", new_time.to_rfc3339()); let _ = board @@ -955,12 +942,12 @@ async fn try_connect_wifi_sntp_mqtt( .get_rtc_module() .set_rtc_time(&new_time) .await; - SntpMode::Sync { current: new_time } + network::SntpMode::SYNC { current: new_time } } Err(err) => { warn!("sntp error: {err}"); board.board_hal.general_fault(true).await; - SntpMode::Offline + network::SntpMode::OFFLINE } }; @@ -988,7 +975,7 @@ async fn try_connect_wifi_sntp_mqtt( None => String::from("No IP"), }, }; - NetworkMode::Wifi { + network::NetworkMode::WIFI { sntp: sntp_mode, mqtt: mqtt_connected, ip_address: ip, @@ -997,7 +984,7 @@ async fn try_connect_wifi_sntp_mqtt( Err(err) => { info!("Offline mode due to {err}"); board.board_hal.general_fault(true).await; - NetworkMode::Offline + network::NetworkMode::OFFLINE } } } diff --git a/Software/MainBoard/rust/src/network.rs b/Software/MainBoard/rust/src/network.rs new file mode 100644 index 0000000..48500a5 --- /dev/null +++ b/Software/MainBoard/rust/src/network.rs @@ -0,0 +1,19 @@ +use alloc::string::String; +use chrono::{DateTime, Utc}; +use serde::Serialize; + +#[derive(Serialize, Debug, PartialEq)] +pub enum SntpMode { + OFFLINE, + SYNC { current: DateTime }, +} + +#[derive(Serialize, Debug, PartialEq)] +pub enum NetworkMode { + WIFI { + sntp: SntpMode, + mqtt: bool, + ip_address: String, + }, + OFFLINE, +} From ba654a904bd60c884862c95d93733d82462de5b7 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sun, 17 May 2026 12:21:32 +0200 Subject: [PATCH 2/7] refactor: move net_task to network module --- Software/MainBoard/rust/src/hal/esp.rs | 9 +++------ Software/MainBoard/rust/src/network.rs | 7 +++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 1f35b16..50325da 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -40,7 +40,9 @@ use esp_radio::wifi::sta::StationConfig; use esp_radio::wifi::{AuthenticationMethod, Config, Interface, WifiController}; use log::{error, info, warn}; use portable_atomic::AtomicBool; -use sntpc::{get_time, NtpContext, NtpTimestampGenerator, NtpUdpSocket}; +use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; + +use crate::network::net_task; #[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; @@ -658,11 +660,6 @@ impl Esp<'_> { } -#[embassy_executor::task(pool_size = 2)] -async fn net_task(mut runner: Runner<'static, Interface<'static>>) { - runner.run().await; -} - #[embassy_executor::task] async fn run_dhcp(stack: Stack<'static>, ip: Ipv4Addr) { use core::net::SocketAddrV4; diff --git a/Software/MainBoard/rust/src/network.rs b/Software/MainBoard/rust/src/network.rs index 48500a5..c27a03d 100644 --- a/Software/MainBoard/rust/src/network.rs +++ b/Software/MainBoard/rust/src/network.rs @@ -1,5 +1,7 @@ use alloc::string::String; use chrono::{DateTime, Utc}; +use embassy_net::Runner; +use esp_radio::wifi::Interface; use serde::Serialize; #[derive(Serialize, Debug, PartialEq)] @@ -17,3 +19,8 @@ pub enum NetworkMode { }, OFFLINE, } + +#[embassy_executor::task(pool_size = 2)] +pub(crate) async fn net_task(mut runner: Runner<'static, Interface<'static>>) { + runner.run().await; +} From 5f9db41d65de7fc3426768975b20a7648a27f309 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sun, 17 May 2026 12:22:07 +0200 Subject: [PATCH 3/7] refactor: move run_dhcp to network module --- Software/MainBoard/rust/src/hal/esp.rs | 46 +------------------------- Software/MainBoard/rust/src/network.rs | 46 +++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 50325da..12f20f7 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -42,7 +42,7 @@ use log::{error, info, warn}; use portable_atomic::AtomicBool; use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; -use crate::network::net_task; +use crate::network::{net_task, run_dhcp}; #[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; @@ -659,47 +659,3 @@ impl Esp<'_> { } } - -#[embassy_executor::task] -async fn run_dhcp(stack: Stack<'static>, ip: Ipv4Addr) { - use core::net::SocketAddrV4; - - use edge_dhcp::{ - io::{self, DEFAULT_SERVER_PORT}, - server::{Server, ServerOptions}, - }; - use edge_nal::UdpBind; - use edge_nal_embassy::{Udp, UdpBuffers}; - - let mut buf = [0u8; 1500]; - - let mut gw_buf = [Ipv4Addr::UNSPECIFIED]; - - let buffers = UdpBuffers::<3, 1024, 1024, 10>::new(); - let unbound_socket = Udp::new(stack, &buffers); - let mut bound_socket = match unbound_socket - .bind(SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::UNSPECIFIED, - DEFAULT_SERVER_PORT, - ))) - .await - { - Ok(s) => s, - Err(e) => { - error!("dhcp task failed to bind socket: {:?}", e); - return; - } - }; - - loop { - _ = io::server::run( - &mut Server::<_, 64>::new_with_et(ip), - &ServerOptions::new(ip, Some(&mut gw_buf)), - &mut bound_socket, - &mut buf, - ) - .await - .inspect_err(|e| warn!("DHCP server error: {e:?}")); - Timer::after(Duration::from_millis(500)).await; - } -} diff --git a/Software/MainBoard/rust/src/network.rs b/Software/MainBoard/rust/src/network.rs index c27a03d..a208bac 100644 --- a/Software/MainBoard/rust/src/network.rs +++ b/Software/MainBoard/rust/src/network.rs @@ -1,7 +1,16 @@ use alloc::string::String; use chrono::{DateTime, Utc}; -use embassy_net::Runner; +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use embassy_net::{Runner, Stack}; +use embassy_time::{Duration, Timer}; +use edge_dhcp::{ + io::{self, DEFAULT_SERVER_PORT}, + server::{Server, ServerOptions}, +}; +use edge_nal::UdpBind; +use edge_nal_embassy::{Udp, UdpBuffers}; use esp_radio::wifi::Interface; +use log::{warn, error}; use serde::Serialize; #[derive(Serialize, Debug, PartialEq)] @@ -24,3 +33,38 @@ pub enum NetworkMode { pub(crate) async fn net_task(mut runner: Runner<'static, Interface<'static>>) { runner.run().await; } + +#[embassy_executor::task] +pub(crate) async fn run_dhcp(stack: Stack<'static>, ip: Ipv4Addr) { + let mut buf = [0u8; 1500]; + + let mut gw_buf = [Ipv4Addr::UNSPECIFIED]; + + let buffers = UdpBuffers::<3, 1024, 1024, 10>::new(); + let unbound_socket = Udp::new(stack, &buffers); + let mut bound_socket = match unbound_socket + .bind(SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::UNSPECIFIED, + DEFAULT_SERVER_PORT, + ))) + .await + { + Ok(s) => s, + Err(e) => { + error!("dhcp task failed to bind socket: {:?}", e); + return; + } + }; + + loop { + _ = io::server::run( + &mut Server::<_, 64>::new_with_et(ip), + &ServerOptions::new(ip, Some(&mut gw_buf)), + &mut bound_socket, + &mut buf, + ) + .await + .inspect_err(|e| warn!("DHCP server error: {e:?}")); + Timer::after(Duration::from_millis(500)).await; + } +} From bafc86681c0bfd04b09960b6696fece61104a00a Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sun, 17 May 2026 12:24:39 +0200 Subject: [PATCH 4/7] refactor: move sntp to network module --- Software/MainBoard/rust/src/hal/esp.rs | 117 ------------------------ Software/MainBoard/rust/src/main.rs | 2 +- Software/MainBoard/rust/src/network.rs | 122 ++++++++++++++++++++++++- 3 files changed, 120 insertions(+), 121 deletions(-) diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 12f20f7..43e25a3 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -13,8 +13,6 @@ use alloc::{format, string::String, vec, vec::Vec}; use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use core::sync::atomic::Ordering; use embassy_executor::Spawner; -use embassy_net::dns::DnsQueryType; -use embassy_net::udp::{PacketMetadata, UdpSocket}; use embassy_net::{DhcpConfig, IpAddress, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; @@ -40,7 +38,6 @@ use esp_radio::wifi::sta::StationConfig; use esp_radio::wifi::{AuthenticationMethod, Config, Interface, WifiController}; use log::{error, info, warn}; use portable_atomic::AtomicBool; -use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; use crate::network::{net_task, run_dhcp}; @@ -57,46 +54,6 @@ static mut RESTART_TO_CONF: i8 = 0; #[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut LAST_CORROSION_PROTECTION_CHECK_DAY: i8 = -1; -const NTP_SERVER: &str = "pool.ntp.org"; - -#[derive(Copy, Clone, Default)] -struct Timestamp { - stamp: DateTime, -} - -struct EmbassyNtpSocket<'a, 'b> { - socket: &'a UdpSocket<'b>, -} - -impl<'a, 'b> EmbassyNtpSocket<'a, 'b> { - fn new(socket: &'a UdpSocket<'b>) -> Self { - Self { socket } - } -} - -impl NtpUdpSocket for EmbassyNtpSocket<'_, '_> { - async fn send_to(&self, buf: &[u8], addr: SocketAddr) -> sntpc::Result { - self.socket - .send_to(buf, addr) - .await - .map_err(|_| sntpc::Error::Network)?; - Ok(buf.len()) - } - - async fn recv_from(&self, buf: &mut [u8]) -> sntpc::Result<(usize, SocketAddr)> { - let (len, metadata) = self - .socket - .recv_from(buf) - .await - .map_err(|_| sntpc::Error::Network)?; - let addr = match metadata.endpoint.addr { - IpAddress::Ipv4(ip) => IpAddr::V4(ip), - IpAddress::Ipv6(ip) => IpAddr::V6(ip), - }; - Ok((len, SocketAddr::new(addr, metadata.endpoint.port))) - } -} - // Minimal esp-idf equivalent for gpio_hold on esp32c6 via ROM functions extern "C" { fn gpio_pad_hold(gpio_num: u32); @@ -113,20 +70,6 @@ pub fn hold_disable(gpio_num: u8) { unsafe { gpio_pad_unhold(gpio_num as u32) } } -impl NtpTimestampGenerator for Timestamp { - fn init(&mut self) { - self.stamp = DateTime::default(); - } - - fn timestamp_sec(&self) -> u64 { - self.stamp.timestamp() as u64 - } - - fn timestamp_subsec_micros(&self) -> u32 { - self.stamp.timestamp_subsec_micros() - } -} - pub struct Esp<'a> { pub savegame: SavegameManager, pub rng: Rng, @@ -255,66 +198,6 @@ impl Esp<'_> { self.boot_button.is_low() } - pub(crate) async fn sntp( - &mut self, - _max_wait_ms: u32, - stack: Stack<'_>, - ) -> FatResult> { - println!("start sntp"); - let mut rx_meta = [PacketMetadata::EMPTY; 16]; - let mut rx_buffer = [0; 4096]; - let mut tx_meta = [PacketMetadata::EMPTY; 16]; - let mut tx_buffer = [0; 4096]; - - let mut socket = UdpSocket::new( - stack, - &mut rx_meta, - &mut rx_buffer, - &mut tx_meta, - &mut tx_buffer, - ); - socket.bind(123).context("Could not bind UDP socket")?; - - let context = NtpContext::new(Timestamp::default()); - let ntp_socket = EmbassyNtpSocket::new(&socket); - - let ntp_addrs = stack - .dns_query(NTP_SERVER, DnsQueryType::A) - .await - .context("Failed to resolve DNS")?; - - if ntp_addrs.is_empty() { - bail!("No IP addresses found for NTP server"); - } - let ntp = ntp_addrs[0]; - info!("NTP server: {ntp:?}"); - - let mut counter = 0; - loop { - let addr: IpAddr = ntp.into(); - let timeout = get_time(SocketAddr::from((addr, 123)), &ntp_socket, context) - .with_timeout(Duration::from_millis((_max_wait_ms / 10) as u64)) - .await; - - match timeout { - Ok(result) => { - let time = result?; - info!("Time: {time:?}"); - return DateTime::from_timestamp(time.seconds as i64, 0) - .context("Could not convert Sntp result"); - } - Err(err) => { - warn!("sntp timeout, retry: {err:?}"); - counter += 1; - if counter > 10 { - bail!("Failed to get time from NTP server"); - } - Timer::after(Duration::from_millis(100)).await; - } - } - } - } - pub(crate) async fn wifi_scan(&mut self) -> FatResult> { info!("start wifi scan"); let mut lock = self.controller.try_lock()?; diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 4bd67f7..5d60882 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -934,7 +934,7 @@ async fn try_connect_wifi_sntp_mqtt( Ok(stack) => { stack_store.replace(stack); - let sntp_mode: network::SntpMode = match board.board_hal.get_esp().sntp(1000 * 10, stack).await { + let sntp_mode: network::SntpMode = match network::sntp(1000 * 10, stack).await { Ok(new_time) => { info!("Using time from sntp {}", new_time.to_rfc3339()); let _ = board diff --git a/Software/MainBoard/rust/src/network.rs b/Software/MainBoard/rust/src/network.rs index a208bac..789392d 100644 --- a/Software/MainBoard/rust/src/network.rs +++ b/Software/MainBoard/rust/src/network.rs @@ -1,17 +1,133 @@ +use crate::bail; +use crate::fat_error::{ContextExt, FatError, FatResult}; use alloc::string::String; use chrono::{DateTime, Utc}; -use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; +use embassy_net::dns::DnsQueryType; +use embassy_net::udp::{PacketMetadata, UdpSocket}; use embassy_net::{Runner, Stack}; -use embassy_time::{Duration, Timer}; +use embassy_time::{Duration, Timer, WithTimeout}; use edge_dhcp::{ io::{self, DEFAULT_SERVER_PORT}, server::{Server, ServerOptions}, }; use edge_nal::UdpBind; use edge_nal_embassy::{Udp, UdpBuffers}; +use esp_println::println; use esp_radio::wifi::Interface; -use log::{warn, error}; +use log::{info, warn, error}; use serde::Serialize; +use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; + +const NTP_SERVER: &str = "pool.ntp.org"; + +#[derive(Copy, Clone, Default)] +struct Timestamp { + stamp: DateTime, +} + +impl NtpTimestampGenerator for Timestamp { + fn init(&mut self) { + self.stamp = DateTime::default(); + } + + fn timestamp_sec(&self) -> u64 { + self.stamp.timestamp() as u64 + } + + fn timestamp_subsec_micros(&self) -> u32 { + self.stamp.timestamp_subsec_micros() + } +} + +struct EmbassyNtpSocket<'a, 'b> { + socket: &'a UdpSocket<'b>, +} + +impl<'a, 'b> EmbassyNtpSocket<'a, 'b> { + fn new(socket: &'a UdpSocket<'b>) -> Self { + Self { socket } + } +} + +impl NtpUdpSocket for EmbassyNtpSocket<'_, '_> { + async fn send_to(&self, buf: &[u8], addr: SocketAddr) -> sntpc::Result { + self.socket + .send_to(buf, addr) + .await + .map_err(|_| sntpc::Error::Network)?; + Ok(buf.len()) + } + + async fn recv_from(&self, buf: &mut [u8]) -> sntpc::Result<(usize, SocketAddr)> { + let (len, metadata) = self + .socket + .recv_from(buf) + .await + .map_err(|_| sntpc::Error::Network)?; + let addr = match metadata.endpoint.addr { + embassy_net::IpAddress::Ipv4(ip) => IpAddr::V4(ip), + embassy_net::IpAddress::Ipv6(ip) => IpAddr::V6(ip), + }; + Ok((len, SocketAddr::new(addr, metadata.endpoint.port))) + } +} + +pub async fn sntp(max_wait_ms: u32, stack: Stack<'_>) -> FatResult> { + println!("start sntp"); + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut rx_buffer = [0; 4096]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_buffer = [0; 4096]; + + let mut socket = UdpSocket::new( + stack, + &mut rx_meta, + &mut rx_buffer, + &mut tx_meta, + &mut tx_buffer, + ); + socket.bind(123).context("Could not bind UDP socket")?; + + let context = NtpContext::new(Timestamp::default()); + let ntp_socket = EmbassyNtpSocket::new(&socket); + + let ntp_addrs = stack + .dns_query(NTP_SERVER, DnsQueryType::A) + .await + .context("Failed to resolve DNS")?; + + if ntp_addrs.is_empty() { + bail!("No IP addresses found for NTP server"); + } + let ntp = ntp_addrs[0]; + info!("NTP server: {ntp:?}"); + + let mut counter = 0; + loop { + let addr: IpAddr = ntp.into(); + let timeout = get_time(SocketAddr::from((addr, 123)), &ntp_socket, context) + .with_timeout(Duration::from_millis((max_wait_ms / 10) as u64)) + .await; + + match timeout { + Ok(result) => { + let time = result?; + info!("Time: {time:?}"); + return DateTime::from_timestamp(time.seconds as i64, 0) + .context("Could not convert Sntp result"); + } + Err(err) => { + warn!("sntp timeout, retry: {err:?}"); + counter += 1; + if counter > 10 { + bail!("Failed to get time from NTP server"); + } + Timer::after(Duration::from_millis(100)).await; + } + } + } +} #[derive(Serialize, Debug, PartialEq)] pub enum SntpMode { From 9d57805502f58f67d81e9b11d3bff04323b4ff1f Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sun, 17 May 2026 12:27:54 +0200 Subject: [PATCH 5/7] refactor: move wifi_ap to network module --- Software/MainBoard/rust/src/hal/esp.rs | 56 -------------------- Software/MainBoard/rust/src/main.rs | 21 ++++++-- Software/MainBoard/rust/src/network.rs | 71 +++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 61 deletions(-) diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 43e25a3..012d8de 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -271,62 +271,6 @@ impl Esp<'_> { } } - pub(crate) async fn wifi_ap(&mut self, spawner: Spawner) -> FatResult> { - let ssid = match self.load_config().await { - Ok(config) => config.network.ap_ssid.as_str().to_string(), - Err(_) => "PlantCtrl Emergency Mode".to_string(), - }; - - let device = self - .interface_ap - .take() - .context("AP interface already taken")?; - let gw_ip_addr = Ipv4Addr::new(192, 168, 71, 1); - - let config = embassy_net::Config::ipv4_static(StaticConfigV4 { - address: Ipv4Cidr::new(gw_ip_addr, 24), - gateway: Some(gw_ip_addr), - dns_servers: Default::default(), - }); - - let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64; - - println!("init secondary stack"); - // Init network stack - let (stack, runner) = embassy_net::new( - device, - config, - mk_static!(StackResources<4>, StackResources::<4>::new()), - seed, - ); - let stack = mk_static!(Stack, stack); - - let client_config = - Config::AccessPoint(AccessPointConfig::default().with_ssid(ssid.clone())); - self.controller.lock().await.set_config(&client_config)?; - - println!("start net task"); - spawner.spawn(net_task(runner)?); - println!("run dhcp"); - spawner.spawn(run_dhcp(*stack, gw_ip_addr)?); - - loop { - if stack.is_link_up() { - break; - } - Timer::after(Duration::from_millis(500)).await; - } - while !stack.is_config_up() { - Timer::after(Duration::from_millis(100)).await - } - println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr}/"); - stack - .config_v4() - .inspect(|c| println!("ipv4 config: {c:?}")); - - Ok(*stack) - } - pub(crate) async fn wifi( &mut self, network_config: &NetworkConfig, diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 5d60882..76f3fba 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -16,7 +16,7 @@ use esp_backtrace as _; use crate::hal::PROGRESS_ACTIVE; use crate::config::{NetworkConfig, PlantConfig, PlantControllerConfig}; -use crate::fat_error::FatResult; +use crate::fat_error::{ContextExt, FatResult}; use crate::log::log; use crate::tank::{determine_tank_state, TankError, TankState, WATER_FROZEN_THRESH}; @@ -206,7 +206,12 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { { info!("No wifi configured, starting initial config mode"); - let stack = board.board_hal.get_esp().wifi_ap(spawner).await?; + let esp = board.board_hal.get_esp(); + let ssid = esp.load_config().await + .map(|config| config.network.ap_ssid.to_string()) + .unwrap_or_else(|_| String::from("PlantCtrl Emergency Mode")); + let device = esp.interface_ap.take().context("AP interface already taken")?; + let stack = network::wifi_ap(ssid, device, &esp.controller, &mut esp.rng, spawner).await?; let reboot_now = Arc::new(AtomicBool::new(false)); println!("starting webserver"); @@ -230,7 +235,17 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { let res = { let esp = board.board_hal.get_esp(); - esp.wifi_ap(spawner).await + let ssid = esp.load_config().await + .map(|config| config.network.ap_ssid.to_string()) + .unwrap_or_else(|_| String::from("PlantCtrl Emergency Mode")); + let device = match esp.interface_ap.take() { + Some(d) => d, + None => { + use crate::fat_error::FatError; + return Err(FatError::String { error: "AP interface already taken".to_string() }); + } + }; + network::wifi_ap(ssid, device, &esp.controller, &mut esp.rng, spawner).await }; match res { Ok(ap_stack) => { diff --git a/Software/MainBoard/rust/src/network.rs b/Software/MainBoard/rust/src/network.rs index 789392d..4b1311a 100644 --- a/Software/MainBoard/rust/src/network.rs +++ b/Software/MainBoard/rust/src/network.rs @@ -1,11 +1,15 @@ use crate::bail; use crate::fat_error::{ContextExt, FatError, FatResult}; use alloc::string::String; +use alloc::sync::Arc; use chrono::{DateTime, Utc}; use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; +use embassy_executor::Spawner; use embassy_net::dns::DnsQueryType; use embassy_net::udp::{PacketMetadata, UdpSocket}; -use embassy_net::{Runner, Stack}; +use embassy_net::{Runner, Stack, StackResources, StaticConfigV4}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer, WithTimeout}; use edge_dhcp::{ io::{self, DEFAULT_SERVER_PORT}, @@ -13,8 +17,10 @@ use edge_dhcp::{ }; use edge_nal::UdpBind; use edge_nal_embassy::{Udp, UdpBuffers}; +use esp_hal::rng::Rng; use esp_println::println; -use esp_radio::wifi::Interface; +use esp_radio::wifi::ap::AccessPointConfig; +use esp_radio::wifi::{Config, Interface}; use log::{info, warn, error}; use serde::Serialize; use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; @@ -184,3 +190,64 @@ pub(crate) async fn run_dhcp(stack: Stack<'static>, ip: Ipv4Addr) { Timer::after(Duration::from_millis(500)).await; } } + +macro_rules! mk_static { + ($t:ty,$val:expr) => {{ + static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); + #[deny(unused_attributes)] + let x = STATIC_CELL.uninit().write(($val)); + x + }}; +} + +pub async fn wifi_ap( + ssid: String, + interface_ap: Interface<'static>, + controller: &Arc>>, + rng: &mut Rng, + spawner: Spawner, +) -> FatResult> { + let gw_ip_addr = Ipv4Addr::new(192, 168, 71, 1); + + let config = embassy_net::Config::ipv4_static(StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new(gw_ip_addr, 24), + gateway: Some(gw_ip_addr), + dns_servers: Default::default(), + }); + + let seed = (rng.random() as u64) << 32 | rng.random() as u64; + + println!("init secondary stack"); + let (stack, runner) = embassy_net::new( + interface_ap, + config, + mk_static!(StackResources<4>, StackResources::<4>::new()), + seed, + ); + let stack = mk_static!(Stack, stack); + + let client_config = + Config::AccessPoint(AccessPointConfig::default().with_ssid(ssid.clone())); + controller.lock().await.set_config(&client_config)?; + + println!("start net task"); + spawner.spawn(net_task(runner)?); + println!("run dhcp"); + spawner.spawn(run_dhcp(*stack, gw_ip_addr)?); + + loop { + if stack.is_link_up() { + break; + } + Timer::after(Duration::from_millis(500)).await; + } + while !stack.is_config_up() { + Timer::after(Duration::from_millis(100)).await + } + println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr}/"); + stack + .config_v4() + .inspect(|c| println!("ipv4 config: {c:?}")); + + Ok(*stack) +} From ac200af7a91e1fbc957ecc150d1ea5ee8d258c95 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sun, 17 May 2026 12:29:57 +0200 Subject: [PATCH 6/7] refactor: move wifi to network module --- Software/MainBoard/rust/src/hal/esp.rs | 94 ---------------------- Software/MainBoard/rust/src/main.rs | 11 ++- Software/MainBoard/rust/src/network.rs | 103 ++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 98 deletions(-) diff --git a/Software/MainBoard/rust/src/hal/esp.rs b/Software/MainBoard/rust/src/hal/esp.rs index 012d8de..2e6c35f 100644 --- a/Software/MainBoard/rust/src/hal/esp.rs +++ b/Software/MainBoard/rust/src/hal/esp.rs @@ -271,100 +271,6 @@ impl Esp<'_> { } } - pub(crate) async fn wifi( - &mut self, - network_config: &NetworkConfig, - spawner: Spawner, - ) -> FatResult> { - esp_radio::wifi_set_log_verbose(); - let ssid = match &network_config.ssid { - Some(ssid) => { - if ssid.is_empty() { - bail!("Wifi ssid was empty") - } - ssid.to_string() - } - None => { - bail!("Wifi ssid was empty") - } - }; - info!("attempting to connect wifi {ssid}"); - let password = match network_config.password { - Some(ref password) => password.to_string(), - None => "".to_string(), - }; - let max_wait = network_config.max_wait; - - let device = self - .interface_sta - .take() - .context("STA interface already taken")?; - let config = embassy_net::Config::dhcpv4(DhcpConfig::default()); - - let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64; - - // Init network stack - let (stack, runner) = embassy_net::new( - device, - config, - mk_static!(StackResources<8>, StackResources::<8>::new()), - seed, - ); - let stack = mk_static!(Stack, stack); - - let auth_method = if password.is_empty() { - AuthenticationMethod::None - } else { - AuthenticationMethod::Wpa2Personal - }; - let client_config = StationConfig::default() - .with_ssid(ssid) - .with_auth_method(auth_method) - .with_password(password); - - self.controller - .lock() - .await - .set_config(&Config::Station(client_config))?; - spawner.spawn(net_task(runner)?); - self.controller - .lock() - .await - .connect_async() - .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) - .await - .context("Timeout waiting for wifi sta connected")??; - - let res = async { - while !stack.is_link_up() { - Timer::after(Duration::from_millis(500)).await; - } - Ok::<(), FatError>(()) - } - .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) - .await; - - if res.is_err() { - bail!("Timeout waiting for wifi link up") - } - - let res = async { - while !stack.is_config_up() { - Timer::after(Duration::from_millis(100)).await - } - Ok::<(), FatError>(()) - } - .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) - .await; - - if res.is_err() { - bail!("Timeout waiting for wifi config up") - } - - info!("Connected WIFI, dhcp: {:?}", stack.config_v4()); - Ok(*stack) - } - pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { // Mark the current OTA image as valid if we reached here while in pending verify. if let Ok(cur) = self.ota.current_ota_state() { diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 76f3fba..3114040 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -945,7 +945,16 @@ async fn try_connect_wifi_sntp_mqtt( spawner: Spawner, ) -> network::NetworkMode { let nw_conf = &board.board_hal.get_config().network.clone(); - match board.board_hal.get_esp().wifi(nw_conf, spawner).await { + let esp = board.board_hal.get_esp(); + let device = match esp.interface_sta.take() { + Some(d) => d, + None => { + info!("Offline mode due to STA interface already taken"); + board.board_hal.general_fault(true).await; + return network::NetworkMode::OFFLINE; + } + }; + match network::wifi(nw_conf, device, &esp.controller, &mut esp.rng, spawner).await { Ok(stack) => { stack_store.replace(stack); diff --git a/Software/MainBoard/rust/src/network.rs b/Software/MainBoard/rust/src/network.rs index 4b1311a..848eae8 100644 --- a/Software/MainBoard/rust/src/network.rs +++ b/Software/MainBoard/rust/src/network.rs @@ -1,13 +1,14 @@ use crate::bail; +use crate::config::NetworkConfig; use crate::fat_error::{ContextExt, FatError, FatResult}; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::sync::Arc; use chrono::{DateTime, Utc}; use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use embassy_executor::Spawner; use embassy_net::dns::DnsQueryType; use embassy_net::udp::{PacketMetadata, UdpSocket}; -use embassy_net::{Runner, Stack, StackResources, StaticConfigV4}; +use embassy_net::{DhcpConfig, Runner, Stack, StackResources, StaticConfigV4}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer, WithTimeout}; @@ -20,7 +21,8 @@ use edge_nal_embassy::{Udp, UdpBuffers}; use esp_hal::rng::Rng; use esp_println::println; use esp_radio::wifi::ap::AccessPointConfig; -use esp_radio::wifi::{Config, Interface}; +use esp_radio::wifi::sta::StationConfig; +use esp_radio::wifi::{AuthenticationMethod, Config, Interface}; use log::{info, warn, error}; use serde::Serialize; use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; @@ -251,3 +253,98 @@ pub async fn wifi_ap( Ok(*stack) } + +pub async fn wifi( + network_config: &NetworkConfig, + interface_sta: Interface<'static>, + controller: &Arc>>, + rng: &mut Rng, + spawner: Spawner, +) -> FatResult> { + esp_radio::wifi_set_log_verbose(); + let ssid = match &network_config.ssid { + Some(ssid) => { + if ssid.is_empty() { + bail!("Wifi ssid was empty") + } + ssid.as_str().to_string() + } + None => { + bail!("Wifi ssid was empty") + } + }; + info!("attempting to connect wifi {ssid}"); + let password = match network_config.password { + Some(ref password) => password.as_str().to_string(), + None => "".to_string(), + }; + let max_wait = network_config.max_wait; + + let config = embassy_net::Config::dhcpv4(DhcpConfig::default()); + + let seed = (rng.random() as u64) << 32 | rng.random() as u64; + + let (stack, runner) = embassy_net::new( + interface_sta, + config, + mk_static!(StackResources<8>, StackResources::<8>::new()), + seed, + ); + let stack = mk_static!(Stack, stack); + + let auth_method = if password.is_empty() { + AuthenticationMethod::None + } else { + AuthenticationMethod::Wpa2Personal + }; + let client_config = StationConfig::default() + .with_ssid(ssid) + .with_auth_method(auth_method) + .with_scan_method(esp_radio::wifi::sta::ScanMethod::AllChannels) + .with_listen_interval(10) + .with_beacon_timeout(10) + .with_failure_retry_cnt(3) + .with_password(password); + + controller + .lock() + .await + .set_config(&Config::Station(client_config))?; + spawner.spawn(net_task(runner)?); + controller + .lock() + .await + .connect_async() + .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) + .await + .context("Timeout waiting for wifi sta connected")??; + + let res = async { + while !stack.is_link_up() { + Timer::after(Duration::from_millis(500)).await; + } + Ok::<(), FatError>(()) + } + .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) + .await; + + if res.is_err() { + bail!("Timeout waiting for wifi link up") + } + + let res = async { + while !stack.is_config_up() { + Timer::after(Duration::from_millis(100)).await + } + Ok::<(), FatError>(()) + } + .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) + .await; + + if res.is_err() { + bail!("Timeout waiting for wifi config up") + } + + info!("Connected WIFI, dhcp: {:?}", stack.config_v4()); + Ok(*stack) +} From 40f99870cfa595071e235b08107522da13fd856d Mon Sep 17 00:00:00 2001 From: ju6ge Date: Sun, 17 May 2026 12:30:43 +0200 Subject: [PATCH 7/7] refactor: move try_connect_wifi_sntp_mqtt to network module --- Software/MainBoard/rust/src/main.rs | 84 +------------------------- Software/MainBoard/rust/src/network.rs | 79 +++++++++++++++++++++++- 2 files changed, 79 insertions(+), 84 deletions(-) diff --git a/Software/MainBoard/rust/src/main.rs b/Software/MainBoard/rust/src/main.rs index 3114040..6bde80d 100644 --- a/Software/MainBoard/rust/src/main.rs +++ b/Software/MainBoard/rust/src/main.rs @@ -222,7 +222,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { let mut stack: OptionLock = OptionLock::empty(); let network_mode = if board.board_hal.get_config().network.ssid.is_some() { - try_connect_wifi_sntp_mqtt(&mut board, &mut stack, spawner).await + network::try_connect_wifi_sntp_mqtt(&mut board, &mut stack, spawner).await } else { info!("No wifi configured"); //the current sensors require this amount to stabilize, in the case of Wi-Fi this is already handled due to connect timings; @@ -931,88 +931,6 @@ async fn publish_firmware_info( .await; mqtt::publish("/state", "online").await; } -macro_rules! mk_static { - ($t:ty,$val:expr) => {{ - static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); - #[deny(unused_attributes)] - let x = STATIC_CELL.uninit().write($val); - x - }}; -} -async fn try_connect_wifi_sntp_mqtt( - board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>, - stack_store: &mut OptionLock>, - spawner: Spawner, -) -> network::NetworkMode { - let nw_conf = &board.board_hal.get_config().network.clone(); - let esp = board.board_hal.get_esp(); - let device = match esp.interface_sta.take() { - Some(d) => d, - None => { - info!("Offline mode due to STA interface already taken"); - board.board_hal.general_fault(true).await; - return network::NetworkMode::OFFLINE; - } - }; - match network::wifi(nw_conf, device, &esp.controller, &mut esp.rng, spawner).await { - Ok(stack) => { - stack_store.replace(stack); - - let sntp_mode: network::SntpMode = match network::sntp(1000 * 10, stack).await { - Ok(new_time) => { - info!("Using time from sntp {}", new_time.to_rfc3339()); - let _ = board - .board_hal - .get_rtc_module() - .set_rtc_time(&new_time) - .await; - network::SntpMode::SYNC { current: new_time } - } - Err(err) => { - warn!("sntp error: {err}"); - board.board_hal.general_fault(true).await; - network::SntpMode::OFFLINE - } - }; - - let mqtt_connected = if board.board_hal.get_config().network.mqtt_url.is_some() { - let nw_config = board.board_hal.get_config().network.clone(); - let nw_config = mk_static!(NetworkConfig, nw_config); - match mqtt::mqtt_init(nw_config, stack, spawner).await { - Ok(_) => { - info!("Mqtt connection ready"); - true - } - Err(err) => { - warn!("Could not connect mqtt due to {err}"); - false - } - } - } else { - false - }; - - let ip = match stack.config_v4() { - Some(config) => config.address.address().to_string(), - None => match stack.config_v6() { - Some(config) => config.address.address().to_string(), - None => String::from("No IP"), - }, - }; - network::NetworkMode::WIFI { - sntp: sntp_mode, - mqtt: mqtt_connected, - ip_address: ip, - } - } - Err(err) => { - info!("Offline mode due to {err}"); - board.board_hal.general_fault(true).await; - network::NetworkMode::OFFLINE - } - } -} - async fn pump_info( board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, plant_id: usize, diff --git a/Software/MainBoard/rust/src/network.rs b/Software/MainBoard/rust/src/network.rs index 848eae8..dfe545e 100644 --- a/Software/MainBoard/rust/src/network.rs +++ b/Software/MainBoard/rust/src/network.rs @@ -1,6 +1,8 @@ use crate::bail; use crate::config::NetworkConfig; use crate::fat_error::{ContextExt, FatError, FatResult}; +use crate::hal::{PlantHal, HAL}; +use crate::mqtt; use alloc::string::{String, ToString}; use alloc::sync::Arc; use chrono::{DateTime, Utc}; @@ -10,8 +12,9 @@ use embassy_net::dns::DnsQueryType; use embassy_net::udp::{PacketMetadata, UdpSocket}; use embassy_net::{DhcpConfig, Runner, Stack, StackResources, StaticConfigV4}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::mutex::Mutex; +use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_time::{Duration, Timer, WithTimeout}; +use option_lock::OptionLock; use edge_dhcp::{ io::{self, DEFAULT_SERVER_PORT}, server::{Server, ServerOptions}, @@ -348,3 +351,77 @@ pub async fn wifi( info!("Connected WIFI, dhcp: {:?}", stack.config_v4()); Ok(*stack) } + +pub async fn try_connect_wifi_sntp_mqtt( + board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>, + stack_store: &mut OptionLock>, + spawner: Spawner, +) -> NetworkMode { + let nw_conf = &board.board_hal.get_config().network.clone(); + let esp = board.board_hal.get_esp(); + let device = match esp.interface_sta.take() { + Some(d) => d, + None => { + info!("Offline mode due to STA interface already taken"); + board.board_hal.general_fault(true).await; + return NetworkMode::OFFLINE; + } + }; + match wifi(nw_conf, device, &esp.controller, &mut esp.rng, spawner).await { + Ok(stack) => { + stack_store.replace(stack); + + let sntp_mode: SntpMode = match sntp(1000 * 10, stack).await { + Ok(new_time) => { + info!("Using time from sntp {}", new_time.to_rfc3339()); + let _ = board + .board_hal + .get_rtc_module() + .set_rtc_time(&new_time) + .await; + SntpMode::SYNC { current: new_time } + } + Err(err) => { + warn!("sntp error: {err}"); + board.board_hal.general_fault(true).await; + SntpMode::OFFLINE + } + }; + + let mqtt_connected = if board.board_hal.get_config().network.mqtt_url.is_some() { + let nw_config = board.board_hal.get_config().network.clone(); + let nw_config = mk_static!(NetworkConfig, nw_config); + match mqtt::mqtt_init(nw_config, stack, spawner).await { + Ok(_) => { + info!("Mqtt connection ready"); + true + } + Err(err) => { + warn!("Could not connect mqtt due to {err}"); + false + } + } + } else { + false + }; + + let ip = match stack.config_v4() { + Some(config) => config.address.address().to_string(), + None => match stack.config_v6() { + Some(config) => config.address.address().to_string(), + None => String::from("No IP"), + }, + }; + NetworkMode::WIFI { + sntp: sntp_mode, + mqtt: mqtt_connected, + ip_address: ip, + } + } + Err(err) => { + info!("Offline mode due to {err}"); + board.board_hal.general_fault(true).await; + NetworkMode::OFFLINE + } + } +}