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/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/embassy_access_point_with_sta.rs b/esp-wifi/examples/embassy_access_point_with_sta.rs new file mode 100644 index 00000000..4c4fbe43 --- /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; 4096]; + let mut ap_tx_buffer = [0; 4096]; + + 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; 4096]; + let mut sta_tx_buffer = [0; 4096]; + + 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/src/wifi/utils.rs b/esp-wifi/src/wifi/utils.rs index 7c65ae03..5a3af24c 100644 --- a/esp-wifi/src/wifi/utils.rs +++ b/esp-wifi/src/wifi/utils.rs @@ -71,10 +71,9 @@ pub struct ApStaInterface<'a, 'd> { pub sta_socket_set: SocketSet<'a>, } -pub fn create_ap_sta_network_interface<'a, 'd, MODE: WifiDeviceMode>( +pub fn create_ap_sta_network_interface<'a, 'd>( inited: &EspWifiInitialization, device: impl crate::hal::peripheral::Peripheral

+ 'd, - _mode: MODE, ap_storage: &'a mut [SocketStorage<'a>], sta_storage: &'a mut [SocketStorage<'a>], ) -> Result, WifiError> {