diff --git a/Cargo.lock b/Cargo.lock index 5c163c0..2961a0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,7 +187,9 @@ dependencies = [ "critical-section", "defmt 1.1.1", "embassy-executor", + "embassy-futures", "embassy-net", + "embassy-sync 0.8.0", "embassy-time", "esp-alloc", "esp-backtrace", diff --git a/Cargo.toml b/Cargo.toml index f25f7b0..11044bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ esp-backtrace = { version = "0.19.0", features = [ esp-println = { version = "0.17.0", features = ["defmt-espflash", "esp32c6"] } critical-section = "1.2.0" -static_cell = "2.1.1" +static_cell = { version = "2.1.1", features = ["nightly"] } embassy-net = { version = "0.9.1", features = [ "defmt", @@ -56,6 +56,8 @@ sntpc-time-embassy = { version = "0.6" } portable-atomic = "1.15.0" picoserve = { version = "0.19", features = ["defmt", "embassy", "json"] } +embassy-sync = { version = "0.8.0", features = ["defmt"] } +embassy-futures = { version = "0.1.2", features = ["defmt"] } # For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html diff --git a/src/bin/main.rs b/src/bin/main.rs index 9e721d0..3132d52 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -6,20 +6,20 @@ holding buffers for the duration of a data transfer." )] #![deny(clippy::large_stack_frames)] -#![feature(impl_trait_in_assoc_type)] -use defmt::{error, info}; +use cover_theif::wifi::start_wifi; +use defmt::info; use embassy_executor::Spawner; -use embassy_net::Runner; use embassy_time::{Duration, Timer}; use esp_backtrace as _; use esp_hal::clock::CpuClock; use esp_hal::timer::timg::TimerGroup; use esp_println as _; -use esp_radio::wifi::sta::StationConfig; -use esp_radio::wifi::{ControllerConfig, Interface, WifiController}; -use cover_theif::{http_api::{self, HttpApiProps, HttpApiService}, clock}; +use cover_theif::{ + clock, + http_api::{HttpApiProps, HttpApiService}, +}; extern crate alloc; @@ -27,19 +27,6 @@ extern crate alloc; // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); -// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html -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 - }}; -} - -const SSID: &str = env!("SSID"); -const PASSWORD: &str = env!("PASSWORD"); - #[allow( clippy::large_stack_frames, reason = "it's not unusual to allocate larger buffers etc. in main" @@ -61,47 +48,18 @@ async fn main(spawner: Spawner) -> ! { info!("Embassy initialized!"); - let wifi_config = esp_radio::wifi::Config::Station( - StationConfig::default() - .with_ssid(SSID) - .with_password(PASSWORD.into()), - ); - - let (wifi_controller, wifi_interfaces) = esp_radio::wifi::new( - peripherals.WIFI, - ControllerConfig::default().with_initial_config(wifi_config), - ) - .expect("Failed to initialize Wi-Fi controller"); - - let mut dhcp_config = embassy_net::DhcpConfig::default(); - dhcp_config.hostname = Some("cover-theif".try_into().unwrap()); - let net_config = embassy_net::Config::dhcpv4(dhcp_config); - - let rng = esp_hal::rng::Rng::new(); - let seed = (rng.random() as u64) << 32 | rng.random() as u64; - - let (net_stack, net_runner) = embassy_net::new( - wifi_interfaces.station, - net_config, - mk_static!( - embassy_net::StackResources<3>, - embassy_net::StackResources::<3>::new() - ), - seed, - ); - - spawner.spawn(connection(wifi_controller).unwrap()); - spawner.spawn(net_task(net_runner).unwrap()); - - net_stack.wait_config_up().await; - - if let Some(config) = net_stack.config_v4() { - info!("Got IP: {}", config.address); - } + let net_stack = start_wifi(spawner, peripherals.WIFI); spawner.spawn(clock::ntp_upkeep_service(net_stack).unwrap()); - spawner.spawn(http_api_serve_task(HttpApiService::new(net_stack, Default::default(), Default::default())).unwrap()); + spawner.spawn( + http_api_serve_task(HttpApiService::new( + net_stack, + Default::default(), + Default::default(), + )) + .unwrap(), + ); loop { info!("Hello world"); @@ -111,35 +69,6 @@ async fn main(spawner: Spawner) -> ! { // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.1.0/examples } -#[embassy_executor::task] -async fn connection(mut controller: WifiController<'static>) { - info!("start connection task"); - - loop { - info!("About to connect..."); - - match controller.connect_async().await { - Ok(info) => { - info!("Wifi connected to {:?}", info); - - // wait until we're no longer connected - let info = controller.wait_for_disconnect_async().await.ok(); - info!("Disconnected: {:?}", info); - } - Err(e) => { - error!("Failed to connect to wifi: {:?}", e); - } - } - - Timer::after(Duration::from_millis(5000)).await - } -} - -#[embassy_executor::task] -async fn net_task(mut runner: Runner<'static, Interface<'static>>) { - runner.run().await -} - #[embassy_executor::task] async fn http_api_serve_task(mut service: HttpApiService<'static, HttpApiProps>) { service.serve().await; diff --git a/src/lib.rs b/src/lib.rs index 34783b7..ccf4964 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,21 @@ )] #![deny(clippy::large_stack_frames)] #![feature(impl_trait_in_assoc_type)] +#![feature(type_alias_impl_trait)] pub mod http_api; pub mod clock; + +pub mod wifi; +// +// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html +#[macro_export] +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 + }}; +} diff --git a/src/wifi.rs b/src/wifi.rs new file mode 100644 index 0000000..404ceb2 --- /dev/null +++ b/src/wifi.rs @@ -0,0 +1,125 @@ +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_futures::select::Either::First; +use embassy_futures::select::select; +use embassy_net::{Runner, Stack}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::{Channel, Sender}; +use embassy_time::{Duration, Timer}; +use esp_hal::peripherals::WIFI; +use esp_radio::wifi::sta::StationConfig; +use esp_radio::wifi::{ControllerConfig, Interface, WifiController, WifiError}; + +pub enum WifiCommand<'a> { + SetStationConfig( + esp_radio::wifi::Config, + Sender<'a, CriticalSectionRawMutex, Result<(), WifiError>, 1>, + ), + SetDhcpConfig(embassy_net::DhcpConfig), +} + +pub static WIFI_CH: Channel = Channel::new(); + +pub fn start_wifi(spawner: Spawner, wifi: WIFI<'static>) -> Stack<'static> { + let wifi_config = esp_radio::wifi::Config::Station(StationConfig::default()); + + let (wifi_controller, wifi_interfaces) = esp_radio::wifi::new( + wifi, + ControllerConfig::default().with_initial_config(wifi_config), + ) + .expect("Failed to initialize Wi-Fi controller"); + + let mut dhcp_config = embassy_net::DhcpConfig::default(); + dhcp_config.hostname = Some("cover-theif".try_into().unwrap()); + let net_config = embassy_net::Config::dhcpv4(dhcp_config); + + let rng = esp_hal::rng::Rng::new(); + let seed = (rng.random() as u64) << 32 | rng.random() as u64; + + let (net_stack, net_runner) = embassy_net::new( + wifi_interfaces.station, + net_config, + crate::mk_static!( + embassy_net::StackResources<3>, + embassy_net::StackResources::<3>::new() + ), + seed, + ); + + spawner.spawn(net_task(net_runner).unwrap()); + spawner.spawn(handler(wifi_controller, net_stack).unwrap()); + + net_stack +} + +#[embassy_executor::task] +async fn handler(mut controller: WifiController<'static>, net_stack: Stack<'static>) { + let receiver = WIFI_CH.receiver(); + loop { + match select(receiver.receive(), connection(&mut controller, net_stack)).await { + First(WifiCommand::SetStationConfig(config, res)) => { + res.send(controller.set_config(&config)).await; + } + First(WifiCommand::SetDhcpConfig(dhcp_config)) => { + net_stack.set_config_v4(embassy_net::ConfigV4::Dhcp(dhcp_config)); + } + embassy_futures::select::Either::Second(_) => { + unreachable!("connection function should be infinite loop") + } + } + } +} + +async fn connection(controller: &mut WifiController<'static>, net_stack: Stack<'static>) { + info!("start connection task"); + + select( + async { + loop { + info!("Connecting to WiFi..."); + + match controller.connect_async().await { + Ok(info) => { + info!("Wifi connected to {:?}", info); + + // wait until we're no longer connected + let info = controller.wait_for_disconnect_async().await.ok(); + info!("Disconnected: {:?}", info); + } + Err(e) => { + error!("Failed to connect to wifi: {:?}", e); + } + } + + Timer::after(Duration::from_millis(5000)).await + } + }, + async { + loop { + info!("Getting IP address..."); + net_stack.wait_config_up().await; + + match net_stack.config_v4() { + Some(info) => { + info!("Got IP: {:?}", info); + + // wait until we're no longer connected + net_stack.wait_config_down().await; + info!("Address no longer configured"); + } + None => { + error!("Failed to configure an address"); + } + } + + Timer::after(Duration::from_millis(5000)).await + } + }, + ) + .await; +} + +#[embassy_executor::task] +async fn net_task(mut runner: Runner<'static, Interface<'static>>) { + runner.run().await +}