diff --git a/README.md b/README.md index 460f87f4..f2118ba2 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,19 @@ If a cell contains am em dash (—) this means that the particular feature i | ESP32-C2 | `CARGO_PROFILE_RELEASE_LTO=false cargo +nightly run --example embassy_esp_now --release --target riscv32imc-unknown-none-elf --features "esp32c2,esp32c2-async,esp-now"` | | ESP32-C3 | `cargo +nightly run --example embassy_esp_now --release --target riscv32imc-unknown-none-elf --features "esp32c3,esp32c3-async,esp-now"` | | ESP32-S2 | `cargo +esp run --example embassy_esp_now --release --target xtensa-esp32s2-none-elf --features "esp32s2,esp32s2-async,esp-now"` | -| ESP32-S3 | `cargo +esp run --example embassy_esp_now --release --target xtensa-esp32s3-none-elf --features "esp32s3,esp32s3-async,esp-now"` | +| ESP32-S3 | `cargo +esp run --example embassy_esp_now --release --target xtensa-esp32s3-none-elf --features "esp32s3,esp32s3-async,esp-now"` + +### embassy_dhcp + +- Read and Write to sockets over WiFi asyncronously using embassy-executor. + +| Chip | Command | +| :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ESP32 | `cargo +esp run --example embassy_dhcp --release --target xtensa-esp32-none-elf --features "esp32,esp32-async,embedded-svc,wifi,embassy-net"` | +| ESP32-C2 | `CARGO_PROFILE_RELEASE_LTO=false cargo +nightly run --example embassy_dhcp --release --target riscv32imc-unknown-none-elf --features "esp32c2,esp32c2-async,embedded-svc,wifi,embassy-net"` | +| ESP32-C3 | `cargo +nightly run --example embassy_dhcp --release --target riscv32imc-unknown-none-elf --features "esp32c3,esp32c3-async,embedded-svc,wifi,embassy-net"` | +| ESP32-S2 | `cargo +esp run --example embassy_dhcp --release --target xtensa-esp32s2-none-elf --features "esp32s2,esp32s2-async,embedded-svc,wifi,embassy-net"` | +| ESP32-S3 | `cargo +esp run --example embassy_dhcp --release --target xtensa-esp32s3-none-elf --features "esp32s3,esp32s3-async,embedded-svc,wifi,embassy-net"` | ## Features diff --git a/esp-wifi/Cargo.toml b/esp-wifi/Cargo.toml index d8298561..a1b50729 100644 --- a/esp-wifi/Cargo.toml +++ b/esp-wifi/Cargo.toml @@ -26,20 +26,22 @@ log = "0.4.17" embedded-svc = { version = "0.23.1", default-features = false, features = [], optional = true } enumset = { version = "1", default-features = false, optional = true } linked_list_allocator = { version = "0.10.3", default-features = false, features = ["const_mut_refs"] } -embedded-io = "0.3.1" +embedded-io = "0.4.0" fugit = "0.3.6" heapless = { version = "0.7.14", default-features = false } num-derive = { version = "0.3", features = ["full-syntax"] } num-traits = { version = "0.2", default-features = false } esp-wifi-sys = { version = "0.1.0", path = "../esp-wifi-sys" } embassy-sync = { version = "0.1.0", optional = true } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "26474ce6eb759e5add1c137f3417845e0797df3a", features = ["nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"], optional = true } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "26474ce6eb759e5add1c137f3417845e0797df3a", optional = true } [build-dependencies] riscv-target = { version = "0.1.2", optional = true } [dev-dependencies] -bleps = { git = "https://github.com/bjoernQ/bleps", package = "bleps", rev = "5ce5ca139fe85c0120e9c2f9607d165d1c32d725" } -bleps-macros = { git = "https://github.com/bjoernQ/bleps", package = "bleps-macros", rev = "5ce5ca139fe85c0120e9c2f9607d165d1c32d725" } +bleps = { git = "https://github.com/bjoernQ/bleps", package = "bleps", rev = "33fde67257bfbc6c0aebf7649fd302c82ed94c64" } +bleps-macros = { git = "https://github.com/bjoernQ/bleps", package = "bleps-macros", rev = "33fde67257bfbc6c0aebf7649fd302c82ed94c64" } embassy-executor = { package = "embassy-executor", git = "https://github.com/embassy-rs/embassy/", rev = "cd9a65b", features = ["nightly", "integrated-timers"] } embassy-time = { version = "0.1.0", features = ["nightly"] } embassy-futures = "0.1.0" @@ -65,16 +67,24 @@ xtensa-atomic-emulation-trap = "0.3.0" [features] default = [ "utils" ] + +# chip features esp32c3 = [ "riscv-target", "riscv", "riscv-rt", "esp32c3-hal", "dep:esp32c3", "esp-wifi-sys/esp32c3" ] esp32c2 = [ "riscv-target", "riscv", "riscv-rt", "esp32c2-hal", "dep:esp32c2", "esp-wifi-sys/esp32c2" ] esp32 = [ "esp32-hal", "xtensa-lx-rt/esp32", "xtensa-lx/esp32", "esp-wifi-sys/esp32" ] esp32s3 = [ "esp32s3-hal", "xtensa-lx-rt/esp32s3", "xtensa-lx/esp32s3", "esp-wifi-sys/esp32s3" ] esp32s2 = [ "esp32s2-hal", "xtensa-lx-rt/esp32s2", "xtensa-lx/esp32s2", "esp-wifi-sys/esp32s2" ] + +# async features esp32c3-async = [ "esp32c3-hal/embassy", "esp32c3-hal/embassy-time-timg0", "async" ] esp32c2-async = [ "esp32c2-hal/embassy", "esp32c2-hal/embassy-time-timg0", "async" ] esp32-async = [ "esp32-hal/embassy", "esp32-hal/embassy-time-timg0", "async" ] esp32s2-async = [ "esp32s2-hal/embassy", "esp32s2-hal/embassy-time-timg0", "async" ] esp32s3-async = [ "esp32s3-hal/embassy", "esp32s3-hal/embassy-time-timg0", "async" ] +async = [ "dep:embassy-sync", "embedded-io/async"] +embassy-net = ["dep:embassy-net", "dep:embassy-net-driver", "async"] + +# misc features wifi-logs = [] dump-packets = [] utils = [] @@ -84,5 +94,4 @@ wifi = [] ble = [ "esp32-hal?/bluetooth" ] phy-enable-usb = [] ps-min-modem = [] -esp-now = [ "wifi", "embedded-svc" ] -async = [ "dep:embassy-sync"] +esp-now = [ "wifi", "embedded-svc" ] \ No newline at end of file diff --git a/esp-wifi/examples/embassy_dhcp.rs b/esp-wifi/examples/embassy_dhcp.rs new file mode 100644 index 00000000..94fb94fc --- /dev/null +++ b/esp-wifi/examples/embassy_dhcp.rs @@ -0,0 +1,219 @@ +#![no_std] +#![no_main] +#![feature(c_variadic)] +#![feature(const_mut_refs)] +#![feature(type_alias_impl_trait)] + +use embassy_executor::_export::StaticCell; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Stack, StackResources}; +#[cfg(feature = "esp32")] +use esp32_hal as hal; +#[cfg(feature = "esp32c2")] +use esp32c2_hal as hal; +#[cfg(feature = "esp32c3")] +use esp32c3_hal as hal; +#[cfg(feature = "esp32s2")] +use esp32s2_hal as hal; +#[cfg(feature = "esp32s3")] +use esp32s3_hal as hal; + +use embassy_executor::Executor; +use embassy_time::{Duration, Timer}; +use embedded_svc::wifi::{AccessPointInfo, ClientConfiguration, Configuration, Wifi}; +use esp_backtrace as _; +use esp_println::logger::init_logger; +use esp_println::println; +use esp_wifi::initialize; +use esp_wifi::wifi::{WifiDevice, WifiError}; +use hal::clock::{ClockControl, CpuClock}; +use hal::Rng; +use hal::{embassy, peripherals::Peripherals, prelude::*, timer::TimerGroup, Rtc}; + +#[cfg(any(feature = "esp32c3", feature = "esp32c2"))] +use hal::system::SystemExt; + +#[cfg(any(feature = "esp32c3", feature = "esp32c2"))] +use riscv_rt::entry; +#[cfg(any(feature = "esp32", feature = "esp32s3", feature = "esp32s2"))] +use xtensa_lx_rt::entry; + +const SSID: &str = env!("SSID"); +const PASSWORD: &str = env!("PASSWORD"); + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + init_logger(log::LevelFilter::Info); + esp_wifi::init_heap(); + + let peripherals = Peripherals::take(); + + #[cfg(not(feature = "esp32"))] + let system = peripherals.SYSTEM.split(); + #[cfg(feature = "esp32")] + let system = peripherals.DPORT.split(); + + #[cfg(feature = "esp32c3")] + let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock160MHz).freeze(); + #[cfg(feature = "esp32c2")] + let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock120MHz).freeze(); + #[cfg(any(feature = "esp32", feature = "esp32s3", feature = "esp32s2"))] + let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock240MHz).freeze(); + + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable watchdog timers + #[cfg(not(any(feature = "esp32", feature = "esp32s2")))] + rtc.swd.disable(); + + rtc.rwdt.disable(); + + #[cfg(any(feature = "esp32c3", feature = "esp32c2"))] + { + use hal::systimer::SystemTimer; + let syst = SystemTimer::new(peripherals.SYSTIMER); + initialize(syst.alarm0, Rng::new(peripherals.RNG), &clocks).unwrap(); + } + #[cfg(any(feature = "esp32", feature = "esp32s3", feature = "esp32s2"))] + { + use hal::timer::TimerGroup; + let timg1 = TimerGroup::new(peripherals.TIMG1, &clocks); + initialize(timg1.timer0, Rng::new(peripherals.RNG), &clocks).unwrap(); + } + + let mut wifi_interface = WifiDevice::new(); + + println!("is wifi started: {:?}", wifi_interface.is_started()); + + println!("Start Wifi Scan"); + let res: Result<(heapless::Vec, usize), WifiError> = + wifi_interface.scan_n(); + if let Ok((res, _count)) = res { + for ap in res { + println!("{:?}", ap); + } + } + + println!("Call wifi_connect"); + let client_config = Configuration::Client(ClientConfiguration { + ssid: SSID.into(), + password: PASSWORD.into(), + ..Default::default() + }); + let res = wifi_interface.set_configuration(&client_config); + println!("wifi_set_configuration returned {:?}", res); + + println!("{:?}", wifi_interface.get_capabilities()); + println!("wifi_connect {:?}", wifi_interface.connect()); + + // wait to get connected + println!("Wait to get connected"); + loop { + let res = wifi_interface.is_connected(); + match res { + Ok(connected) => { + if connected { + break; + } + } + Err(err) => { + println!("{:?}", err); + loop {} + } + } + } + println!("{:?}", wifi_interface.is_connected()); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + embassy::init(&clocks, timer_group0.timer0); + + let config = Config::Dhcp(Default::default()); + + let seed = 1234; // very random, very secure seed + + // Init network stack + let stack = &*singleton!(Stack::new( + wifi_interface, + config, + singleton!(StackResources::<3>::new()), + seed + )); + + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(net_task(&stack)).ok(); + spawner.spawn(task(&stack)).ok(); + }); +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) { + stack.run().await +} + +#[embassy_executor::task] +async fn task(stack: &'static Stack) { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + + println!("Waiting to get IP address..."); + loop { + if let Some(config) = stack.config() { + println!("Got IP: {}", config.address); + break; + } + Timer::after(Duration::from_millis(500)).await; + } + + loop { + Timer::after(Duration::from_millis(1_000)).await; + + let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(142, 250, 185, 115), 80); + println!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + println!("connect error: {:?}", e); + continue; + } + println!("connected!"); + let mut buf = [0; 1024]; + loop { + use embedded_io::asynch::Write; + let r = 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!("write error: {:?}", e); + break; + } + let n = match socket.read(&mut buf).await { + Ok(0) => { + println!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + println!("read error: {:?}", e); + break; + } + }; + println!("{}", core::str::from_utf8(&buf[..n]).unwrap()); + } + Timer::after(Duration::from_millis(1000)).await; + } +} diff --git a/esp-wifi/src/wifi/mod.rs b/esp-wifi/src/wifi/mod.rs index c44caf58..1633424e 100644 --- a/esp-wifi/src/wifi/mod.rs +++ b/esp-wifi/src/wifi/mod.rs @@ -567,6 +567,10 @@ unsafe extern "C" fn recv_cb( let packet = DataFrame::from_bytes(src); queue.enqueue(packet).unwrap(); esp_wifi_internal_free_rx_buffer(eb); + + #[cfg(feature = "embassy-net")] + embassy::RECEIVE_WAKER.wake(); + 0 } else { 1 @@ -805,6 +809,9 @@ pub fn send_data_if_needed() { log::trace!("esp_wifi_internal_tx {}", _res); } } + + #[cfg(feature = "embassy-net")] + embassy::TRANSMIT_WAKER.wake(); }); } @@ -1059,3 +1066,111 @@ macro_rules! esp_wifi_result { } }; } + +#[cfg(feature = "embassy-net")] +pub(crate) mod embassy { + use super::*; + use embassy_net_driver::{Capabilities, Driver, RxToken, TxToken}; + use embassy_sync::waitqueue::AtomicWaker; + + pub(crate) static TRANSMIT_WAKER: AtomicWaker = AtomicWaker::new(); + pub(crate) static RECEIVE_WAKER: AtomicWaker = AtomicWaker::new(); + + impl RxToken for WifiRxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + critical_section::with(|cs| { + let mut queue = DATA_QUEUE_RX.borrow_ref_mut(cs); + + let mut data = queue.dequeue().expect( + "unreachable: transmit()/receive() ensures there is a packet to process", + ); + let buffer = + unsafe { core::slice::from_raw_parts(&data.data as *const u8, data.len) }; + dump_packet_info(&buffer); + f(&mut data.data[..]) + }) + } + } + + impl TxToken for WifiTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let res = critical_section::with(|cs| { + let mut queue = DATA_QUEUE_TX.borrow_ref_mut(cs); + + let mut packet = DataFrame::new(); + packet.len = len; + let res = f(&mut packet.data[..len]); + queue + .enqueue(packet) + .expect("unreachable: transmit()/receive() ensures there is a buffer free"); + res + }); + + send_data_if_needed(); + res + } + } + + impl Driver for WifiDevice { + 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()); + critical_section::with(|cs| { + let rx = DATA_QUEUE_RX.borrow_ref_mut(cs); + let tx = DATA_QUEUE_TX.borrow_ref_mut(cs); + if !rx.is_empty() && !tx.is_full() { + Some((WifiRxToken::default(), WifiTxToken::default())) + } else { + None + } + }) + } + + fn transmit(&mut self, cx: &mut core::task::Context) -> Option> { + TRANSMIT_WAKER.register(cx.waker()); + critical_section::with(|cs| { + let tx = DATA_QUEUE_TX.borrow_ref_mut(cs); + if !tx.is_full() { + Some(WifiTxToken::default()) + } else { + None + } + }) + } + + fn link_state(&mut self, _cx: &mut core::task::Context) -> embassy_net_driver::LinkState { + // TODO once we have an async way of connecting to wifi, use here + // for now just assume the link is up + embassy_net_driver::LinkState::Up + } + + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = 1514; + caps.max_burst_size = Some(1); + caps + } + + fn ethernet_address(&self) -> [u8; 6] { + let mut mac = [0; 6]; + get_sta_mac(&mut mac); + mac + } + } +}