From 516608916e71671e99c234511df93801fde2d3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 6 Nov 2023 10:30:08 +0100 Subject: [PATCH] AP-STA mode (#299) * Configure AP-STA mode * Split up link state waking into two branches * Add WifiMode::ApSta * Simplify mode setup * Deduce wifi mode from config if available * Rework internals to support AP-STA mode * Add `new_ap_sta` constructor * Demote Rx token warning * Allow using is_sta_enabled and is_ap_enabled * Disallow certain config changes * Update readme * Return capability based on configuration * Add a default-config constructor for AP-STA * Shorten unsafe block * Typestate devices * Add examples --- .github/workflows/ci.yml | 6 + README.md | 1 - docs/examples.md | 22 + esp-wifi/examples/access_point.rs | 4 +- esp-wifi/examples/access_point_with_sta.rs | 224 +++++ esp-wifi/examples/bench.rs | 10 +- esp-wifi/examples/coex.rs | 4 +- esp-wifi/examples/dhcp.rs | 4 +- esp-wifi/examples/embassy_access_point.rs | 10 +- .../examples/embassy_access_point_with_sta.rs | 302 +++++++ esp-wifi/examples/embassy_bench.rs | 6 +- esp-wifi/examples/embassy_dhcp.rs | 6 +- esp-wifi/examples/static_ip.rs | 4 +- esp-wifi/src/wifi/mod.rs | 794 ++++++++++++------ esp-wifi/src/wifi/os_adapter.rs | 18 +- esp-wifi/src/wifi/utils.rs | 88 +- esp-wifi/src/wifi_interface.rs | 51 +- 17 files changed, 1200 insertions(+), 354 deletions(-) create mode 100644 esp-wifi/examples/access_point_with_sta.rs create mode 100644 esp-wifi/examples/embassy_access_point_with_sta.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c41ad98..fd398792 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,6 +63,10 @@ jobs: run: cd esp-wifi && cargo b${{ matrix.chip }} --features=async,wifi,esp-now,embassy-net,log,${{ matrix.chip }}-hal/embassy-time-timg0 - name: build (common features + defmt) run: cd esp-wifi && cargo b${{ matrix.chip }} --no-default-features --features=async,wifi,esp-now,embassy-net,defmt,${{ matrix.chip }}-hal/embassy-time-timg0 + - name: build (access_point) + run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=access_point --features=wifi + - name: build (access_point_with_sta) + run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=access_point_with_sta --features=wifi - name: build (dhcp) run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=dhcp --features=wifi - name: build (bench) @@ -81,6 +85,8 @@ jobs: run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=embassy_bench --features=async,wifi,embassy-net,${{ matrix.chip }}-hal/embassy-time-timg0 - name: build (embassy_access_point) run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=embassy_access_point --features=async,wifi,embassy-net,${{ matrix.chip }}-hal/embassy-time-timg0 + - name: build (embassy_access_point_with_sta) + run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=embassy_access_point_with_sta --features=async,wifi,embassy-net,${{ matrix.chip }}-hal/embassy-time-timg0 - name: build (common features + ble) if: ${{ matrix.chip != 'esp32s2' }} diff --git a/README.md b/README.md index 892a5120..f51baab0 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,6 @@ See [Examples] for details. ## Missing / To be done - Make CoEx work on ESP32 (it kind of works when commenting out setting the country in wifi_start, probably some mis-compilation since it then crashes in a totally different code path) -- Combined SoftAP/STA mode - Support for non-open SoftAP - Direct-boot mode isn't supported diff --git a/docs/examples.md b/docs/examples.md index 6f5c0c2d..5b934cc8 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -91,6 +91,17 @@ cargo esp32c3 --release ... `cargo $CHIP --example access_point --release --features "embedded-svc,wifi"` +### access_point_with_sta + +- set SSID and PASSWORD env variable +- gets an ip address via DHCP +- creates an open access-point with SSID `esp-wifi` +- you can connect to it using a static IP in range 192.168.2.2 .. 192.168.2.255, gateway 192.168.2.1 +- open http://192.168.2.1:8080/ in your browser - the example will perform an HTTP get request to some "random" server +- on Android you might need to choose _Keep Accesspoint_ when it tells you the WiFi has no internet connection, Chrome might not want to load the URL - you can use a shell and try `curl` and `ping` + +`cargo $CHIP --example access_point_with_sta --release --features "embedded-svc,wifi"` + ### embassy_access_point - creates an open access-point with SSID `esp-wifi` @@ -100,6 +111,17 @@ cargo esp32c3 --release ... `cargo $CHIP --example embassy_access_point --release --features "async,embedded-svc,wifi,embassy-net"` +### embassy_access_point_with_sta + +- set SSID and PASSWORD env variable +- gets an ip address via DHCP +- creates an open access-point with SSID `esp-wifi` +- you can connect to it using a static IP in range 192.168.2.2 .. 192.168.2.255, gateway 192.168.2.1 +- open http://192.168.2.1:8080/ in your browser - the example will perform an HTTP get request to some "random" server +- on Android you might need to choose _Keep Accesspoint_ when it tells you the WiFi has no internet connection, Chrome might not want to load the URL - you can use a shell and try `curl` and `ping` + +`cargo $CHIP --example embassy_access_point_with_sta --release --features "async,embedded-svc,wifi,embassy-net"` + ## Benchmarking A prerequisite to running the benchmark examples is to run the benchmark server on your local machine. Simply run the following commands to do so. diff --git a/esp-wifi/examples/access_point.rs b/esp-wifi/examples/access_point.rs index c555f5ae..0a25b2d4 100644 --- a/esp-wifi/examples/access_point.rs +++ b/esp-wifi/examples/access_point.rs @@ -13,7 +13,7 @@ use esp_backtrace as _; use esp_println::{print, println}; use esp_wifi::initialize; use esp_wifi::wifi::utils::create_network_interface; -use esp_wifi::wifi::WifiMode; +use esp_wifi::wifi::WifiApDevice; use esp_wifi::wifi_interface::WifiStack; use esp_wifi::{current_millis, EspWifiInitFor}; use hal::clock::ClockControl; @@ -48,7 +48,7 @@ fn main() -> ! { let wifi = peripherals.WIFI; let mut socket_set_entries: [SocketStorage; 3] = Default::default(); let (iface, device, mut controller, sockets) = - create_network_interface(&init, wifi, WifiMode::Ap, &mut socket_set_entries).unwrap(); + create_network_interface(&init, wifi, WifiApDevice, &mut socket_set_entries).unwrap(); let mut wifi_stack = WifiStack::new(iface, device, sockets, current_millis); let client_config = Configuration::AccessPoint(AccessPointConfiguration { diff --git a/esp-wifi/examples/access_point_with_sta.rs b/esp-wifi/examples/access_point_with_sta.rs new file mode 100644 index 00000000..e42c55be --- /dev/null +++ b/esp-wifi/examples/access_point_with_sta.rs @@ -0,0 +1,224 @@ +#![no_std] +#![no_main] + +#[path = "../../examples-util/util.rs"] +mod examples_util; +use examples_util::hal; + +use embedded_io::*; +use embedded_svc::ipv4::Interface; +use embedded_svc::wifi::{AccessPointConfiguration, ClientConfiguration, Configuration, Wifi}; + +use esp_backtrace as _; +use esp_println::{print, println}; +use esp_wifi::initialize; +use esp_wifi::wifi::utils::{create_ap_sta_network_interface, ApStaInterface}; +use esp_wifi::wifi_interface::WifiStack; +use esp_wifi::{current_millis, EspWifiInitFor}; +use hal::clock::ClockControl; +use hal::Rng; +use hal::{peripherals::Peripherals, prelude::*}; + +use smoltcp::iface::SocketStorage; +use smoltcp::wire::IpAddress; +use smoltcp::wire::Ipv4Address; + +const SSID: &str = env!("SSID"); +const PASSWORD: &str = env!("PASSWORD"); + +#[entry] +fn main() -> ! { + #[cfg(feature = "log")] + esp_println::logger::init_logger(log::LevelFilter::Info); + + let peripherals = Peripherals::take(); + + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::max(system.clock_control).freeze(); + + #[cfg(target_arch = "xtensa")] + let timer = hal::timer::TimerGroup::new(peripherals.TIMG1, &clocks).timer0; + #[cfg(target_arch = "riscv32")] + let timer = hal::systimer::SystemTimer::new(peripherals.SYSTIMER).alarm0; + let init = initialize( + EspWifiInitFor::Wifi, + timer, + Rng::new(peripherals.RNG), + system.radio_clock_control, + &clocks, + ) + .unwrap(); + + let wifi = peripherals.WIFI; + + let mut ap_socket_set_entries: [SocketStorage; 3] = Default::default(); + let mut sta_socket_set_entries: [SocketStorage; 3] = Default::default(); + + let ApStaInterface { + ap_interface, + sta_interface, + ap_device, + sta_device, + mut controller, + ap_socket_set, + sta_socket_set, + } = create_ap_sta_network_interface( + &init, + wifi, + &mut ap_socket_set_entries, + &mut sta_socket_set_entries, + ) + .unwrap(); + + let mut wifi_ap_stack = WifiStack::new(ap_interface, ap_device, ap_socket_set, current_millis); + let wifi_sta_stack = WifiStack::new(sta_interface, sta_device, sta_socket_set, current_millis); + + let client_config = Configuration::Mixed( + ClientConfiguration { + ssid: SSID.into(), + password: PASSWORD.into(), + ..Default::default() + }, + AccessPointConfiguration { + ssid: "esp-wifi".into(), + ..Default::default() + }, + ); + let res = controller.set_configuration(&client_config); + println!("wifi_set_configuration returned {:?}", res); + + controller.start().unwrap(); + println!("is wifi started: {:?}", controller.is_started()); + + println!("{:?}", controller.get_capabilities()); + + wifi_ap_stack + .set_iface_configuration(&embedded_svc::ipv4::Configuration::Client( + embedded_svc::ipv4::ClientConfiguration::Fixed(embedded_svc::ipv4::ClientSettings { + ip: embedded_svc::ipv4::Ipv4Addr::from(parse_ip("192.168.2.1")), + subnet: embedded_svc::ipv4::Subnet { + gateway: embedded_svc::ipv4::Ipv4Addr::from(parse_ip("192.168.2.1")), + mask: embedded_svc::ipv4::Mask(24), + }, + dns: None, + secondary_dns: None, + }), + )) + .unwrap(); + + println!("wifi_connect {:?}", controller.connect()); + + // wait for STA getting an ip address + println!("Wait to get an ip address"); + loop { + wifi_sta_stack.work(); + + if wifi_sta_stack.is_iface_up() { + println!("got ip {:?}", wifi_sta_stack.get_ip_info()); + break; + } + } + + println!("Start busy loop on main. Connect to the AP `esp-wifi` and point your browser to http://192.168.2.1:8080/"); + println!("Use a static IP in the range 192.168.2.2 .. 192.168.2.255, use gateway 192.168.2.1"); + + let mut rx_buffer = [0u8; 1536]; + let mut tx_buffer = [0u8; 1536]; + let mut ap_socket = wifi_ap_stack.get_socket(&mut rx_buffer, &mut tx_buffer); + + let mut sta_rx_buffer = [0u8; 1536]; + let mut sta_tx_buffer = [0u8; 1536]; + let mut sta_socket = wifi_sta_stack.get_socket(&mut sta_rx_buffer, &mut sta_tx_buffer); + + ap_socket.listen(8080).unwrap(); + + loop { + ap_socket.work(); + + if !ap_socket.is_open() { + ap_socket.listen(8080).unwrap(); + } + + if ap_socket.is_connected() { + println!("Connected"); + + let mut time_out = false; + let wait_end = current_millis() + 20 * 1000; + let mut buffer = [0u8; 1024]; + let mut pos = 0; + loop { + if let Ok(len) = ap_socket.read(&mut buffer[pos..]) { + let to_print = + unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) }; + + if to_print.contains("\r\n\r\n") { + print!("{}", to_print); + println!(); + break; + } + + pos += len; + } else { + break; + } + + if current_millis() > wait_end { + println!("Timeout"); + time_out = true; + break; + } + } + + if !time_out { + println!("Making HTTP request"); + sta_socket.work(); + + sta_socket + .open(IpAddress::Ipv4(Ipv4Address::new(142, 250, 185, 115)), 80) + .unwrap(); + + sta_socket + .write(b"GET / HTTP/1.0\r\nHost: www.mobile-j.de\r\n\r\n") + .unwrap(); + sta_socket.flush().unwrap(); + + let wait_end = current_millis() + 20 * 1000; + loop { + let mut buffer = [0u8; 512]; + if let Ok(len) = sta_socket.read(&mut buffer) { + ap_socket.write_all(&buffer[..len]).unwrap(); + ap_socket.flush().unwrap(); + } else { + break; + } + + if current_millis() > wait_end { + println!("Timeout"); + break; + } + } + println!(); + + sta_socket.disconnect(); + } + + ap_socket.close(); + + println!("Done\n"); + println!(); + } + + let wait_end = current_millis() + 5 * 1000; + while current_millis() < wait_end { + ap_socket.work(); + } + } +} + +fn parse_ip(ip: &str) -> [u8; 4] { + let mut result = [0u8; 4]; + for (idx, octet) in ip.split(".").into_iter().enumerate() { + result[idx] = u8::from_str_radix(octet, 10).unwrap(); + } + result +} diff --git a/esp-wifi/examples/bench.rs b/esp-wifi/examples/bench.rs index ba867f84..b100bc38 100644 --- a/esp-wifi/examples/bench.rs +++ b/esp-wifi/examples/bench.rs @@ -12,7 +12,7 @@ use embedded_svc::wifi::{AccessPointInfo, ClientConfiguration, Configuration, Wi use esp_backtrace as _; use esp_println::println; use esp_wifi::wifi::utils::create_network_interface; -use esp_wifi::wifi::{WifiError, WifiMode}; +use esp_wifi::wifi::{WifiError, WifiStaDevice}; use esp_wifi::wifi_interface::WifiStack; use esp_wifi::{current_millis, initialize, EspWifiInitFor}; use hal::clock::ClockControl; @@ -62,7 +62,7 @@ fn main() -> ! { let wifi = peripherals.WIFI; let mut socket_set_entries: [SocketStorage; 3] = Default::default(); let (iface, device, mut controller, sockets) = - create_network_interface(&init, wifi, WifiMode::Sta, &mut socket_set_entries).unwrap(); + create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap(); let wifi_stack = WifiStack::new(iface, device, sockets, current_millis); let client_config = Configuration::Client(ClientConfiguration { @@ -137,7 +137,7 @@ fn main() -> ! { fn test_download<'a>( server_address: Ipv4Address, - socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a>, + socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a, WifiStaDevice>, ) { println!("Testing download..."); socket.work(); @@ -171,7 +171,7 @@ fn test_download<'a>( fn test_upload<'a>( server_address: Ipv4Address, - socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a>, + socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a, WifiStaDevice>, ) { println!("Testing upload..."); socket.work(); @@ -205,7 +205,7 @@ fn test_upload<'a>( fn test_upload_download<'a>( server_address: Ipv4Address, - socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a>, + socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a, WifiStaDevice>, ) { println!("Testing upload+download..."); socket.work(); diff --git a/esp-wifi/examples/coex.rs b/esp-wifi/examples/coex.rs index 43c344e4..023e3e6e 100644 --- a/esp-wifi/examples/coex.rs +++ b/esp-wifi/examples/coex.rs @@ -14,7 +14,7 @@ use bleps::{ }; use esp_wifi::{ - ble::controller::BleConnector, current_millis, wifi::WifiMode, wifi_interface::WifiStack, + ble::controller::BleConnector, current_millis, wifi::WifiStaDevice, wifi_interface::WifiStack, EspWifiInitFor, }; @@ -61,7 +61,7 @@ fn main() -> ! { let mut socket_set_entries: [SocketStorage; 3] = Default::default(); let (iface, device, mut controller, sockets) = - create_network_interface(&init, wifi, WifiMode::Sta, &mut socket_set_entries).unwrap(); + create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap(); let wifi_stack = WifiStack::new(iface, device, sockets, current_millis); let client_config = Configuration::Client(ClientConfiguration { diff --git a/esp-wifi/examples/dhcp.rs b/esp-wifi/examples/dhcp.rs index e4435ce9..cd67c266 100644 --- a/esp-wifi/examples/dhcp.rs +++ b/esp-wifi/examples/dhcp.rs @@ -12,7 +12,7 @@ use embedded_svc::wifi::{AccessPointInfo, ClientConfiguration, Configuration, Wi use esp_backtrace as _; use esp_println::{print, println}; use esp_wifi::wifi::utils::create_network_interface; -use esp_wifi::wifi::{WifiError, WifiMode}; +use esp_wifi::wifi::{WifiError, WifiStaDevice}; use esp_wifi::wifi_interface::WifiStack; use esp_wifi::{current_millis, initialize, EspWifiInitFor}; use hal::clock::ClockControl; @@ -51,7 +51,7 @@ fn main() -> ! { let wifi = peripherals.WIFI; let mut socket_set_entries: [SocketStorage; 3] = Default::default(); let (iface, device, mut controller, sockets) = - create_network_interface(&init, wifi, WifiMode::Sta, &mut socket_set_entries).unwrap(); + create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap(); let wifi_stack = WifiStack::new(iface, device, sockets, current_millis); let client_config = Configuration::Client(ClientConfiguration { diff --git a/esp-wifi/examples/embassy_access_point.rs b/esp-wifi/examples/embassy_access_point.rs index 22cf27dd..03552b72 100644 --- a/esp-wifi/examples/embassy_access_point.rs +++ b/esp-wifi/examples/embassy_access_point.rs @@ -15,7 +15,7 @@ use embassy_time::{Duration, Timer}; use embedded_svc::wifi::{AccessPointConfiguration, Configuration, Wifi}; use esp_backtrace as _; use esp_println::{print, println}; -use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiMode, WifiState}; +use esp_wifi::wifi::{WifiApDevice, WifiController, WifiDevice, WifiEvent, WifiState}; use esp_wifi::{initialize, EspWifiInitFor}; use hal::clock::ClockControl; use hal::Rng; @@ -47,7 +47,7 @@ async fn main(spawner: Spawner) -> ! { let wifi = peripherals.WIFI; let (wifi_interface, controller) = - esp_wifi::wifi::new_with_mode(&init, wifi, WifiMode::Ap).unwrap(); + esp_wifi::wifi::new_with_mode(&init, wifi, WifiApDevice).unwrap(); let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); embassy::init(&clocks, timer_group0.timer0); @@ -71,8 +71,8 @@ async fn main(spawner: Spawner) -> ! { spawner.spawn(connection(controller)).ok(); spawner.spawn(net_task(&stack)).ok(); - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; + let mut rx_buffer = [0; 1536]; + let mut tx_buffer = [0; 1536]; loop { if stack.is_link_up() { @@ -184,6 +184,6 @@ async fn connection(mut controller: WifiController<'static>) { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) { +async fn net_task(stack: &'static Stack>) { stack.run().await } diff --git a/esp-wifi/examples/embassy_access_point_with_sta.rs b/esp-wifi/examples/embassy_access_point_with_sta.rs new file mode 100644 index 00000000..1e465f9c --- /dev/null +++ b/esp-wifi/examples/embassy_access_point_with_sta.rs @@ -0,0 +1,302 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_net::tcp::TcpSocket; +use embassy_net::{ + Config, IpListenEndpoint, Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4, +}; +#[path = "../../examples-util/util.rs"] +mod examples_util; +use examples_util::hal; + +use embassy_executor::Spawner; +use embassy_time::{Duration, Timer}; +use embedded_svc::wifi::{AccessPointConfiguration, ClientConfiguration, Configuration, Wifi}; +use esp_backtrace as _; +use esp_println::{print, println}; +use esp_wifi::wifi::{ + WifiApDevice, WifiController, WifiDevice, WifiEvent, WifiStaDevice, WifiState, +}; +use esp_wifi::{initialize, EspWifiInitFor}; +use hal::clock::ClockControl; +use hal::Rng; +use hal::{embassy, peripherals::Peripherals, prelude::*, timer::TimerGroup}; +use static_cell::make_static; + +const SSID: &str = env!("SSID"); +const PASSWORD: &str = env!("PASSWORD"); + +#[main] +async fn main(spawner: Spawner) -> ! { + #[cfg(feature = "log")] + esp_println::logger::init_logger(log::LevelFilter::Info); + + let peripherals = Peripherals::take(); + + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::max(system.clock_control).freeze(); + + #[cfg(target_arch = "xtensa")] + let timer = hal::timer::TimerGroup::new(peripherals.TIMG1, &clocks).timer0; + #[cfg(target_arch = "riscv32")] + let timer = hal::systimer::SystemTimer::new(peripherals.SYSTIMER).alarm0; + let init = initialize( + EspWifiInitFor::Wifi, + timer, + Rng::new(peripherals.RNG), + system.radio_clock_control, + &clocks, + ) + .unwrap(); + + let wifi = peripherals.WIFI; + let (wifi_ap_interface, wifi_sta_interface, mut controller) = + esp_wifi::wifi::new_ap_sta(&init, wifi).unwrap(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + embassy::init(&clocks, timer_group0.timer0); + + let ap_config = Config::ipv4_static(StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 2, 1), 24), + gateway: Some(Ipv4Address::from_bytes(&[192, 168, 2, 1])), + dns_servers: Default::default(), + }); + let sta_config = Config::dhcpv4(Default::default()); + + let seed = 1234; // very random, very secure seed + + // Init network stacks + let ap_stack = &*make_static!(Stack::new( + wifi_ap_interface, + ap_config, + make_static!(StackResources::<3>::new()), + seed + )); + let sta_stack = &*make_static!(Stack::new( + wifi_sta_interface, + sta_config, + make_static!(StackResources::<3>::new()), + seed + )); + + let client_config = Configuration::Mixed( + ClientConfiguration { + ssid: SSID.into(), + password: PASSWORD.into(), + ..Default::default() + }, + AccessPointConfiguration { + ssid: "esp-wifi".into(), + ..Default::default() + }, + ); + controller.set_configuration(&client_config).unwrap(); + + spawner.spawn(connection(controller)).ok(); + spawner.spawn(ap_task(&ap_stack)).ok(); + spawner.spawn(sta_task(&sta_stack)).ok(); + + loop { + if sta_stack.is_link_up() { + break; + } + println!("Waiting for IP..."); + Timer::after(Duration::from_millis(500)).await; + } + loop { + if ap_stack.is_link_up() { + break; + } + Timer::after(Duration::from_millis(500)).await; + } + println!("Connect to the AP `esp-wifi` and point your browser to http://192.168.2.1:8080/"); + println!("Use a static IP in the range 192.168.2.2 .. 192.168.2.255, use gateway 192.168.2.1"); + + let mut ap_rx_buffer = [0; 1536]; + let mut ap_tx_buffer = [0; 1536]; + + let mut ap_socket = TcpSocket::new(&ap_stack, &mut ap_rx_buffer, &mut ap_tx_buffer); + ap_socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + let mut sta_rx_buffer = [0; 1536]; + let mut sta_tx_buffer = [0; 1536]; + + let mut sta_socket = TcpSocket::new(&sta_stack, &mut sta_rx_buffer, &mut sta_tx_buffer); + sta_socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + loop { + println!("Wait for connection..."); + let r = ap_socket + .accept(IpListenEndpoint { + addr: None, + port: 8080, + }) + .await; + println!("Connected..."); + + if let Err(e) = r { + println!("connect error: {:?}", e); + continue; + } + + use embedded_io_async::Write; + + let mut buffer = [0u8; 1024]; + let mut pos = 0; + loop { + match ap_socket.read(&mut buffer).await { + Ok(0) => { + println!("AP read EOF"); + break; + } + Ok(len) => { + let to_print = + unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) }; + + if to_print.contains("\r\n\r\n") { + print!("{}", to_print); + println!(); + break; + } + + pos += len; + } + Err(e) => { + println!("AP read error: {:?}", e); + break; + } + }; + } + + if sta_stack.is_link_up() { + let remote_endpoint = (Ipv4Address::new(142, 250, 185, 115), 80); + println!("connecting..."); + let r = sta_socket.connect(remote_endpoint).await; + if let Err(e) = r { + println!("STA connect error: {:?}", e); + continue; + } + + use embedded_io_async::Write; + let r = sta_socket + .write_all(b"GET / HTTP/1.0\r\nHost: www.mobile-j.de\r\n\r\n") + .await; + + if let Err(e) = r { + println!("STA write error: {:?}", e); + + let r = ap_socket + .write_all( + b"HTTP/1.0 500 Internal Server Error\r\n\r\n\ + \ + \ +

Hello Rust! Hello esp-wifi! STA failed to send request.

\ + \ + \r\n\ + ", + ) + .await; + if let Err(e) = r { + println!("AP write error: {:?}", e); + } + } else { + let r = sta_socket.flush().await; + if let Err(e) = r { + println!("STA flush error: {:?}", e); + } else { + println!("connected!"); + let mut buf = [0; 1024]; + loop { + match sta_socket.read(&mut buf).await { + Ok(0) => { + println!("STA read EOF"); + break; + } + Ok(n) => { + let r = ap_socket.write_all(&buf[..n]).await; + if let Err(e) = r { + println!("AP write error: {:?}", e); + break; + } + } + Err(e) => { + println!("STA read error: {:?}", e); + break; + } + } + } + } + } + + sta_socket.close(); + } else { + let r = ap_socket + .write_all( + b"HTTP/1.0 200 OK\r\n\r\n\ + \ + \ +

Hello Rust! Hello esp-wifi! STA is not connected.

\ + \ + \r\n\ + ", + ) + .await; + if let Err(e) = r { + println!("AP write error: {:?}", e); + } + } + + let r = ap_socket.flush().await; + if let Err(e) = r { + println!("AP flush error: {:?}", e); + } + Timer::after(Duration::from_millis(1000)).await; + + ap_socket.close(); + Timer::after(Duration::from_millis(1000)).await; + + ap_socket.abort(); + } +} + +#[embassy_executor::task] +async fn connection(mut controller: WifiController<'static>) { + println!("start connection task"); + println!("Device capabilities: {:?}", controller.get_capabilities()); + + println!("Starting wifi"); + controller.start().await.unwrap(); + println!("Wifi started!"); + + loop { + match esp_wifi::wifi::get_ap_state() { + WifiState::ApStarted => { + println!("About to connect..."); + + match controller.connect().await { + Ok(_) => { + // wait until we're no longer connected + controller.wait_for_event(WifiEvent::StaDisconnected).await; + println!("STA disconnected"); + } + Err(e) => { + println!("Failed to connect to wifi: {e:?}"); + Timer::after(Duration::from_millis(5000)).await + } + } + } + _ => return, + } + } +} + +#[embassy_executor::task] +async fn ap_task(stack: &'static Stack>) { + stack.run().await +} + +#[embassy_executor::task] +async fn sta_task(stack: &'static Stack>) { + stack.run().await +} diff --git a/esp-wifi/examples/embassy_bench.rs b/esp-wifi/examples/embassy_bench.rs index 4e5df0c1..771bf0dd 100644 --- a/esp-wifi/examples/embassy_bench.rs +++ b/esp-wifi/examples/embassy_bench.rs @@ -14,7 +14,7 @@ use embassy_time::{with_timeout, Duration, Timer}; use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; use esp_backtrace as _; use esp_println::println; -use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiMode, WifiState}; +use esp_wifi::wifi::{WifiApDevice, WifiController, WifiDevice, WifiEvent, WifiState}; use esp_wifi::{initialize, EspWifiInitFor}; use hal::clock::ClockControl; use hal::Rng; @@ -60,7 +60,7 @@ async fn main(spawner: Spawner) -> ! { let wifi = peripherals.WIFI; let (wifi_interface, controller) = - esp_wifi::wifi::new_with_mode(&init, wifi, WifiMode::Sta).unwrap(); + esp_wifi::wifi::new_with_mode(&init, wifi, WifiApDevice).unwrap(); let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); embassy::init(&clocks, timer_group0.timer0); @@ -146,7 +146,7 @@ async fn connection(mut controller: WifiController<'static>) { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) { +async fn net_task(stack: &'static Stack>) { stack.run().await } diff --git a/esp-wifi/examples/embassy_dhcp.rs b/esp-wifi/examples/embassy_dhcp.rs index 243b92f9..d652efb3 100644 --- a/esp-wifi/examples/embassy_dhcp.rs +++ b/esp-wifi/examples/embassy_dhcp.rs @@ -13,7 +13,7 @@ use embassy_time::{Duration, Timer}; use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; use esp_backtrace as _; use esp_println::println; -use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiMode, WifiState}; +use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiStaDevice, WifiState}; use esp_wifi::{initialize, EspWifiInitFor}; use hal::clock::ClockControl; use hal::Rng; @@ -48,7 +48,7 @@ async fn main(spawner: Spawner) -> ! { let wifi = peripherals.WIFI; let (wifi_interface, controller) = - esp_wifi::wifi::new_with_mode(&init, wifi, WifiMode::Sta).unwrap(); + esp_wifi::wifi::new_with_mode(&init, wifi, WifiStaDevice).unwrap(); let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); embassy::init(&clocks, timer_group0.timer0); @@ -166,6 +166,6 @@ async fn connection(mut controller: WifiController<'static>) { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) { +async fn net_task(stack: &'static Stack>) { stack.run().await } diff --git a/esp-wifi/examples/static_ip.rs b/esp-wifi/examples/static_ip.rs index 4919944c..7b313502 100644 --- a/esp-wifi/examples/static_ip.rs +++ b/esp-wifi/examples/static_ip.rs @@ -12,7 +12,7 @@ use embedded_svc::wifi::{AccessPointInfo, ClientConfiguration, Configuration, Wi use esp_backtrace as _; use esp_println::{print, println}; use esp_wifi::initialize; -use esp_wifi::wifi::WifiMode; +use esp_wifi::wifi::WifiStaDevice; use esp_wifi::wifi::{utils::create_network_interface, WifiError}; use esp_wifi::wifi_interface::WifiStack; use esp_wifi::{current_millis, EspWifiInitFor}; @@ -53,7 +53,7 @@ fn main() -> ! { let wifi = peripherals.WIFI; let mut socket_set_entries: [SocketStorage; 3] = Default::default(); let (iface, device, mut controller, sockets) = - create_network_interface(&init, wifi, WifiMode::Sta, &mut socket_set_entries).unwrap(); + create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap(); let mut wifi_stack = WifiStack::new(iface, device, sockets, current_millis); let client_config = Configuration::Client(ClientConfiguration { diff --git a/esp-wifi/src/wifi/mod.rs b/esp-wifi/src/wifi/mod.rs index aeed7a84..eda12e06 100644 --- a/esp-wifi/src/wifi/mod.rs +++ b/esp-wifi/src/wifi/mod.rs @@ -7,7 +7,10 @@ use atomic_polyfill::AtomicUsize; use core::ptr::addr_of; use core::sync::atomic::Ordering; use core::time::Duration; -use core::{cell::RefCell, mem::MaybeUninit}; +use core::{ + cell::{RefCell, RefMut}, + mem::MaybeUninit, +}; use crate::common_adapter::*; use crate::esp_wifi_result; @@ -16,11 +19,11 @@ use crate::hal::peripheral::Peripheral; use crate::hal::peripheral::PeripheralRef; use crate::EspWifiInitialization; -use critical_section::Mutex; +use critical_section::{CriticalSection, Mutex}; use embedded_svc::wifi::{ - AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Protocol, - SecondaryChannel, Wifi, + AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration, + Protocol, SecondaryChannel, Wifi, }; use enumset::EnumSet; @@ -59,11 +62,12 @@ use crate::{ wifi_config_t, wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL, wifi_country_t, wifi_init_config_t, wifi_interface_t, wifi_interface_t_WIFI_IF_AP, wifi_interface_t_WIFI_IF_STA, wifi_mode_t, wifi_mode_t_WIFI_MODE_AP, - wifi_mode_t_WIFI_MODE_NULL, wifi_mode_t_WIFI_MODE_STA, wifi_osi_funcs_t, - wifi_pmf_config_t, wifi_scan_config_t, wifi_scan_threshold_t, wifi_scan_time_t, - wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE, wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE, - wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL, wifi_sta_config_t, wpa_crypto_funcs_t, - ESP_WIFI_OS_ADAPTER_MAGIC, ESP_WIFI_OS_ADAPTER_VERSION, WIFI_INIT_CONFIG_MAGIC, + wifi_mode_t_WIFI_MODE_APSTA, wifi_mode_t_WIFI_MODE_NULL, wifi_mode_t_WIFI_MODE_STA, + wifi_osi_funcs_t, wifi_pmf_config_t, wifi_scan_config_t, wifi_scan_threshold_t, + wifi_scan_time_t, wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE, + wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE, wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL, + wifi_sta_config_t, wpa_crypto_funcs_t, ESP_WIFI_OS_ADAPTER_MAGIC, + ESP_WIFI_OS_ADAPTER_VERSION, WIFI_INIT_CONFIG_MAGIC, }, }, compat::queue::SimpleQueue, @@ -105,12 +109,13 @@ impl AuthMethodExt for AuthMethod { } } -/// Wifi Mode (Sta or Ap) -#[derive(Debug, Clone, Copy)] +/// Wifi Mode (Sta and/or Ap) +#[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum WifiMode { Sta, Ap, + ApSta, } impl WifiMode { @@ -118,34 +123,50 @@ impl WifiMode { let mut mode = wifi_mode_t_WIFI_MODE_NULL; esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut mode) })?; - WifiMode::try_from(mode) + Self::try_from(mode) } - /// Returns true if this mode is STA + /// Returns true if this mode works as a client pub fn is_sta(&self) -> bool { match self { - WifiMode::Sta => true, - WifiMode::Ap => false, + Self::Sta | Self::ApSta => true, + Self::Ap => false, } } - /// Returns true if this mode is AP + /// Returns true if this mode works as an access point pub fn is_ap(&self) -> bool { match self { - WifiMode::Sta => false, - WifiMode::Ap => true, + Self::Sta => false, + Self::Ap | Self::ApSta => true, } } } +impl TryFrom<&Configuration> for WifiMode { + type Error = WifiError; + + fn try_from(config: &Configuration) -> Result { + let mode = match config { + Configuration::None => return Err(WifiError::UnknownWifiMode), + Configuration::AccessPoint(_) => Self::Ap, + Configuration::Client(_) => Self::Sta, + Configuration::Mixed(_, _) => Self::ApSta, + }; + + Ok(mode) + } +} + impl TryFrom for WifiMode { type Error = WifiError; fn try_from(value: wifi_mode_t) -> Result { #[allow(non_upper_case_globals)] match value { - include::wifi_mode_t_WIFI_MODE_STA => Ok(WifiMode::Sta), - include::wifi_mode_t_WIFI_MODE_AP => Ok(WifiMode::Ap), + include::wifi_mode_t_WIFI_MODE_STA => Ok(Self::Sta), + include::wifi_mode_t_WIFI_MODE_AP => Ok(Self::Ap), + include::wifi_mode_t_WIFI_MODE_APSTA => Ok(Self::ApSta), _ => Err(WifiError::UnknownWifiMode), } } @@ -155,47 +176,25 @@ impl Into for WifiMode { fn into(self) -> wifi_mode_t { #[allow(non_upper_case_globals)] match self { - WifiMode::Sta => wifi_mode_t_WIFI_MODE_STA, - WifiMode::Ap => wifi_mode_t_WIFI_MODE_AP, + Self::Sta => wifi_mode_t_WIFI_MODE_STA, + Self::Ap => wifi_mode_t_WIFI_MODE_AP, + Self::ApSta => wifi_mode_t_WIFI_MODE_APSTA, } } } -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Take care not to drop this while in a critical section. -/// -/// Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer` which -/// will try to lock an internal mutex. If the mutex is already taken, the function will -/// try to trigger a context switch, which will fail if we are in a critical section. -pub(crate) struct EspWifiPacketBuffer { - pub(crate) buffer: *mut c_types::c_void, - pub(crate) len: u16, - pub(crate) eb: *mut c_types::c_void, -} - -unsafe impl Send for EspWifiPacketBuffer {} - -impl Drop for EspWifiPacketBuffer { - fn drop(&mut self) { - trace!("Dropping EspWifiPacketBuffer, freeing memory"); - unsafe { esp_wifi_internal_free_rx_buffer(self.eb) }; - } -} - -impl EspWifiPacketBuffer { - pub fn as_slice_mut(&mut self) -> &mut [u8] { - unsafe { core::slice::from_raw_parts_mut(self.buffer as *mut u8, self.len as usize) } - } -} - const DATA_FRAME_SIZE: usize = MTU + ETHERNET_FRAME_HEADER_SIZE; const RX_QUEUE_SIZE: usize = crate::CONFIG.rx_queue_size; const TX_QUEUE_SIZE: usize = crate::CONFIG.tx_queue_size; -pub(crate) static DATA_QUEUE_RX: Mutex>> = - Mutex::new(RefCell::new(SimpleQueue::new())); +pub(crate) static DATA_QUEUE_RX_AP: Mutex< + RefCell>, +> = Mutex::new(RefCell::new(SimpleQueue::new())); + +pub(crate) static DATA_QUEUE_RX_STA: Mutex< + RefCell>, +> = Mutex::new(RefCell::new(SimpleQueue::new())); /// Common errors #[derive(Debug, Clone, Copy)] @@ -631,13 +630,13 @@ pub(crate) fn wifi_init() -> Result<(), WifiError> { esp_wifi_result!(esp_wifi_internal_reg_rxcb( esp_interface_t_ESP_IF_WIFI_STA, - Some(recv_cb) + Some(recv_cb_sta) ))?; // until we support APSTA we just register the same callback for AP and STA esp_wifi_result!(esp_wifi_internal_reg_rxcb( esp_interface_t_ESP_IF_WIFI_AP, - Some(recv_cb) + Some(recv_cb_ap) ))?; #[cfg(any(esp32, esp32s3))] @@ -650,30 +649,47 @@ pub(crate) fn wifi_init() -> Result<(), WifiError> { } } -unsafe extern "C" fn recv_cb( +unsafe extern "C" fn recv_cb_sta( buffer: *mut c_types::c_void, len: u16, eb: *mut c_types::c_void, ) -> esp_err_t { - let result = critical_section::with(|cs| { - let packet = EspWifiPacketBuffer { buffer, len, eb }; - - DATA_QUEUE_RX.borrow_ref_mut(cs).enqueue(packet) - }); - + let packet = EspWifiPacketBuffer { buffer, len, eb }; // We must handle the result outside of the critical section because // EspWifiPacketBuffer::drop must not be called in a critical section. // Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer` which // will try to lock an internal mutex. If the mutex is already taken, the function will // try to trigger a context switch, which will fail if we are in a critical section. - match result { + match critical_section::with(|cs| DATA_QUEUE_RX_STA.borrow_ref_mut(cs).enqueue(packet)) { Ok(_) => { #[cfg(feature = "embassy-net")] - embassy::RECEIVE_WAKER.wake(); - + embassy::STA_RECEIVE_WAKER.wake(); include::ESP_OK as esp_err_t } + Err(_) => { + debug!("RX QUEUE FULL"); + include::ESP_ERR_NO_MEM as esp_err_t + } + } +} +unsafe extern "C" fn recv_cb_ap( + buffer: *mut c_types::c_void, + len: u16, + eb: *mut c_types::c_void, +) -> esp_err_t { + let packet = EspWifiPacketBuffer { buffer, len, eb }; + // We must handle the result outside of the critical section because + // EspWifiPacketBuffer::drop must not be called in a critical section. + // Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer` which + // will try to lock an internal mutex. If the mutex is already taken, the function will + // try to trigger a context switch, which will fail if we are in a critical section. + match critical_section::with(|cs| DATA_QUEUE_RX_AP.borrow_ref_mut(cs).enqueue(packet)) { + Ok(_) => { + #[cfg(feature = "embassy-net")] + embassy::AP_RECEIVE_WAKER.wake(); + include::ESP_OK as esp_err_t + } Err(_) => { debug!("RX QUEUE FULL"); include::ESP_ERR_NO_MEM as esp_err_t @@ -902,52 +918,314 @@ pub(crate) fn wifi_start_scan( unsafe { esp_wifi_scan_start(&scan_config, block) } } -/// Create a new [WifiDevice] and [WifiController] from the given config -pub fn new_with_config<'d>( +/// Creates a new [WifiDevice] and [WifiController] in either AP or STA mode with the given +/// configuration. +/// +/// This function will panic if the configuration is not +/// [`Configuration::Client`] or [`Configuration::Station`]. +/// +/// If you want to use AP-STA mode, use `[new_ap_sta]`. +pub fn new_with_config<'d, MODE: WifiDeviceMode>( inited: &EspWifiInitialization, device: impl Peripheral

+ 'd, - config: embedded_svc::wifi::Configuration, -) -> Result<(WifiDevice<'d>, WifiController<'d>), WifiError> { - if !inited.is_wifi() { - return Err(WifiError::NotInitialized); - } - + config: MODE::Config, +) -> Result<(WifiDevice<'d, MODE>, WifiController<'d>), WifiError> { crate::hal::into_ref!(device); Ok(( - WifiDevice::new(unsafe { device.clone_unchecked() }), - WifiController::new_with_config(device, config)?, + WifiDevice::new(unsafe { device.clone_unchecked() }, MODE::new()), + WifiController::new_with_config(inited, device, MODE::wrap_config(config))?, )) } -/// Create a new [WifiDevice] and [WifiController] for the given mode -pub fn new_with_mode<'d>( +/// Creates a new [WifiDevice] and [WifiController] in either AP or STA mode with a default +/// configuration. +/// +/// This function will panic if the mode is [`WifiMode::ApSta`]. +/// If you want to use AP-STA mode, use `[new_ap_sta]`. +pub fn new_with_mode<'d, MODE: WifiDeviceMode>( + inited: &EspWifiInitialization, + device: impl crate::hal::peripheral::Peripheral

+ 'd, + _mode: MODE, +) -> Result<(WifiDevice<'d, MODE>, WifiController<'d>), WifiError> { + new_with_config(inited, device, ::Config::default()) +} + +/// Creates a new [WifiDevice] and [WifiController] in AP-STA mode, with a default configuration. +/// +/// Returns a tuple of `(AP device, STA device, controller)`. +pub fn new_ap_sta<'d>( inited: &EspWifiInitialization, device: impl Peripheral

+ 'd, - mode: WifiMode, -) -> Result<(WifiDevice<'d>, WifiController<'d>), WifiError> { - new_with_config( - inited, - device, - match mode { - WifiMode::Sta => embedded_svc::wifi::Configuration::Client(Default::default()), - WifiMode::Ap => embedded_svc::wifi::Configuration::AccessPoint(Default::default()), - }, - ) +) -> Result< + ( + WifiDevice<'d, WifiApDevice>, + WifiDevice<'d, WifiStaDevice>, + WifiController<'d>, + ), + WifiError, +> { + new_ap_sta_with_config(inited, device, Default::default(), Default::default()) +} + +/// Creates a new Wifi device and controller in AP-STA mode. +/// +/// Returns a tuple of `(AP device, STA device, controller)`. +pub fn new_ap_sta_with_config<'d>( + inited: &EspWifiInitialization, + device: impl Peripheral

+ 'd, + sta_config: embedded_svc::wifi::ClientConfiguration, + ap_config: embedded_svc::wifi::AccessPointConfiguration, +) -> Result< + ( + WifiDevice<'d, WifiApDevice>, + WifiDevice<'d, WifiStaDevice>, + WifiController<'d>, + ), + WifiError, +> { + crate::hal::into_ref!(device); + + Ok(( + WifiDevice::new(unsafe { device.clone_unchecked() }, WifiApDevice), + WifiDevice::new(unsafe { device.clone_unchecked() }, WifiStaDevice), + WifiController::new_with_config( + inited, + device, + Configuration::Mixed(sta_config, ap_config), + )?, + )) +} + +mod sealed { + use super::*; + + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + /// Take care not to drop this while in a critical section. + /// + /// Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer` which + /// will try to lock an internal mutex. If the mutex is already taken, the function will + /// try to trigger a context switch, which will fail if we are in a critical section. + pub struct EspWifiPacketBuffer { + pub(crate) buffer: *mut c_types::c_void, + pub(crate) len: u16, + pub(crate) eb: *mut c_types::c_void, + } + + unsafe impl Send for EspWifiPacketBuffer {} + + impl Drop for EspWifiPacketBuffer { + fn drop(&mut self) { + trace!("Dropping EspWifiPacketBuffer, freeing memory"); + unsafe { esp_wifi_internal_free_rx_buffer(self.eb) }; + } + } + + impl EspWifiPacketBuffer { + pub fn as_slice_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.buffer as *mut u8, self.len as usize) } + } + } + + pub trait Sealed: Copy + Sized { + type Config: Default; + + fn new() -> Self; + + fn wrap_config(config: Self::Config) -> Configuration; + + fn data_queue_rx( + self, + cs: CriticalSection, + ) -> RefMut<'_, SimpleQueue>; + + fn can_send(self) -> bool { + WIFI_TX_INFLIGHT.load(Ordering::SeqCst) < TX_QUEUE_SIZE + } + + fn increase_in_flight_counter(self) { + WIFI_TX_INFLIGHT.fetch_add(1, Ordering::SeqCst); + } + + fn tx_token(self) -> Option> { + if self.can_send() { + Some(WifiTxToken { mode: self }) + } else { + warn!("no Tx token available"); + None + } + } + + fn rx_token(self) -> Option<(WifiRxToken, WifiTxToken)> { + let is_empty = critical_section::with(|cs| self.data_queue_rx(cs).is_empty()); + + if !is_empty { + self.tx_token().map(|tx| (WifiRxToken { mode: self }, tx)) + } else { + trace!("no Rx token available"); + None + } + } + + fn interface(self) -> wifi_interface_t; + + #[cfg(feature = "embassy-net")] + fn register_transmit_waker(self, cx: &mut core::task::Context) { + embassy::TRANSMIT_WAKER.register(cx.waker()) + } + + #[cfg(feature = "embassy-net")] + fn register_receive_waker(self, cx: &mut core::task::Context); + + #[cfg(feature = "embassy-net")] + fn register_link_state_waker(self, cx: &mut core::task::Context); + + #[cfg(feature = "embassy-net")] + fn link_state(self) -> embassy_net::driver::LinkState; + } + + impl Sealed for WifiStaDevice { + type Config = ClientConfiguration; + + fn new() -> Self { + Self + } + + fn wrap_config(config: ClientConfiguration) -> Configuration { + Configuration::Client(config) + } + + fn data_queue_rx( + self, + cs: CriticalSection, + ) -> RefMut<'_, SimpleQueue> { + DATA_QUEUE_RX_STA.borrow_ref_mut(cs) + } + + fn interface(self) -> wifi_interface_t { + wifi_interface_t_WIFI_IF_STA + } + + #[cfg(feature = "embassy-net")] + fn register_receive_waker(self, cx: &mut core::task::Context) { + embassy::STA_RECEIVE_WAKER.register(cx.waker()); + } + + #[cfg(feature = "embassy-net")] + fn register_link_state_waker(self, cx: &mut core::task::Context) { + embassy::STA_LINK_STATE_WAKER.register(cx.waker()); + } + + #[cfg(feature = "embassy-net")] + fn link_state(self) -> embassy_net::driver::LinkState { + if matches!(get_sta_state(), WifiState::StaConnected) { + embassy_net::driver::LinkState::Up + } else { + embassy_net::driver::LinkState::Down + } + } + } + + impl Sealed for WifiApDevice { + type Config = AccessPointConfiguration; + + fn new() -> Self { + Self + } + + fn wrap_config(config: AccessPointConfiguration) -> Configuration { + Configuration::AccessPoint(config) + } + + fn data_queue_rx( + self, + cs: CriticalSection, + ) -> RefMut<'_, SimpleQueue> { + DATA_QUEUE_RX_AP.borrow_ref_mut(cs) + } + + fn interface(self) -> wifi_interface_t { + wifi_interface_t_WIFI_IF_AP + } + + #[cfg(feature = "embassy-net")] + fn register_receive_waker(self, cx: &mut core::task::Context) { + embassy::AP_RECEIVE_WAKER.register(cx.waker()); + } + + #[cfg(feature = "embassy-net")] + fn register_link_state_waker(self, cx: &mut core::task::Context) { + embassy::AP_LINK_STATE_WAKER.register(cx.waker()); + } + + #[cfg(feature = "embassy-net")] + fn link_state(self) -> embassy_net::driver::LinkState { + if matches!(get_ap_state(), WifiState::ApStarted) { + embassy_net::driver::LinkState::Up + } else { + embassy_net::driver::LinkState::Down + } + } + } +} + +use sealed::*; + +pub trait WifiDeviceMode: Sealed { + fn mode(self) -> WifiMode; + + fn mac_address(self) -> [u8; 6]; +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct WifiStaDevice; + +impl WifiDeviceMode for WifiStaDevice { + fn mode(self) -> WifiMode { + WifiMode::Sta + } + + fn mac_address(self) -> [u8; 6] { + let mut mac = [0; 6]; + get_sta_mac(&mut mac); + mac + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct WifiApDevice; + +impl WifiDeviceMode for WifiApDevice { + fn mode(self) -> WifiMode { + WifiMode::Ap + } + + fn mac_address(self) -> [u8; 6] { + let mut mac = [0; 6]; + get_ap_mac(&mut mac); + mac + } } /// A wifi device implementing smoltcp's Device trait. -pub struct WifiDevice<'d> { +pub struct WifiDevice<'d, MODE: WifiDeviceMode> { _device: PeripheralRef<'d, crate::hal::peripherals::WIFI>, + mode: MODE, } -impl<'d> WifiDevice<'d> { - pub(crate) fn new(_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>) -> WifiDevice { - Self { _device } +impl<'d, MODE: WifiDeviceMode> WifiDevice<'d, MODE> { + pub(crate) fn new( + _device: PeripheralRef<'d, crate::hal::peripherals::WIFI>, + mode: MODE, + ) -> Self { + Self { _device, mode } } - pub(crate) fn get_wifi_mode(&self) -> Result { - WifiMode::current() + pub fn mac_address(&self) -> [u8; 6] { + self.mode.mac_address() } } @@ -981,14 +1259,19 @@ fn convert_ap_info(record: &include::wifi_ap_record_t) -> AccessPointInfo { /// A wifi controller implementing embedded_svc::Wifi traits pub struct WifiController<'d> { _device: PeripheralRef<'d, crate::hal::peripherals::WIFI>, - config: embedded_svc::wifi::Configuration, + config: Configuration, } impl<'d> WifiController<'d> { pub(crate) fn new_with_config( + inited: &EspWifiInitialization, _device: PeripheralRef<'d, crate::hal::peripherals::WIFI>, - config: embedded_svc::wifi::Configuration, + config: Configuration, ) -> Result { + if !inited.is_wifi() { + return Err(WifiError::NotInitialized); + } + // We set up the controller with the default config because we need to call // `set_configuration` to apply the actual configuration, and it will update the stored // configuration anyway. @@ -997,18 +1280,9 @@ impl<'d> WifiController<'d> { config: Default::default(), }; - match config { - embedded_svc::wifi::Configuration::None => panic!(), - embedded_svc::wifi::Configuration::Client(_) => { - esp_wifi_result!(unsafe { esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_STA) })?; - debug!("Wifi mode STA set"); - } - embedded_svc::wifi::Configuration::AccessPoint(_) => { - esp_wifi_result!(unsafe { esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_AP) })?; - debug!("Wifi mode AP set"); - } - embedded_svc::wifi::Configuration::Mixed(_, _) => unimplemented!(), - }; + let mode = WifiMode::try_from(&config)?; + esp_wifi_result!(unsafe { esp_wifi_set_mode(mode.into()) })?; + debug!("Wifi mode {:?} set", mode); this.set_configuration(&config)?; Ok(this) @@ -1038,14 +1312,12 @@ impl<'d> WifiController<'d> { Ok(()) } - #[allow(unused)] - fn is_sta_enabled(&self) -> Result { - WifiMode::current().map(|m| m.is_sta()) + pub fn is_sta_enabled(&self) -> Result { + WifiMode::try_from(&self.config).map(|m| m.is_sta()) } - #[allow(unused)] - fn is_ap_enabled(&self) -> Result { - WifiMode::current().map(|m| m.is_ap()) + pub fn is_ap_enabled(&self) -> Result { + WifiMode::try_from(&self.config).map(|m| m.is_ap()) } fn scan_result_count(&mut self) -> Result { @@ -1093,31 +1365,19 @@ impl<'d> WifiController<'d> { } // see https://docs.rs/smoltcp/0.7.1/smoltcp/phy/index.html -impl Device for WifiDevice<'_> { - type RxToken<'a> = WifiRxToken where Self: 'a; - type TxToken<'a> = WifiTxToken where Self: 'a; +impl Device for WifiDevice<'_, MODE> { + type RxToken<'a> = WifiRxToken where Self: 'a; + type TxToken<'a> = WifiTxToken where Self: 'a; fn receive( &mut self, _instant: smoltcp::time::Instant, ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - critical_section::with(|cs| { - let rx = DATA_QUEUE_RX.borrow_ref_mut(cs); - if !rx.is_empty() && esp_wifi_can_send() { - Some((WifiRxToken::default(), WifiTxToken::default())) - } else { - None - } - }) + self.mode.rx_token() } fn transmit(&mut self, _instant: smoltcp::time::Instant) -> Option> { - if esp_wifi_can_send() { - Some(WifiTxToken::default()) - } else { - warn!("no Tx token available"); - None - } + self.mode.tx_token() } fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities { @@ -1133,82 +1393,80 @@ impl Device for WifiDevice<'_> { } #[doc(hidden)] -#[derive(Debug, Default)] -pub struct WifiRxToken {} +#[derive(Debug)] +pub struct WifiRxToken { + mode: MODE, +} -impl RxToken for WifiRxToken { - fn consume(self, f: F) -> R +impl WifiRxToken { + fn consume_token(self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - rx_token_consume(f) + let mut data = critical_section::with(|cs| { + let mut queue = self.mode.data_queue_rx(cs); + + unwrap!( + queue.dequeue(), + "unreachable: transmit()/receive() ensures there is a packet to process" + ) + }); + + // We handle the received data outside of the critical section because + // EspWifiPacketBuffer::drop must not be called in a critical section. + // Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer` which + // will try to lock an internal mutex. If the mutex is already taken, the function will + // try to trigger a context switch, which will fail if we are in a critical section. + let buffer = data.as_slice_mut(); + dump_packet_info(&buffer); + + f(buffer) } } -#[doc(hidden)] -#[derive(Debug, Default)] -pub struct WifiTxToken {} - -impl TxToken for WifiTxToken { - fn consume(self, len: usize, f: F) -> R +impl RxToken for WifiRxToken { + fn consume(self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - tx_token_consume(len, f) + self.consume_token(f) } } -fn rx_token_consume(f: F) -> R -where - F: FnOnce(&mut [u8]) -> R, -{ - let mut data = critical_section::with(|cs| { - let mut queue = DATA_QUEUE_RX.borrow_ref_mut(cs); - - unwrap!( - queue.dequeue(), - "unreachable: transmit()/receive() ensures there is a packet to process" - ) - }); +#[doc(hidden)] +#[derive(Debug)] +pub struct WifiTxToken { + mode: MODE, +} - // We handle the received data outside of the critical section because - // EspWifiPacketBuffer::drop must not be called in a critical section. - // Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer` which - // will try to lock an internal mutex. If the mutex is already taken, the function will - // try to trigger a context switch, which will fail if we are in a critical section. - let buffer = data.as_slice_mut(); - dump_packet_info(&buffer); +impl WifiTxToken { + fn consume_token(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + self.mode.increase_in_flight_counter(); - f(buffer) -} + // (safety): creation of multiple WiFi devices with the same mode is impossible in safe Rust, + // therefore only smoltcp _or_ embassy-net can be used at one time + static mut BUFFER: [u8; DATA_FRAME_SIZE] = [0u8; DATA_FRAME_SIZE]; -fn tx_token_consume(len: usize, f: F) -> R -where - F: FnOnce(&mut [u8]) -> R, -{ - WIFI_TX_INFLIGHT.fetch_add(1, Ordering::SeqCst); - // (safety): creation of multiple WiFi devices is impossible in safe Rust, therefore only smoltcp _or_ embassy-net can be used at one time - // TODO: this probably won't do in AP-STA mode - static mut BUFFER: [u8; DATA_FRAME_SIZE] = [0u8; DATA_FRAME_SIZE]; - let buffer = unsafe { &mut BUFFER[..len] }; - let res = f(buffer); - - let mode = unwrap!(WifiMode::current()); - - // FIXME this won't do in AP-STA mode - let interface = if mode.is_ap() { - wifi_interface_t_WIFI_IF_AP - } else { - wifi_interface_t_WIFI_IF_STA - }; + let buffer = unsafe { &mut BUFFER[..len] }; + + let res = f(buffer); - esp_wifi_send_data(interface, buffer); + esp_wifi_send_data(self.mode.interface(), buffer); - res + res + } } -fn esp_wifi_can_send() -> bool { - WIFI_TX_INFLIGHT.load(Ordering::SeqCst) < TX_QUEUE_SIZE +impl TxToken for WifiTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + self.consume_token(len, f) + } } // FIXME data here has to be &mut because of `esp_wifi_internal_tx` signature, requiring a *mut ptr to the buffer @@ -1307,10 +1565,17 @@ impl Wifi for WifiController<'_> { /// This currently only supports the `Client` and `AccessPoint` capability. fn get_capabilities(&self) -> Result, Self::Error> { - // we only support STA and AP mode - let mut caps = EnumSet::empty(); - caps.insert(embedded_svc::wifi::Capability::Client); - caps.insert(embedded_svc::wifi::Capability::AccessPoint); + use embedded_svc::wifi::Capability; + + let caps = match self.config { + Configuration::None => unreachable!(), + Configuration::Client(_) => enumset::enum_set! { Capability::Client }, + Configuration::AccessPoint(_) => enumset::enum_set! { Capability::AccessPoint }, + Configuration::Mixed(_, _) => { + Capability::Client | Capability::AccessPoint | Capability::Mixed + } + }; + Ok(caps) } @@ -1327,23 +1592,57 @@ impl Wifi for WifiController<'_> { } /// Get the currently used configuration. - fn get_configuration(&self) -> Result { + fn get_configuration(&self) -> Result { Ok(self.config.clone()) } /// Set the configuration, you need to use Wifi::connect() for connecting to an AP /// Trying anything but `Configuration::Client` or `Configuration::AccessPoint` will result in a panic! - fn set_configuration( - &mut self, - conf: &embedded_svc::wifi::Configuration, - ) -> Result<(), Self::Error> { - self.config = conf.clone(); + fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> { + match self.config { + Configuration::None => self.config = conf.clone(), // initial config + Configuration::Client(ref mut client) => { + if let Configuration::Client(conf) = conf { + *client = conf.clone(); + } else { + return Err(WifiError::InternalError( + InternalWifiError::EspErrInvalidArg, + )); + } + } + Configuration::AccessPoint(ref mut ap) => { + if let Configuration::AccessPoint(conf) = conf { + *ap = conf.clone(); + } else { + return Err(WifiError::InternalError( + InternalWifiError::EspErrInvalidArg, + )); + } + } + Configuration::Mixed(ref mut client, ref mut ap) => match conf { + Configuration::None => { + return Err(WifiError::InternalError( + InternalWifiError::EspErrInvalidArg, + )); + } + Configuration::Mixed(_, _) => self.config = conf.clone(), + Configuration::Client(conf) => *client = conf.clone(), + Configuration::AccessPoint(conf) => *ap = conf.clone(), + }, + } match conf { - embedded_svc::wifi::Configuration::None => panic!(), - embedded_svc::wifi::Configuration::Client(config) => apply_sta_config(config)?, - embedded_svc::wifi::Configuration::AccessPoint(config) => apply_ap_config(config)?, - embedded_svc::wifi::Configuration::Mixed(_, _) => panic!(), + Configuration::None => { + return Err(WifiError::InternalError( + InternalWifiError::EspErrInvalidArg, + )); + } + Configuration::Client(config) => apply_sta_config(config)?, + Configuration::AccessPoint(config) => apply_ap_config(config)?, + Configuration::Mixed(sta_config, ap_config) => { + apply_ap_config(ap_config)?; + apply_sta_config(sta_config)?; + } }; Ok(()) @@ -1417,86 +1716,55 @@ pub(crate) mod embassy { use embassy_net::driver::{Capabilities, Driver, HardwareAddress, RxToken, TxToken}; use embassy_sync::waitqueue::AtomicWaker; + // We can get away with a single tx waker because the transmit queue is shared + // between interfaces. pub(crate) static TRANSMIT_WAKER: AtomicWaker = AtomicWaker::new(); - pub(crate) static RECEIVE_WAKER: AtomicWaker = AtomicWaker::new(); - pub(crate) static LINK_STATE: AtomicWaker = AtomicWaker::new(); - impl RxToken for WifiRxToken { + pub(crate) static AP_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new(); + pub(crate) static AP_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new(); + + pub(crate) static STA_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new(); + pub(crate) static STA_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new(); + + impl RxToken for WifiRxToken { fn consume(self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - rx_token_consume(f) + self.consume_token(f) } } - impl TxToken for WifiTxToken { + impl TxToken for WifiTxToken { fn consume(self, len: usize, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - tx_token_consume(len, f) + self.consume_token(len, f) } } - impl Driver for WifiDevice<'_> { - type RxToken<'a> = WifiRxToken - where - Self: 'a; - - type TxToken<'a> = WifiTxToken - where - Self: 'a; + impl Driver for WifiDevice<'_, MODE> { + type RxToken<'a> = WifiRxToken where Self: 'a; + type TxToken<'a> = WifiTxToken where Self: 'a; fn receive( &mut self, cx: &mut core::task::Context, ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - RECEIVE_WAKER.register(cx.waker()); - TRANSMIT_WAKER.register(cx.waker()); - - critical_section::with(|cs| { - let rx = DATA_QUEUE_RX.borrow_ref_mut(cs); - if !rx.is_empty() && esp_wifi_can_send() { - Some((WifiRxToken::default(), WifiTxToken::default())) - } else { - None - } - }) + self.mode.register_receive_waker(cx); + self.mode.register_transmit_waker(cx); + self.mode.rx_token() } fn transmit(&mut self, cx: &mut core::task::Context) -> Option> { - TRANSMIT_WAKER.register(cx.waker()); - if esp_wifi_can_send() { - Some(WifiTxToken::default()) - } else { - None - } + self.mode.register_transmit_waker(cx); + self.mode.tx_token() } fn link_state(&mut self, cx: &mut core::task::Context) -> embassy_net::driver::LinkState { - LINK_STATE.register(cx.waker()); - - match self.get_wifi_mode() { - Ok(WifiMode::Sta) => { - if matches!(get_sta_state(), WifiState::StaConnected) { - embassy_net::driver::LinkState::Up - } else { - embassy_net::driver::LinkState::Down - } - } - Ok(WifiMode::Ap) => { - if matches!(get_ap_state(), WifiState::ApStarted) { - embassy_net::driver::LinkState::Up - } else { - embassy_net::driver::LinkState::Down - } - } - _ => { - warn!("Unknown wifi mode in link_state"); - embassy_net::driver::LinkState::Down - } - } + self.mode.register_link_state_waker(cx); + self.mode.link_state() } fn capabilities(&self) -> Capabilities { @@ -1511,13 +1779,7 @@ pub(crate) mod embassy { } fn hardware_address(&self) -> HardwareAddress { - let mut mac = [0; 6]; - match self.get_wifi_mode() { - Ok(WifiMode::Ap) => get_ap_mac(&mut mac), - Ok(WifiMode::Sta) => get_sta_mac(&mut mac), - _ => get_sta_mac(&mut mac), - } - HardwareAddress::Ethernet(mac) + HardwareAddress::Ethernet(self.mac_address()) } } } @@ -1561,12 +1823,7 @@ mod asynch { /// Async version of [`embedded_svc::wifi::Wifi`]'s `start` method pub async fn start(&mut self) -> Result<(), WifiError> { - let mode = match self.config { - embedded_svc::wifi::Configuration::None => panic!(), - embedded_svc::wifi::Configuration::Client(_) => WifiMode::Sta, - embedded_svc::wifi::Configuration::AccessPoint(_) => WifiMode::Ap, - embedded_svc::wifi::Configuration::Mixed(_, _) => panic!(), - }; + let mode = WifiMode::try_from(&self.config)?; let mut events = enumset::enum_set! {}; if mode.is_ap() { @@ -1587,12 +1844,7 @@ mod asynch { /// Async version of [`embedded_svc::wifi::Wifi`]'s `stop` method pub async fn stop(&mut self) -> Result<(), WifiError> { - let mode = match self.config { - embedded_svc::wifi::Configuration::None => panic!(), - embedded_svc::wifi::Configuration::Client(_) => WifiMode::Sta, - embedded_svc::wifi::Configuration::AccessPoint(_) => WifiMode::Ap, - embedded_svc::wifi::Configuration::Mixed(_, _) => panic!(), - }; + let mode = WifiMode::try_from(&self.config)?; let mut events = enumset::enum_set! {}; if mode.is_ap() { diff --git a/esp-wifi/src/wifi/os_adapter.rs b/esp-wifi/src/wifi/os_adapter.rs index 45916233..ecf940b4 100644 --- a/esp-wifi/src/wifi/os_adapter.rs +++ b/esp-wifi/src/wifi/os_adapter.rs @@ -893,14 +893,16 @@ pub unsafe extern "C" fn event_post( event.waker().wake(); #[cfg(feature = "embassy-net")] - if matches!( - event, - WifiEvent::StaConnected - | WifiEvent::StaDisconnected - | WifiEvent::ApStart - | WifiEvent::ApStop - ) { - crate::wifi::embassy::LINK_STATE.wake(); + match event { + WifiEvent::StaConnected | WifiEvent::StaDisconnected => { + crate::wifi::embassy::STA_LINK_STATE_WAKER.wake(); + } + + WifiEvent::ApStart | WifiEvent::ApStop => { + crate::wifi::embassy::AP_LINK_STATE_WAKER.wake(); + } + + _ => {} } memory_fence(); diff --git a/esp-wifi/src/wifi/utils.rs b/esp-wifi/src/wifi/utils.rs index f783a287..5a3af24c 100644 --- a/esp-wifi/src/wifi/utils.rs +++ b/esp-wifi/src/wifi/utils.rs @@ -7,44 +7,88 @@ use smoltcp::{ wire::{EthernetAddress, HardwareAddress}, }; -use crate::{current_millis, wifi::get_sta_mac}; -use crate::{wifi::get_ap_mac, EspWifiInitialization}; +use crate::current_millis; +use crate::EspWifiInitialization; -use super::{WifiController, WifiDevice, WifiError, WifiMode}; +use super::{WifiApDevice, WifiController, WifiDevice, WifiDeviceMode, WifiError, WifiStaDevice}; -/// Convenient way to create an `smoltcp` ethernet interface -/// You can use the provided macros to create and pass a suitable backing storage. -pub fn create_network_interface<'a, 'd>( - inited: &EspWifiInitialization, - device: impl crate::hal::peripheral::Peripheral

+ 'd, - mode: WifiMode, +fn setup_iface<'a, MODE: WifiDeviceMode>( + device: &mut WifiDevice<'_, MODE>, + mode: MODE, storage: &'a mut [SocketStorage<'a>], -) -> Result<(Interface, WifiDevice<'d>, WifiController<'d>, SocketSet<'a>), WifiError> { - let socket_set_entries = storage; - - let mut mac = [0u8; 6]; - match mode.is_ap() { - true => get_ap_mac(&mut mac), - false => get_sta_mac(&mut mac), - } +) -> (Interface, SocketSet<'a>) { + let mac = mode.mac_address(); let hw_address = HardwareAddress::Ethernet(EthernetAddress::from_bytes(&mac)); - let (mut device, controller) = crate::wifi::new_with_mode(inited, device, mode)?; - let config = Config::new(hw_address); let iface = Interface::new( config, - &mut device, + device, Instant::from_millis(current_millis() as i64), ); - let mut socket_set = SocketSet::new(socket_set_entries); + let mut socket_set = SocketSet::new(storage); - if !mode.is_ap() { + if mode.mode().is_sta() { // only add DHCP client in STA mode let dhcp_socket = Dhcpv4Socket::new(); socket_set.add(dhcp_socket); } + (iface, socket_set) +} + +/// Convenient way to create an `smoltcp` ethernet interface +/// You can use the provided macros to create and pass a suitable backing storage. +pub fn create_network_interface<'a, 'd, MODE: WifiDeviceMode>( + inited: &EspWifiInitialization, + device: impl crate::hal::peripheral::Peripheral

+ 'd, + mode: MODE, + storage: &'a mut [SocketStorage<'a>], +) -> Result< + ( + Interface, + WifiDevice<'d, MODE>, + WifiController<'d>, + SocketSet<'a>, + ), + WifiError, +> { + let (mut device, controller) = crate::wifi::new_with_mode(inited, device, mode)?; + + let (iface, socket_set) = setup_iface(&mut device, mode, storage); + Ok((iface, device, controller, socket_set)) } + +pub struct ApStaInterface<'a, 'd> { + pub ap_interface: Interface, + pub sta_interface: Interface, + pub ap_device: WifiDevice<'d, WifiApDevice>, + pub sta_device: WifiDevice<'d, WifiStaDevice>, + pub controller: WifiController<'d>, + pub ap_socket_set: SocketSet<'a>, + pub sta_socket_set: SocketSet<'a>, +} + +pub fn create_ap_sta_network_interface<'a, 'd>( + inited: &EspWifiInitialization, + device: impl crate::hal::peripheral::Peripheral

+ 'd, + ap_storage: &'a mut [SocketStorage<'a>], + sta_storage: &'a mut [SocketStorage<'a>], +) -> Result, WifiError> { + let (mut ap_device, mut sta_device, controller) = crate::wifi::new_ap_sta(inited, device)?; + + let (ap_interface, ap_socket_set) = setup_iface(&mut ap_device, WifiApDevice, ap_storage); + let (sta_interface, sta_socket_set) = setup_iface(&mut sta_device, WifiStaDevice, sta_storage); + + Ok(ApStaInterface { + ap_interface, + sta_interface, + ap_device, + sta_device, + controller, + ap_socket_set, + sta_socket_set, + }) +} diff --git a/esp-wifi/src/wifi_interface.rs b/esp-wifi/src/wifi_interface.rs index 2a6e90e5..f1853206 100644 --- a/esp-wifi/src/wifi_interface.rs +++ b/esp-wifi/src/wifi_interface.rs @@ -12,15 +12,15 @@ use smoltcp::time::Instant; use smoltcp::wire::{DnsQueryType, IpAddress, IpCidr, IpEndpoint, Ipv4Address}; use crate::current_millis; -use crate::wifi::{get_ap_mac, get_sta_mac, WifiDevice, WifiMode}; +use crate::wifi::{WifiDevice, WifiDeviceMode}; use core::borrow::BorrowMut; /// Non-async TCP/IP network stack /// /// Mostly a convenience wrapper for `smoltcp` -pub struct WifiStack<'a> { - device: RefCell>, // TODO allow non static lifetime +pub struct WifiStack<'a, MODE: WifiDeviceMode> { + device: RefCell>, // TODO allow non static lifetime network_interface: RefCell, sockets: RefCell>, current_millis_fn: fn() -> u64, @@ -31,13 +31,13 @@ pub struct WifiStack<'a> { dns_socket_handle: RefCell>, } -impl<'a> WifiStack<'a> { +impl<'a, MODE: WifiDeviceMode> WifiStack<'a, MODE> { pub fn new( network_interface: Interface, - device: WifiDevice<'static>, // TODO relax this lifetime requirement + device: WifiDevice<'static, MODE>, // TODO relax this lifetime requirement mut sockets: SocketSet<'a>, current_millis_fn: fn() -> u64, - ) -> WifiStack<'a> { + ) -> WifiStack<'a, MODE> { let mut dhcp_socket_handle: Option = None; let mut dns_socket_handle: Option = None; @@ -76,12 +76,7 @@ impl<'a> WifiStack<'a> { &self, conf: &ipv4::Configuration, ) -> Result<(), WifiStackError> { - let mut mac = [0u8; 6]; - match self.device.borrow().get_wifi_mode() { - Ok(WifiMode::Sta) => get_sta_mac(&mut mac), - Ok(WifiMode::Ap) => get_ap_mac(&mut mac), - _ => (), - } + let mac = self.device.borrow().mac_address(); let hw_address = smoltcp::wire::HardwareAddress::Ethernet( smoltcp::wire::EthernetAddress::from_bytes(&mac), ); @@ -240,7 +235,7 @@ impl<'a> WifiStack<'a> { &'s self, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8], - ) -> Socket<'s, 'a> + ) -> Socket<'s, 'a, MODE> where 'a: 's, { @@ -265,7 +260,7 @@ impl<'a> WifiStack<'a> { rx_buffer: &'a mut [u8], tx_meta: &'a mut [smoltcp::socket::udp::PacketMetadata], tx_buffer: &'a mut [u8], - ) -> UdpSocket<'s, 'a> + ) -> UdpSocket<'s, 'a, MODE> where 'a: 's, { @@ -412,7 +407,7 @@ impl<'a> WifiStack<'a> { } #[allow(unused)] - fn with(&self, f: impl FnOnce(&Interface, &WifiDevice, &SocketSet<'a>) -> R) -> R { + fn with(&self, f: impl FnOnce(&Interface, &WifiDevice, &SocketSet<'a>) -> R) -> R { f( &self.network_interface.borrow(), &self.device.borrow(), @@ -422,7 +417,7 @@ impl<'a> WifiStack<'a> { fn with_mut( &self, - f: impl FnOnce(&mut Interface, &mut WifiDevice, &mut SocketSet<'a>) -> R, + f: impl FnOnce(&mut Interface, &mut WifiDevice, &mut SocketSet<'a>) -> R, ) -> R { f( &mut self.network_interface.borrow_mut(), @@ -456,7 +451,7 @@ pub fn timestamp() -> Instant { Instant::from_millis(current_millis() as i64) } -impl<'a> ipv4::Interface for WifiStack<'a> { +impl ipv4::Interface for WifiStack<'_, MODE> { type Error = WifiStackError; fn get_iface_configuration(&self) -> Result { @@ -477,12 +472,12 @@ impl<'a> ipv4::Interface for WifiStack<'a> { } /// A TCP socket -pub struct Socket<'s, 'n: 's> { +pub struct Socket<'s, 'n: 's, MODE: WifiDeviceMode> { socket_handle: SocketHandle, - network: &'s WifiStack<'n>, + network: &'s WifiStack<'n, MODE>, } -impl<'s, 'n: 's> Socket<'s, 'n> { +impl<'s, 'n: 's, MODE: WifiDeviceMode> Socket<'s, 'n, MODE> { /// Connect the socket pub fn open<'i>(&'i mut self, addr: IpAddress, port: u16) -> Result<(), IoError> where @@ -612,7 +607,7 @@ impl<'s, 'n: 's> Socket<'s, 'n> { } } -impl<'s, 'n: 's> Drop for Socket<'s, 'n> { +impl<'s, 'n: 's, MODE: WifiDeviceMode> Drop for Socket<'s, 'n, MODE> { fn drop(&mut self) { self.network .with_mut(|_interface, _device, sockets| sockets.remove(self.socket_handle)); @@ -640,11 +635,11 @@ impl embedded_io::Error for IoError { } } -impl<'s, 'n: 's> ErrorType for Socket<'s, 'n> { +impl<'s, 'n: 's, MODE: WifiDeviceMode> ErrorType for Socket<'s, 'n, MODE> { type Error = IoError; } -impl<'s, 'n: 's> Read for Socket<'s, 'n> { +impl<'s, 'n: 's, MODE: WifiDeviceMode> Read for Socket<'s, 'n, MODE> { fn read(&mut self, buf: &mut [u8]) -> Result { self.network.with_mut(|interface, device, sockets| { use smoltcp::socket::tcp::RecvError; @@ -664,7 +659,7 @@ impl<'s, 'n: 's> Read for Socket<'s, 'n> { } } -impl<'s, 'n: 's> Write for Socket<'s, 'n> { +impl<'s, 'n: 's, MODE: WifiDeviceMode> Write for Socket<'s, 'n, MODE> { fn write(&mut self, buf: &[u8]) -> Result { loop { let (may_send, is_open, can_send) = @@ -729,12 +724,12 @@ impl<'s, 'n: 's> Write for Socket<'s, 'n> { } /// A UDP socket -pub struct UdpSocket<'s, 'n: 's> { +pub struct UdpSocket<'s, 'n: 's, MODE: WifiDeviceMode> { socket_handle: SocketHandle, - network: &'s WifiStack<'n>, + network: &'s WifiStack<'n, MODE>, } -impl<'s, 'n: 's> UdpSocket<'s, 'n> { +impl<'s, 'n: 's, MODE: WifiDeviceMode> UdpSocket<'s, 'n, MODE> { /// Binds the socket to the given port pub fn bind<'i>(&'i mut self, port: u16) -> Result<(), IoError> where @@ -862,7 +857,7 @@ impl<'s, 'n: 's> UdpSocket<'s, 'n> { } } -impl<'s, 'n: 's> Drop for UdpSocket<'s, 'n> { +impl<'s, 'n: 's, MODE: WifiDeviceMode> Drop for UdpSocket<'s, 'n, MODE> { fn drop(&mut self) { self.network .with_mut(|_, _, sockets| sockets.borrow_mut().remove(self.socket_handle));