From 86c6ecc1352725ef8fad31a8454bb205759784f3 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 11:14:00 +0000 Subject: [PATCH 01/60] Support for Thread --- .github/configs/sdkconfig.defaults | 2 + Cargo.toml | 2 +- src/lib.rs | 10 + src/netif.rs | 72 +++- src/thread.rs | 539 +++++++++++++++++++++++++++++ 5 files changed, 613 insertions(+), 12 deletions(-) create mode 100644 src/thread.rs diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index 157f3af592e..3dec682b463 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -38,3 +38,5 @@ CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y CONFIG_LWIP_PPP_SUPPORT=y #CONFIG_LWIP_SLIP_SUPPORT=y + +CONFIG_OPENTHREAD_ENABLED=y diff --git a/Cargo.toml b/Cargo.toml index 6873bb2650b..aa081adec79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal" } esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys" } [features] -default = ["std", "binstart"] +default = ["std", "binstart", "experimental"] std = ["alloc", "log/std", "esp-idf-hal/std", "embedded-svc/std", "futures-io"] alloc = ["esp-idf-hal/alloc", "embedded-svc/alloc", "uncased/alloc"] diff --git a/src/lib.rs b/src/lib.rs index bc32b802251..aeeaf77ae9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,16 @@ pub mod ping; pub mod sntp; pub mod sys; pub mod systime; +#[cfg(all( + feature = "alloc", + feature = "experimental", + esp_idf_comp_openthread_enabled, + esp_idf_openthread_enabled, + esp_idf_comp_esp_event_enabled, + esp_idf_comp_nvs_flash_enabled, + esp_idf_comp_vfs_enabled, +))] +pub mod thread; #[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))] pub mod timer; pub mod tls; diff --git a/src/netif.rs b/src/netif.rs index 1d71dddc161..e2d2089b2b3 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -43,6 +43,8 @@ pub enum NetifStack { #[cfg(esp_idf_lwip_slip_support)] /// Serial Line Internet Protocol (SLIP) Slip, + #[cfg(all(esp_idf_comp_openthread_enabled, esp_idf_openthread_enabled,))] + Thread, } impl NetifStack { @@ -70,6 +72,8 @@ impl NetifStack { Self::Ppp => NetifConfiguration::ppp_default_client(), #[cfg(esp_idf_lwip_slip_support)] Self::Slip => NetifConfiguration::slip_default_client(), + #[cfg(all(esp_idf_comp_openthread_enabled, esp_idf_openthread_enabled,))] + Self::Thread => NetifConfiguration::thread_default(), } } @@ -90,6 +94,16 @@ impl NetifStack { #[cfg(esp_idf_esp_wifi_softap_support)] Self::Ap => Some(esp_mac_type_t_ESP_MAC_WIFI_SOFTAP), Self::Eth => Some(esp_mac_type_t_ESP_MAC_ETH), + #[cfg(all(esp_idf_comp_openthread_enabled, esp_idf_openthread_enabled,))] + Self::Thread => { + #[cfg(esp_idf_soc_ieee802154_supported)] + let mac_type = Some(esp_mac_type_t_ESP_MAC_IEEE802154); + + #[cfg(not(esp_idf_soc_ieee802154_supported))] + let mac_type = None; + + mac_type + } #[cfg(any(esp_idf_lwip_slip_support, esp_idf_lwip_ppp_support))] _ => None, } @@ -106,6 +120,8 @@ impl NetifStack { Self::Ppp => _g_esp_netif_netstack_default_ppp, #[cfg(esp_idf_lwip_slip_support)] Self::Slip => _g_esp_netif_netstack_default_slip, + #[cfg(all(esp_idf_comp_openthread_enabled, esp_idf_openthread_enabled,))] + Self::Thread => &g_esp_netif_netstack_default_openthread, } } } @@ -119,7 +135,7 @@ pub struct NetifConfiguration { pub key: heapless::String<32>, pub description: heapless::String<8>, pub route_priority: u32, - pub ip_configuration: ipv4::Configuration, + pub ip_configuration: Option, pub stack: NetifStack, pub custom_mac: Option<[u8; 6]>, } @@ -134,7 +150,7 @@ impl NetifConfiguration { key: "ETH_CL_DEF".try_into().unwrap(), description: "eth".try_into().unwrap(), route_priority: 60, - ip_configuration: ipv4::Configuration::Client(Default::default()), + ip_configuration: Some(ipv4::Configuration::Client(Default::default())), stack: NetifStack::Eth, custom_mac: None, } @@ -148,7 +164,7 @@ impl NetifConfiguration { key: "ETH_RT_DEF".try_into().unwrap(), description: "ethrt".try_into().unwrap(), route_priority: 50, - ip_configuration: ipv4::Configuration::Router(Default::default()), + ip_configuration: Some(ipv4::Configuration::Router(Default::default())), stack: NetifStack::Eth, custom_mac: None, } @@ -163,7 +179,7 @@ impl NetifConfiguration { key: "WIFI_STA_DEF".try_into().unwrap(), description: "sta".try_into().unwrap(), route_priority: 100, - ip_configuration: ipv4::Configuration::Client(Default::default()), + ip_configuration: Some(ipv4::Configuration::Client(Default::default())), stack: NetifStack::Sta, custom_mac: None, } @@ -178,7 +194,7 @@ impl NetifConfiguration { key: "WIFI_AP_DEF".try_into().unwrap(), description: "ap".try_into().unwrap(), route_priority: 10, - ip_configuration: ipv4::Configuration::Router(Default::default()), + ip_configuration: Some(ipv4::Configuration::Router(Default::default())), stack: NetifStack::Ap, custom_mac: None, } @@ -193,7 +209,7 @@ impl NetifConfiguration { key: "PPP_CL_DEF".try_into().unwrap(), description: "ppp".try_into().unwrap(), route_priority: 30, - ip_configuration: ipv4::Configuration::Client(Default::default()), + ip_configuration: Some(ipv4::Configuration::Client(Default::default())), stack: NetifStack::Ppp, custom_mac: None, } @@ -208,7 +224,7 @@ impl NetifConfiguration { key: "PPP_RT_DEF".try_into().unwrap(), description: "ppprt".try_into().unwrap(), route_priority: 20, - ip_configuration: ipv4::Configuration::Router(Default::default()), + ip_configuration: Some(ipv4::Configuration::Router(Default::default())), stack: NetifStack::Ppp, custom_mac: None, } @@ -223,7 +239,7 @@ impl NetifConfiguration { key: "SLIP_CL_DEF".try_into().unwrap(), description: "slip".try_into().unwrap(), route_priority: 35, - ip_configuration: ipv4::Configuration::Client(Default::default()), + ip_configuration: Some(ipv4::Configuration::Client(Default::default())), stack: NetifStack::Slip, custom_mac: None, } @@ -238,11 +254,26 @@ impl NetifConfiguration { key: "SLIP_RT_DEF".try_into().unwrap(), description: "sliprt".try_into().unwrap(), route_priority: 25, - ip_configuration: ipv4::Configuration::Router(Default::default()), + ip_configuration: Some(ipv4::Configuration::Router(Default::default())), stack: NetifStack::Slip, custom_mac: None, } } + + #[cfg(all(esp_idf_comp_openthread_enabled, esp_idf_openthread_enabled,))] + pub fn thread_default() -> Self { + Self { + flags: 0, + got_ip_event_id: None, + lost_ip_event_id: None, + key: "OT_DEF".try_into().unwrap(), + description: "thread".try_into().unwrap(), + route_priority: 15, + ip_configuration: None, + stack: NetifStack::Thread, + custom_mac: None, + } + } } static INITALIZED: mutex::Mutex = mutex::Mutex::new(false); @@ -286,7 +317,7 @@ impl EspNetif { let (mut esp_inherent_config, ip_info, dhcps, dns, secondary_dns, hostname) = match conf .ip_configuration { - ipv4::Configuration::Client(ref ip_conf) => ( + Some(ipv4::Configuration::Client(ref ip_conf)) => ( esp_netif_inherent_config_t { flags: conf.flags | (if matches!(ip_conf, ipv4::ClientConfiguration::DHCP(_)) { @@ -326,7 +357,7 @@ impl EspNetif { ipv4::ClientConfiguration::Fixed(_) => None, }, ), - ipv4::Configuration::Router(ref ip_conf) => ( + Some(ipv4::Configuration::Router(ref ip_conf)) => ( esp_netif_inherent_config_t { flags: conf.flags | (if ip_conf.dhcp_enabled { @@ -355,6 +386,25 @@ impl EspNetif { None, /* For APs, ESP-IDF supports setting a primary DNS only ip_conf.secondary_dns */ None, ), + None => ( + esp_netif_inherent_config_t { + flags: conf.flags | esp_netif_flags_ESP_NETIF_FLAG_AUTOUP, + mac: initial_mac, + ip_info: ptr::null(), + get_ip_event: conf.got_ip_event_id.map(NonZeroU32::get).unwrap_or(0), + lost_ip_event: conf.lost_ip_event_id.map(NonZeroU32::get).unwrap_or(0), + if_key: c_if_key.as_c_str().as_ptr() as _, + if_desc: c_if_description.as_c_str().as_ptr() as _, + route_prio: conf.route_priority as _, + #[cfg(not(esp_idf_version_major = "4"))] + bridge_info: ptr::null_mut(), + }, + None, + false, + None, + None, + None, + ), }; if let Some(ip_info) = ip_info.as_ref() { diff --git a/src/thread.rs b/src/thread.rs new file mode 100644 index 00000000000..732b83616f2 --- /dev/null +++ b/src/thread.rs @@ -0,0 +1,539 @@ +use core::fmt::Debug; +use core::marker::PhantomData; + +use esp_idf_hal::task::CriticalSection; +use log::debug; + +use crate::eventloop::EspSystemEventLoop; +use crate::hal::gpio::{InputPin, OutputPin}; +use crate::hal::peripheral::Peripheral; +use crate::hal::spi::Spi; +use crate::hal::uart::Uart; +#[cfg(esp_idf_comp_esp_netif_enabled)] +use crate::handle::RawHandle; +use crate::io::vfs::MountedEventfs; +#[cfg(esp_idf_comp_esp_netif_enabled)] +use crate::netif::*; +use crate::nvs::EspDefaultNvsPartition; +use crate::sys::*; + +extern crate alloc; + +use alloc::sync::Arc; + +/// The driver will operate in Radio Co-Processor mode +/// +/// The chip needs to be connected via UART or SPI to the host +#[cfg(esp_idf_soc_ieee802154_supported)] +#[derive(Debug)] +pub struct RCP; + +/// The driver will operate as a host +/// +/// This means that - unless the chip has a native Thread suppoort - +/// it needs to be connected via UART or SPI to another chip which does have +/// native Thread support and which is configured to operate in RCP mode +pub struct Host(esp_openthread_platform_config_t); + +impl Debug for Host { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Host").finish() + } +} + +#[cfg(all(esp32c2, esp_idf_xtal_freq_26))] +const BAUD_RATE: u32 = 74880; + +#[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))] +const BAUD_RATE: u32 = 115200; + +static CS: CriticalSection = CriticalSection::new(); + +/// This struct provides a safe wrapper over the ESP IDF Thread C driver. +/// +/// The driver works on Layer 2 (Data Link) in the OSI model, in that it provides +/// facilities for sending and receiving ethernet packets over the Thread radio. +/// +/// For most use cases, utilizing `EspThread` - which provides a networking (IP) +/// layer as well - should be preferred. Using `ThreadDriver` directly is beneficial +/// only when one would like to utilize a custom, non-STD network stack like `smoltcp`. +pub struct ThreadDriver<'d, T> { + mode: T, + _mounted_event_fs: Arc, + //_subscription: EspSubscription<'static, System>, + _nvs: EspDefaultNvsPartition, + _p: PhantomData<&'d mut ()>, +} + +impl<'d> ThreadDriver<'d, Host> { + #[cfg(esp_idf_soc_ieee802154_supported)] + pub fn new( + _modem: impl Peripheral

+ 'd, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, + port_config: esp_openthread_port_config_t { + storage_partition_name: b"TODO\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }, + }; + esp!(unsafe { esp_openthread_init(&cfg) })?; + + debug!("Driver initialized"); + + Ok(Self { + mode: Host(cfg), + _mounted_event_fs: mounted_event_fs, + _nvs: nvs, + _p: PhantomData, + }) + } + + #[allow(clippy::too_many_arguments)] + pub fn new_spi( + _spi: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + cs: Option + 'd>, + intr: Option + 'd>, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + crate::hal::into_ref!(mosi, miso, sclk); + + let cs_pin = if let Some(cs) = cs { + crate::hal::into_ref!(cs); + + cs.pin() as _ + } else { + -1 + }; + + let intr_pin = if let Some(intr) = intr { + crate::hal::into_ref!(intr); + + intr.pin() as _ + } else { + -1 + }; + + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_SPI_RCP, + __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 { + radio_spi_config: esp_openthread_spi_host_config_t { + host_device: S::device() as _, + dma_channel: spi_common_dma_t_SPI_DMA_CH_AUTO, + spi_interface: spi_bus_config_t { + __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 { + mosi_io_num: mosi.pin() as _, + }, + __bindgen_anon_2: spi_bus_config_t__bindgen_ty_2 { + miso_io_num: miso.pin() as _, + }, + sclk_io_num: sclk.pin() as _, + ..Default::default() + }, + spi_device: spi_device_interface_config_t { + spics_io_num: cs_pin as _, + cs_ena_pretrans: 2, + input_delay_ns: 100, + mode: 0, + clock_speed_hz: 2500 * 1000, + queue_size: 5, + ..Default::default() + }, + intr_pin, + }, + }, + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, + port_config: esp_openthread_port_config_t { + storage_partition_name: b"TODO\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }, + }; + esp!(unsafe { esp_openthread_init(&cfg) })?; + + debug!("Driver initialized"); + + Ok(Self { + mode: Host(cfg), + _mounted_event_fs: mounted_event_fs, + _nvs: nvs, + _p: PhantomData, + }) + } + + pub fn new_uart( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + crate::hal::into_ref!(rx, tx); + + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, + __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 { + radio_uart_config: esp_openthread_uart_config_t { + port: U::port() as _, + uart_config: uart_config_t { + baud_rate: BAUD_RATE as _, + data_bits: 8, + parity: 0, + stop_bits: 1, + flow_ctrl: 0, + rx_flow_ctrl_thresh: 0, + ..Default::default() + }, + rx_pin: rx.pin() as _, + tx_pin: tx.pin() as _, + }, + }, + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, + port_config: esp_openthread_port_config_t { + storage_partition_name: b"TODO\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }, + }; + esp!(unsafe { esp_openthread_init(&cfg) })?; + + debug!("Driver initialized"); + + Ok(Self { + mode: Host(cfg), + _mounted_event_fs: mounted_event_fs, + _nvs: nvs, + _p: PhantomData, + }) + } +} + +#[cfg(esp_idf_soc_ieee802154_supported)] +impl<'d> ThreadDriver<'d, RCP> { + pub fn new_rcp_spi( + _modem: impl Peripheral

+ 'd, + _spi: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + cs: Option + 'd>, + intr: Option + 'd>, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + crate::hal::into_ref!(mosi, miso, sclk); + + let cs_pin = if let Some(cs) = cs { + crate::hal::into_ref!(cs); + + cs.pin() as _ + } else { + -1 + }; + + let intr_pin = if let Some(intr) = intr { + crate::hal::into_ref!(intr); + + intr.pin() as _ + } else { + -1 + }; + + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, + __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 { + spi_slave_config: esp_openthread_spi_slave_config_t { + host_device: S::device() as _, + bus_config: spi_bus_config_t { + __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 { + mosi_io_num: mosi.pin() as _, + }, + __bindgen_anon_2: spi_bus_config_t__bindgen_ty_2 { + miso_io_num: miso.pin() as _, + }, + sclk_io_num: sclk.pin() as _, + ..Default::default() + }, + slave_config: spi_slave_interface_config_t { + spics_io_num: cs_pin as _, + ..Default::default() + }, + intr_pin, + }, + }, + }, + port_config: esp_openthread_port_config_t { + storage_partition_name: b"TODO\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }, + }; + esp!(unsafe { esp_openthread_init(&cfg) })?; + + debug!("Driver initialized"); + + Ok(Self { + mode: RCP, + _mounted_event_fs: mounted_event_fs, + _nvs: nvs, + _p: PhantomData, + }) + } + + pub fn new_rcp_uart( + _modem: impl Peripheral

+ 'd, + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + crate::hal::into_ref!(rx, tx); + + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, + __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 { + host_uart_config: esp_openthread_uart_config_t { + port: U::port() as _, + uart_config: uart_config_t { + baud_rate: BAUD_RATE as _, + data_bits: 8, + parity: 0, + stop_bits: 1, + flow_ctrl: 0, + rx_flow_ctrl_thresh: 0, + ..Default::default() + }, + rx_pin: rx.pin() as _, + tx_pin: tx.pin() as _, + }, + }, + }, + port_config: esp_openthread_port_config_t { + storage_partition_name: b"TODO\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }, + }; + esp!(unsafe { esp_openthread_init(&cfg) })?; + + debug!("Driver initialized"); + + Ok(Self { + mode: RCP, + _mounted_event_fs: mounted_event_fs, + _nvs: nvs, + _p: PhantomData, + }) + } +} + +impl<'d, T> ThreadDriver<'d, T> { + /// Run the Thread stack + /// + /// The current thread would block until the stack is running + pub fn run(&self) -> Result<(), EspError> { + // TODO: Figure out how to stop running + + let _cs = CS.enter(); + + esp!(unsafe { esp_openthread_launch_mainloop() }) + } +} + +impl<'d, T> Drop for ThreadDriver<'d, T> { + fn drop(&mut self) { + esp!(unsafe { esp_openthread_deinit() }).unwrap(); + } +} + +/// `EspWifi` wraps a `WifiDriver` Data Link layer instance, and binds the OSI +/// Layer 3 (network) facilities of ESP IDF to it. +/// +/// In other words, it connects the ESP IDF AP and STA Netif interfaces to the +/// Wifi driver. This allows users to utilize the Rust STD APIs for working with +/// TCP and UDP sockets. +/// +/// This struct should be the default option for a Wifi driver in all use cases +/// but the niche one where bypassing the ESP IDF Netif and lwIP stacks is +/// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the +/// ESP IDF Wifi radio. +#[cfg(esp_idf_comp_esp_netif_enabled)] +pub struct EspThread<'d> { + netif: EspNetif, + driver: ThreadDriver<'d, Host>, +} + +#[cfg(esp_idf_comp_esp_netif_enabled)] +impl<'d> EspThread<'d> { + #[cfg(esp_idf_soc_ieee802154_supported)] + pub fn new( + modem: impl Peripheral

+ 'd, + sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + Self::wrap(ThreadDriver::new(modem, sysloop, nvs, mounted_event_fs)?) + } + + #[allow(clippy::too_many_arguments)] + pub fn new_spi( + _spi: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + cs: Option + 'd>, + intr: Option + 'd>, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + Self::wrap(ThreadDriver::new_spi( + _spi, + mosi, + miso, + sclk, + cs, + intr, + _sysloop, + nvs, + mounted_event_fs, + )?) + } + + pub fn new_uart( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + Self::wrap(ThreadDriver::new_uart( + _uart, + rx, + tx, + _sysloop, + nvs, + mounted_event_fs, + )?) + } + + pub fn wrap(driver: ThreadDriver<'d, Host>) -> Result { + Self::wrap_all(driver, EspNetif::new(NetifStack::Thread)?) + } + + pub fn wrap_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result { + let mut this = Self { driver, netif }; + + this.attach_netif()?; + + Ok(this) + } + + /// Replaces the network interface with the provided one and returns the + /// existing network interface. + pub fn swap_netif(&mut self, netif: EspNetif) -> Result { + self.detach_netif()?; + + let old = core::mem::replace(&mut self.netif, netif); + + self.attach_netif()?; + + Ok(old) + } + + /// Returns the underlying [`ThreadDriver`] + pub fn driver(&self) -> &ThreadDriver<'d, Host> { + &self.driver + } + + /// Returns the underlying [`ThreadDriver`], as mutable + pub fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { + &mut self.driver + } + + /// Returns the underlying [`EspNetif`] + pub fn netif(&self) -> &EspNetif { + &self.netif + } + + /// Returns the underlying [`EspNetif`] as mutable + pub fn netif_mut(&mut self) -> &mut EspNetif { + &mut self.netif + } + + /// Run the Thread stack + pub fn run(&self) -> Result<(), EspError> { + self.driver.run() + } + + fn attach_netif(&mut self) -> Result<(), EspError> { + esp!(unsafe { + esp_netif_attach( + self.netif.handle() as *mut _, + esp_openthread_netif_glue_init(&self.driver.mode.0), + ) + })?; + + Ok(()) + } + + fn detach_netif(&mut self) -> Result<(), EspError> { + unsafe { + esp_openthread_netif_glue_deinit(); + } + + Ok(()) + } +} + +impl Drop for EspThread<'_> { + fn drop(&mut self) { + let _ = self.detach_netif(); + } +} From 1ff57f20990ae78867ebb37eddb5f31f33f9a436 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 11:27:18 +0000 Subject: [PATCH 02/60] Fix docu --- src/thread.rs | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 732b83616f2..93c69825656 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -57,6 +57,13 @@ static CS: CriticalSection = CriticalSection::new(); /// For most use cases, utilizing `EspThread` - which provides a networking (IP) /// layer as well - should be preferred. Using `ThreadDriver` directly is beneficial /// only when one would like to utilize a custom, non-STD network stack like `smoltcp`. +/// +/// The driver can work in two modes: +/// - RCP (Radio Co-Processor) mode: The driver operates as a co-processor to the host, +/// which is expected to be another chip connected to ours via SPI or UART. This is +/// of course only supported with MCUs that do have a Thread radio, like esp32c2 and esp32c6 +/// - Host mode: The driver operates as a host, and if the chip does not have a Thread radio +/// it has to be connected via SPI or USB to a chip which runs the Thread stack in RCP mode pub struct ThreadDriver<'d, T> { mode: T, _mounted_event_fs: Arc, @@ -66,6 +73,8 @@ pub struct ThreadDriver<'d, T> { } impl<'d> ThreadDriver<'d, Host> { + /// Create a new Thread Host driver instance utilizing the + /// native Thread radio on the MCU #[cfg(esp_idf_soc_ieee802154_supported)] pub fn new( _modem: impl Peripheral

+ 'd, @@ -101,6 +110,8 @@ impl<'d> ThreadDriver<'d, Host> { }) } + /// Create a new Thread Host driver instance utilizing an SPI connection + /// to another MCU running the Thread stack in RCP mode. #[allow(clippy::too_many_arguments)] pub fn new_spi( _spi: impl Peripheral

+ 'd, @@ -184,6 +195,8 @@ impl<'d> ThreadDriver<'d, Host> { }) } + /// Create a new Thread Host driver instance utilizing a UART connection + /// to another MCU running the Thread stack in RCP mode. pub fn new_uart( _uart: impl Peripheral

+ 'd, rx: impl Peripheral

+ 'd, @@ -240,6 +253,8 @@ impl<'d> ThreadDriver<'d, Host> { #[cfg(esp_idf_soc_ieee802154_supported)] impl<'d> ThreadDriver<'d, RCP> { + /// Create a new Thread RCP driver instance utilizing an SPI connection + /// to another MCU running the Thread Host stack. pub fn new_rcp_spi( _modem: impl Peripheral

+ 'd, _spi: impl Peripheral

+ 'd, @@ -317,6 +332,8 @@ impl<'d> ThreadDriver<'d, RCP> { }) } + /// Create a new Thread RCP driver instance utilizing a UART connection + /// to another MCU running the Thread Host stack. pub fn new_rcp_uart( _modem: impl Peripheral

+ 'd, _uart: impl Peripheral

+ 'd, @@ -391,17 +408,16 @@ impl<'d, T> Drop for ThreadDriver<'d, T> { } } -/// `EspWifi` wraps a `WifiDriver` Data Link layer instance, and binds the OSI +/// `EspThread` wraps a `ThreadDriver` Data Link layer instance, and binds the OSI /// Layer 3 (network) facilities of ESP IDF to it. /// -/// In other words, it connects the ESP IDF AP and STA Netif interfaces to the -/// Wifi driver. This allows users to utilize the Rust STD APIs for working with -/// TCP and UDP sockets. +/// In other words, it connects the ESP IDF Netif interface to the Thread driver. +/// This allows users to utilize the Rust STD APIs for working with TCP and UDP sockets. /// -/// This struct should be the default option for a Wifi driver in all use cases +/// This struct should be the default option for a Thread driver in all use cases /// but the niche one where bypassing the ESP IDF Netif and lwIP stacks is /// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the -/// ESP IDF Wifi radio. +/// ESP IDF Thread radio. #[cfg(esp_idf_comp_esp_netif_enabled)] pub struct EspThread<'d> { netif: EspNetif, @@ -410,6 +426,7 @@ pub struct EspThread<'d> { #[cfg(esp_idf_comp_esp_netif_enabled)] impl<'d> EspThread<'d> { + /// Create a new `EspThread` instance utilizing the native Thread radio on the MCU #[cfg(esp_idf_soc_ieee802154_supported)] pub fn new( modem: impl Peripheral

+ 'd, @@ -420,6 +437,8 @@ impl<'d> EspThread<'d> { Self::wrap(ThreadDriver::new(modem, sysloop, nvs, mounted_event_fs)?) } + /// Create a new `EspThread` instance utilizing an SPI connection to another MCU + /// which is expected to run the Thread RCP driver mode over SPI #[allow(clippy::too_many_arguments)] pub fn new_spi( _spi: impl Peripheral

+ 'd, @@ -445,6 +464,8 @@ impl<'d> EspThread<'d> { )?) } + /// Create a new `EspThread` instance utilizing a UART connection to another MCU + /// which is expected to run the Thread RCP driver mode over UART pub fn new_uart( _uart: impl Peripheral

+ 'd, rx: impl Peripheral

+ 'd, @@ -463,10 +484,12 @@ impl<'d> EspThread<'d> { )?) } + /// Wrap an already created Thread L2 driver instance pub fn wrap(driver: ThreadDriver<'d, Host>) -> Result { Self::wrap_all(driver, EspNetif::new(NetifStack::Thread)?) } + /// Wrap an already created Thread L2 driver instance and a network interface pub fn wrap_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result { let mut this = Self { driver, netif }; @@ -475,7 +498,7 @@ impl<'d> EspThread<'d> { Ok(this) } - /// Replaces the network interface with the provided one and returns the + /// Replace the network interface with the provided one and return the /// existing network interface. pub fn swap_netif(&mut self, netif: EspNetif) -> Result { self.detach_netif()?; @@ -487,22 +510,22 @@ impl<'d> EspThread<'d> { Ok(old) } - /// Returns the underlying [`ThreadDriver`] + /// Return the underlying [`ThreadDriver`] pub fn driver(&self) -> &ThreadDriver<'d, Host> { &self.driver } - /// Returns the underlying [`ThreadDriver`], as mutable + /// Return the underlying [`ThreadDriver`], as mutable pub fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { &mut self.driver } - /// Returns the underlying [`EspNetif`] + /// Return the underlying [`EspNetif`] pub fn netif(&self) -> &EspNetif { &self.netif } - /// Returns the underlying [`EspNetif`] as mutable + /// Return the underlying [`EspNetif`] as mutable pub fn netif_mut(&mut self) -> &mut EspNetif { &mut self.netif } From dc01b6cde88f9b9c57b8dfbea1415c876bd18cdc Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 11:36:04 +0000 Subject: [PATCH 03/60] Support for ESP IDF 4 --- src/netif.rs | 12 ++++++++- src/thread.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/netif.rs b/src/netif.rs index e2d2089b2b3..757af293772 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -120,7 +120,17 @@ impl NetifStack { Self::Ppp => _g_esp_netif_netstack_default_ppp, #[cfg(esp_idf_lwip_slip_support)] Self::Slip => _g_esp_netif_netstack_default_slip, - #[cfg(all(esp_idf_comp_openthread_enabled, esp_idf_openthread_enabled,))] + #[cfg(all( + esp_idf_comp_openthread_enabled, + esp_idf_openthread_enabled, + esp_idf_version_major = "4" + ))] + Self::Thread => _g_esp_netif_netstack_default_openthread, + #[cfg(all( + esp_idf_comp_openthread_enabled, + esp_idf_openthread_enabled, + not(esp_idf_version_major = "4") + ))] Self::Thread => &g_esp_netif_netstack_default_openthread, } } diff --git a/src/thread.rs b/src/thread.rs index 93c69825656..20e9d5145db 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -112,6 +112,7 @@ impl<'d> ThreadDriver<'d, Host> { /// Create a new Thread Host driver instance utilizing an SPI connection /// to another MCU running the Thread stack in RCP mode. + #[cfg(not(esp_idf_version_major = "4"))] #[allow(clippy::too_many_arguments)] pub fn new_spi( _spi: impl Peripheral

+ 'd, @@ -207,6 +208,38 @@ impl<'d> ThreadDriver<'d, Host> { ) -> Result { crate::hal::into_ref!(rx, tx); + #[cfg(esp_idf_version_major = "4")] + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, + radio_uart_config: esp_openthread_uart_config_t { + port: U::port() as _, + uart_config: uart_config_t { + baud_rate: BAUD_RATE as _, + data_bits: 8, + parity: 0, + stop_bits: 1, + flow_ctrl: 0, + rx_flow_ctrl_thresh: 0, + ..Default::default() + }, + rx_pin: rx.pin() as _, + tx_pin: tx.pin() as _, + }, + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, + port_config: esp_openthread_port_config_t { + storage_partition_name: b"TODO\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }, + }; + + #[cfg(not(esp_idf_version_major = "4"))] let cfg = esp_openthread_platform_config_t { radio_config: esp_openthread_radio_config_t { radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, @@ -238,6 +271,7 @@ impl<'d> ThreadDriver<'d, Host> { task_queue_size: 10, }, }; + esp!(unsafe { esp_openthread_init(&cfg) })?; debug!("Driver initialized"); @@ -255,6 +289,7 @@ impl<'d> ThreadDriver<'d, Host> { impl<'d> ThreadDriver<'d, RCP> { /// Create a new Thread RCP driver instance utilizing an SPI connection /// to another MCU running the Thread Host stack. + #[cfg(not(esp_idf_version_major = "4"))] pub fn new_rcp_spi( _modem: impl Peripheral

+ 'd, _spi: impl Peripheral

+ 'd, @@ -345,6 +380,38 @@ impl<'d> ThreadDriver<'d, RCP> { ) -> Result { crate::hal::into_ref!(rx, tx); + #[cfg(esp_idf_version_major = "4")] + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, + host_uart_config: esp_openthread_uart_config_t { + port: U::port() as _, + uart_config: uart_config_t { + baud_rate: BAUD_RATE as _, + data_bits: 8, + parity: 0, + stop_bits: 1, + flow_ctrl: 0, + rx_flow_ctrl_thresh: 0, + ..Default::default() + }, + rx_pin: rx.pin() as _, + tx_pin: tx.pin() as _, + }, + }, + port_config: esp_openthread_port_config_t { + storage_partition_name: b"TODO\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }, + }; + + #[cfg(not(esp_idf_version_major = "4"))] let cfg = esp_openthread_platform_config_t { radio_config: esp_openthread_radio_config_t { radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, @@ -376,6 +443,7 @@ impl<'d> ThreadDriver<'d, RCP> { task_queue_size: 10, }, }; + esp!(unsafe { esp_openthread_init(&cfg) })?; debug!("Driver initialized"); From a0c4c034d7aaaf0ea299ea19363920727f885461 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 11:37:15 +0000 Subject: [PATCH 04/60] Support for ESP IDF 4 --- src/thread.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/thread.rs b/src/thread.rs index 20e9d5145db..3d2ac99f338 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -507,6 +507,7 @@ impl<'d> EspThread<'d> { /// Create a new `EspThread` instance utilizing an SPI connection to another MCU /// which is expected to run the Thread RCP driver mode over SPI + #[cfg(not(esp_idf_version_major = "4"))] #[allow(clippy::too_many_arguments)] pub fn new_spi( _spi: impl Peripheral

+ 'd, From abbb99337f05fc6ddfbee04ecd235ac577f529ce Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 11:40:56 +0000 Subject: [PATCH 05/60] Small fix to the docu --- src/thread.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index 3d2ac99f338..0e8d4ba61a2 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -460,7 +460,8 @@ impl<'d> ThreadDriver<'d, RCP> { impl<'d, T> ThreadDriver<'d, T> { /// Run the Thread stack /// - /// The current thread would block until the stack is running + /// The current thread would block while the stack is running + /// Note that the stack will only exit if an error occurs pub fn run(&self) -> Result<(), EspError> { // TODO: Figure out how to stop running @@ -600,6 +601,9 @@ impl<'d> EspThread<'d> { } /// Run the Thread stack + /// + /// The current thread would block while the stack is running + /// Note that the stack will only exit if an error occurs pub fn run(&self) -> Result<(), EspError> { self.driver.run() } From 79651892aeedcbd89454c58fa3fdca1c0aaf92e2 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 11:56:27 +0000 Subject: [PATCH 06/60] Clippy --- src/thread.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/thread.rs b/src/thread.rs index 0e8d4ba61a2..b451de4eaa2 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -290,6 +290,7 @@ impl<'d> ThreadDriver<'d, RCP> { /// Create a new Thread RCP driver instance utilizing an SPI connection /// to another MCU running the Thread Host stack. #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::too_many_arguments)] pub fn new_rcp_spi( _modem: impl Peripheral

+ 'd, _spi: impl Peripheral

+ 'd, From 964fab8770e6f3ff96fc90623338329b48419374 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 12:15:52 +0000 Subject: [PATCH 07/60] Fix the ESP-IDF 4.4 build --- src/thread.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index b451de4eaa2..b1a58f5dfe5 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -7,7 +7,6 @@ use log::debug; use crate::eventloop::EspSystemEventLoop; use crate::hal::gpio::{InputPin, OutputPin}; use crate::hal::peripheral::Peripheral; -use crate::hal::spi::Spi; use crate::hal::uart::Uart; #[cfg(esp_idf_comp_esp_netif_enabled)] use crate::handle::RawHandle; @@ -114,7 +113,7 @@ impl<'d> ThreadDriver<'d, Host> { /// to another MCU running the Thread stack in RCP mode. #[cfg(not(esp_idf_version_major = "4"))] #[allow(clippy::too_many_arguments)] - pub fn new_spi( + pub fn new_spi( _spi: impl Peripheral

+ 'd, mosi: impl Peripheral

+ 'd, miso: impl Peripheral

+ 'd, @@ -291,7 +290,7 @@ impl<'d> ThreadDriver<'d, RCP> { /// to another MCU running the Thread Host stack. #[cfg(not(esp_idf_version_major = "4"))] #[allow(clippy::too_many_arguments)] - pub fn new_rcp_spi( + pub fn new_rcp_spi( _modem: impl Peripheral

+ 'd, _spi: impl Peripheral

+ 'd, mosi: impl Peripheral

+ 'd, @@ -511,7 +510,7 @@ impl<'d> EspThread<'d> { /// which is expected to run the Thread RCP driver mode over SPI #[cfg(not(esp_idf_version_major = "4"))] #[allow(clippy::too_many_arguments)] - pub fn new_spi( + pub fn new_spi( _spi: impl Peripheral

+ 'd, mosi: impl Peripheral

+ 'd, miso: impl Peripheral

+ 'd, From c6b0570c33eacd6a2daf83a9eddab77e60ee90be Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 12:38:12 +0000 Subject: [PATCH 08/60] Ipv4 conf is now optional --- examples/wifi_static_ip.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wifi_static_ip.rs b/examples/wifi_static_ip.rs index e564a65607e..141e4cb92ae 100644 --- a/examples/wifi_static_ip.rs +++ b/examples/wifi_static_ip.rs @@ -66,7 +66,7 @@ fn configure_wifi(wifi: WifiDriver) -> anyhow::Result { let mut wifi = EspWifi::wrap_all( wifi, EspNetif::new_with_conf(&NetifConfiguration { - ip_configuration: IpConfiguration::Client(IpClientConfiguration::Fixed( + ip_configuration: Some(IpConfiguration::Client(IpClientConfiguration::Fixed( IpClientSettings { ip: static_ip, subnet: Subnet { @@ -77,7 +77,7 @@ fn configure_wifi(wifi: WifiDriver) -> anyhow::Result { dns: None, secondary_dns: None, }, - )), + ))), ..NetifConfiguration::wifi_default_client() })?, #[cfg(esp_idf_esp_wifi_softap_support)] From d9075757bc76df42e759cddca7e7b97436e7b96c Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 16:26:38 +0000 Subject: [PATCH 09/60] Leave out a set of TODOs as to what is pending --- src/thread.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index b1a58f5dfe5..695f04a8b99 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -72,6 +72,17 @@ pub struct ThreadDriver<'d, T> { } impl<'d> ThreadDriver<'d, Host> { + // TODO: + // - Prio A: Ways to programmatically set the Thread Operational Dataset (esp_openthread_auto_start?) + // - Prio A: Ways to perform active and energy scan (otLinkActiveScan / otLinkEnergyScan) + // - Prio A: Status report (joined the network, device type, more?) + // - Prio A: Map all Thread eventloop events to a Rust `ThreadEvent` enum + // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) + // - Prio B: How to control when a device becomes a router? + // - Prio B: Ways to enable the Joiner workflow (need to read on that, but not needed for Matter) + // - Prio B: How to support the OpenThread CLI (useful for debugging) + // - Prio B: Think of a minimal example + /// Create a new Thread Host driver instance utilizing the /// native Thread radio on the MCU #[cfg(esp_idf_soc_ieee802154_supported)] From eef9d8cc130094abc9c7b2a9b7af1cbed0360e3f Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 16:28:14 +0000 Subject: [PATCH 10/60] Leave out a set of TODOs as to what is pending --- src/thread.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index 695f04a8b99..7cbbc982e1b 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -79,9 +79,10 @@ impl<'d> ThreadDriver<'d, Host> { // - Prio A: Map all Thread eventloop events to a Rust `ThreadEvent` enum // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) // - Prio B: How to control when a device becomes a router? + // - Prio B: How to support the Border Router case? // - Prio B: Ways to enable the Joiner workflow (need to read on that, but not needed for Matter) - // - Prio B: How to support the OpenThread CLI (useful for debugging) // - Prio B: Think of a minimal example + // - Prio C: How to support the OpenThread CLI (useful for debugging) /// Create a new Thread Host driver instance utilizing the /// native Thread radio on the MCU From 4253fab56dd81d904393a862194fff8d384bab7f Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 16:59:03 +0000 Subject: [PATCH 11/60] Leave out a set of TODOs as to what is pending --- src/thread.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 7cbbc982e1b..7177c94bb69 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -73,16 +73,24 @@ pub struct ThreadDriver<'d, T> { impl<'d> ThreadDriver<'d, Host> { // TODO: - // - Prio A: Ways to programmatically set the Thread Operational Dataset (esp_openthread_auto_start?) + // - Prio A: Ways to programmatically set the Thread Operational Dataset (esp_openthread_auto_start / otDatasetSetActiveTlvs / otDatasetSetActive?) // - Prio A: Ways to perform active and energy scan (otLinkActiveScan / otLinkEnergyScan) // - Prio A: Status report (joined the network, device type, more?) // - Prio A: Map all Thread eventloop events to a Rust `ThreadEvent` enum - // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) + // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) // - Prio B: How to control when a device becomes a router? - // - Prio B: How to support the Border Router case? - // - Prio B: Ways to enable the Joiner workflow (need to read on that, but not needed for Matter) + // - Prio B: How to support the Border Router case? (esp_openthread_border_router_init / esp_openthread_border_router_deinit? probably also needs CONFIG_OPENTHREAD_BORDER_ROUTER=y) + // - Prio B: Ways to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - no ESP API it seems, just like CONFIG_OPENTHREAD_COMMISSIONER?) // - Prio B: Think of a minimal example // - Prio C: How to support the OpenThread CLI (useful for debugging) + // - Prio C: Figure out what these do (bad/missing docu): + // - CONFIG_OPENTHREAD_DNS_CLIENT + // - CONFIG_OPENTHREAD_DIAG + // - CONFIG_OPENTHREAD_CSL_ENABLE + // - CONFIG_OPENTHREAD_DUA_ENABLE + // - CONFIG_OPENTHREAD_SRP_CLIENT + // - CONFIG_OPENTHREAD_DNS64_CLIENT? "Select this option to acquire NAT64 address from dns servers" why does this require explicit conf + // or in fact why does this has anything to do with the OpenThread client? /// Create a new Thread Host driver instance utilizing the /// native Thread radio on the MCU From 32bfa02a0b1041a9c8bcc75be5c8cf30309b8089 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 17:08:15 +0000 Subject: [PATCH 12/60] Correct part name for port config; eliminate copy-paste --- src/thread.rs | 50 ++++++++++++++------------------------------------ 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 7177c94bb69..d55e0061e1e 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -84,7 +84,7 @@ impl<'d> ThreadDriver<'d, Host> { // - Prio B: Think of a minimal example // - Prio C: How to support the OpenThread CLI (useful for debugging) // - Prio C: Figure out what these do (bad/missing docu): - // - CONFIG_OPENTHREAD_DNS_CLIENT + // - CONFIG_OPENTHREAD_DNS_CLIENT (can this be enabled programmatically too - does not seem so, and why is this part of OpenThread and not the LwIP ipv6 stack?) // - CONFIG_OPENTHREAD_DIAG // - CONFIG_OPENTHREAD_CSL_ENABLE // - CONFIG_OPENTHREAD_DUA_ENABLE @@ -111,11 +111,7 @@ impl<'d> ThreadDriver<'d, Host> { esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, ..Default::default() }, - port_config: esp_openthread_port_config_t { - storage_partition_name: b"TODO\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }, + port_config: Self::PORT_CONFIG, }; esp!(unsafe { esp_openthread_init(&cfg) })?; @@ -197,11 +193,7 @@ impl<'d> ThreadDriver<'d, Host> { esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, ..Default::default() }, - port_config: esp_openthread_port_config_t { - storage_partition_name: b"TODO\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }, + port_config: Self::PORT_CONFIG, }; esp!(unsafe { esp_openthread_init(&cfg) })?; @@ -251,11 +243,7 @@ impl<'d> ThreadDriver<'d, Host> { esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, ..Default::default() }, - port_config: esp_openthread_port_config_t { - storage_partition_name: b"TODO\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }, + port_config: Self::PORT_CONFIG, }; #[cfg(not(esp_idf_version_major = "4"))] @@ -284,11 +272,7 @@ impl<'d> ThreadDriver<'d, Host> { esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, ..Default::default() }, - port_config: esp_openthread_port_config_t { - storage_partition_name: b"TODO\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }, + port_config: Self::PORT_CONFIG, }; esp!(unsafe { esp_openthread_init(&cfg) })?; @@ -369,11 +353,7 @@ impl<'d> ThreadDriver<'d, RCP> { }, }, }, - port_config: esp_openthread_port_config_t { - storage_partition_name: b"TODO\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }, + port_config: Self::PORT_CONFIG, }; esp!(unsafe { esp_openthread_init(&cfg) })?; @@ -424,11 +404,7 @@ impl<'d> ThreadDriver<'d, RCP> { tx_pin: tx.pin() as _, }, }, - port_config: esp_openthread_port_config_t { - storage_partition_name: b"TODO\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }, + port_config: Self::PORT_CONFIG, }; #[cfg(not(esp_idf_version_major = "4"))] @@ -457,11 +433,7 @@ impl<'d> ThreadDriver<'d, RCP> { }, }, }, - port_config: esp_openthread_port_config_t { - storage_partition_name: b"TODO\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }, + port_config: Self::PORT_CONFIG, }; esp!(unsafe { esp_openthread_init(&cfg) })?; @@ -478,6 +450,12 @@ impl<'d> ThreadDriver<'d, RCP> { } impl<'d, T> ThreadDriver<'d, T> { + const PORT_CONFIG: esp_openthread_port_config_t = esp_openthread_port_config_t { + storage_partition_name: b"nvs\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }; + /// Run the Thread stack /// /// The current thread would block while the stack is running From ef054f03fb3dff927607c395c552517c4bd2a462 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 18:49:21 +0000 Subject: [PATCH 13/60] Events --- src/thread.rs | 110 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index d55e0061e1e..e6d2a2a3a47 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,12 +1,13 @@ -use core::fmt::Debug; +use core::ffi; +use core::fmt::{self, Debug}; use core::marker::PhantomData; -use esp_idf_hal::task::CriticalSection; use log::debug; -use crate::eventloop::EspSystemEventLoop; +use crate::eventloop::{EspEventDeserializer, EspEventSource, EspSystemEventLoop}; use crate::hal::gpio::{InputPin, OutputPin}; use crate::hal::peripheral::Peripheral; +use crate::hal::task::CriticalSection; use crate::hal::uart::Uart; #[cfg(esp_idf_comp_esp_netif_enabled)] use crate::handle::RawHandle; @@ -35,7 +36,7 @@ pub struct RCP; pub struct Host(esp_openthread_platform_config_t); impl Debug for Host { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Host").finish() } } @@ -76,7 +77,6 @@ impl<'d> ThreadDriver<'d, Host> { // - Prio A: Ways to programmatically set the Thread Operational Dataset (esp_openthread_auto_start / otDatasetSetActiveTlvs / otDatasetSetActive?) // - Prio A: Ways to perform active and energy scan (otLinkActiveScan / otLinkEnergyScan) // - Prio A: Status report (joined the network, device type, more?) - // - Prio A: Map all Thread eventloop events to a Rust `ThreadEvent` enum // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) // - Prio B: How to control when a device becomes a router? // - Prio B: How to support the Border Router case? (esp_openthread_border_router_init / esp_openthread_border_router_deinit? probably also needs CONFIG_OPENTHREAD_BORDER_ROUTER=y) @@ -631,3 +631,103 @@ impl Drop for EspThread<'_> { let _ = self.detach_netif(); } } + +/// Events reported by the Thread stack on the system event loop +#[derive(Copy, Clone, Debug)] +pub enum ThreadEvent { + /// Thread stack started + Started, + /// Thread stack stopped + Stopped, + /// Thread stack detached + Detached, + /// Thread stack attached + Attached, + /// Thread role changed + RoleChanged { + current_role: otDeviceRole, + previous_role: otDeviceRole, + }, + /// Thread network interface up + IfUp, + /// Thread network interface down + IfDown, + /// Thread got IPv6 address + GotIpv6, + /// Thread lost IPv6 address + LostIpv6, + /// Thread multicast group joined + MulticastJoined, + /// Thread multicast group left + MulticastLeft, + /// Thread TREL IPv6 address added + TrelIpv6Added, + /// Thread TREL IPv6 address removed + TrelIpv6Removed, + /// Thread TREL multicast group joined + TrelMulticastJoined, + /// Thread DNS server set + DnsServerSet, + /// Thread Meshcop E Publish started + MeshcopEPublishStarted, + /// Thread Meshcop E Remove started + MeshcopERemoveStarted, +} + +unsafe impl EspEventSource for ThreadEvent { + fn source() -> Option<&'static ffi::CStr> { + Some(unsafe { ffi::CStr::from_ptr(OPENTHREAD_EVENT) }) + } +} + +impl EspEventDeserializer for ThreadEvent { + type Data<'d> = ThreadEvent; + + #[allow(non_upper_case_globals, non_snake_case)] + fn deserialize<'d>(data: &crate::eventloop::EspEvent<'d>) -> ThreadEvent { + let event_id = data.event_id as u32; + + match event_id { + esp_openthread_event_t_OPENTHREAD_EVENT_START => ThreadEvent::Started, + esp_openthread_event_t_OPENTHREAD_EVENT_STOP => ThreadEvent::Stopped, + esp_openthread_event_t_OPENTHREAD_EVENT_DETACHED => ThreadEvent::Detached, + esp_openthread_event_t_OPENTHREAD_EVENT_ATTACHED => ThreadEvent::Attached, + esp_openthread_event_t_OPENTHREAD_EVENT_ROLE_CHANGED => { + let payload = unsafe { + (data.payload.unwrap() as *const _ + as *const esp_openthread_role_changed_event_t) + .as_ref() + } + .unwrap(); + + ThreadEvent::RoleChanged { + current_role: payload.current_role, + previous_role: payload.previous_role, + } + } + esp_openthread_event_t_OPENTHREAD_EVENT_IF_UP => ThreadEvent::IfUp, + esp_openthread_event_t_OPENTHREAD_EVENT_IF_DOWN => ThreadEvent::IfDown, + esp_openthread_event_t_OPENTHREAD_EVENT_GOT_IP6 => ThreadEvent::GotIpv6, + esp_openthread_event_t_OPENTHREAD_EVENT_LOST_IP6 => ThreadEvent::LostIpv6, + esp_openthread_event_t_OPENTHREAD_EVENT_MULTICAST_GROUP_JOIN => { + ThreadEvent::MulticastJoined + } + esp_openthread_event_t_OPENTHREAD_EVENT_MULTICAST_GROUP_LEAVE => { + ThreadEvent::MulticastLeft + } + esp_openthread_event_t_OPENTHREAD_EVENT_TREL_ADD_IP6 => ThreadEvent::TrelIpv6Added, + esp_openthread_event_t_OPENTHREAD_EVENT_TREL_REMOVE_IP6 => ThreadEvent::TrelIpv6Removed, + esp_openthread_event_t_OPENTHREAD_EVENT_TREL_MULTICAST_GROUP_JOIN => { + ThreadEvent::TrelMulticastJoined + } + esp_openthread_event_t_OPENTHREAD_EVENT_SET_DNS_SERVER => ThreadEvent::DnsServerSet, + esp_openthread_event_t_OPENTHREAD_EVENT_PUBLISH_MESHCOP_E => { + ThreadEvent::MeshcopEPublishStarted + } + esp_openthread_event_t_OPENTHREAD_EVENT_REMOVE_MESHCOP_E => { + ThreadEvent::MeshcopERemoveStarted + } + _ => panic!("unknown event ID: {}", event_id), + } + } +} From 2bef0750eee1068f9a6f26f6037700c0d69b2385 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 19:08:54 +0000 Subject: [PATCH 14/60] Fix CI --- src/thread.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index e6d2a2a3a47..396c2068984 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -721,9 +721,37 @@ impl EspEventDeserializer for ThreadEvent { ThreadEvent::TrelMulticastJoined } esp_openthread_event_t_OPENTHREAD_EVENT_SET_DNS_SERVER => ThreadEvent::DnsServerSet, + // Since 5.2.2 + #[cfg(any( + not(esp_idf_version_major = "4"), + not(esp_idf_version_major = "5"), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1"), + not(all( + esp_idf_version_minor = "2", + any(esp_idf_version_patch = "0", esp_idf_version_patch = "1") + )), + ), + ))] esp_openthread_event_t_OPENTHREAD_EVENT_PUBLISH_MESHCOP_E => { ThreadEvent::MeshcopEPublishStarted } + // Since 5.2.2 + #[cfg(any( + not(esp_idf_version_major = "4"), + not(esp_idf_version_major = "5"), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1"), + not(all( + esp_idf_version_minor = "2", + any(esp_idf_version_patch = "0", esp_idf_version_patch = "1") + )), + ), + ))] esp_openthread_event_t_OPENTHREAD_EVENT_REMOVE_MESHCOP_E => { ThreadEvent::MeshcopERemoveStarted } From b6d99acec9882c9bdbbdad6ac5b6804a8edbe2bb Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 17 Sep 2024 19:19:50 +0000 Subject: [PATCH 15/60] Fix wrong cfg --- src/thread.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 396c2068984..f10e722b141 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -684,7 +684,7 @@ impl EspEventDeserializer for ThreadEvent { type Data<'d> = ThreadEvent; #[allow(non_upper_case_globals, non_snake_case)] - fn deserialize<'d>(data: &crate::eventloop::EspEvent<'d>) -> ThreadEvent { + fn deserialize(data: &crate::eventloop::EspEvent) -> ThreadEvent { let event_id = data.event_id as u32; match event_id { @@ -723,8 +723,7 @@ impl EspEventDeserializer for ThreadEvent { esp_openthread_event_t_OPENTHREAD_EVENT_SET_DNS_SERVER => ThreadEvent::DnsServerSet, // Since 5.2.2 #[cfg(any( - not(esp_idf_version_major = "4"), - not(esp_idf_version_major = "5"), + not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")), all( esp_idf_version_major = "5", not(esp_idf_version_minor = "0"), @@ -740,8 +739,7 @@ impl EspEventDeserializer for ThreadEvent { } // Since 5.2.2 #[cfg(any( - not(esp_idf_version_major = "4"), - not(esp_idf_version_major = "5"), + not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")), all( esp_idf_version_major = "5", not(esp_idf_version_minor = "0"), From b16384bc8a63938ae4e77bb8a83f662b6b96b4ef Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Wed, 18 Sep 2024 09:10:27 +0000 Subject: [PATCH 16/60] fix events build for earlier ESP IDFs --- src/thread.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index f10e722b141..ade6bab6fb1 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -640,10 +640,13 @@ pub enum ThreadEvent { /// Thread stack stopped Stopped, /// Thread stack detached + #[cfg(not(esp_idf_version_major = "4"))] Detached, /// Thread stack attached + #[cfg(not(esp_idf_version_major = "4"))] Attached, /// Thread role changed + #[cfg(not(esp_idf_version_major = "4"))] RoleChanged { current_role: otDeviceRole, previous_role: otDeviceRole, @@ -661,16 +664,50 @@ pub enum ThreadEvent { /// Thread multicast group left MulticastLeft, /// Thread TREL IPv6 address added + #[cfg(not(esp_idf_version_major = "4"))] TrelIpv6Added, /// Thread TREL IPv6 address removed + #[cfg(not(esp_idf_version_major = "4"))] TrelIpv6Removed, /// Thread TREL multicast group joined + #[cfg(not(esp_idf_version_major = "4"))] TrelMulticastJoined, /// Thread DNS server set + // Since 5.1 + #[cfg(all( + not(esp_idf_version_major = "4"), + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] DnsServerSet, /// Thread Meshcop E Publish started + // Since 5.2.2 + #[cfg(any( + not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1"), + not(all( + esp_idf_version_minor = "2", + any(esp_idf_version_patch = "0", esp_idf_version_patch = "1") + )), + ), + ))] MeshcopEPublishStarted, /// Thread Meshcop E Remove started + // Since 5.2.2 + #[cfg(any( + not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1"), + not(all( + esp_idf_version_minor = "2", + any(esp_idf_version_patch = "0", esp_idf_version_patch = "1") + )), + ), + ))] MeshcopERemoveStarted, } @@ -690,8 +727,11 @@ impl EspEventDeserializer for ThreadEvent { match event_id { esp_openthread_event_t_OPENTHREAD_EVENT_START => ThreadEvent::Started, esp_openthread_event_t_OPENTHREAD_EVENT_STOP => ThreadEvent::Stopped, + #[cfg(not(esp_idf_version_major = "4"))] esp_openthread_event_t_OPENTHREAD_EVENT_DETACHED => ThreadEvent::Detached, + #[cfg(not(esp_idf_version_major = "4"))] esp_openthread_event_t_OPENTHREAD_EVENT_ATTACHED => ThreadEvent::Attached, + #[cfg(not(esp_idf_version_major = "4"))] esp_openthread_event_t_OPENTHREAD_EVENT_ROLE_CHANGED => { let payload = unsafe { (data.payload.unwrap() as *const _ @@ -715,11 +755,18 @@ impl EspEventDeserializer for ThreadEvent { esp_openthread_event_t_OPENTHREAD_EVENT_MULTICAST_GROUP_LEAVE => { ThreadEvent::MulticastLeft } + #[cfg(not(esp_idf_version_major = "4"))] esp_openthread_event_t_OPENTHREAD_EVENT_TREL_ADD_IP6 => ThreadEvent::TrelIpv6Added, + #[cfg(not(esp_idf_version_major = "4"))] esp_openthread_event_t_OPENTHREAD_EVENT_TREL_REMOVE_IP6 => ThreadEvent::TrelIpv6Removed, + #[cfg(not(esp_idf_version_major = "4"))] esp_openthread_event_t_OPENTHREAD_EVENT_TREL_MULTICAST_GROUP_JOIN => { ThreadEvent::TrelMulticastJoined } + #[cfg(all( + not(esp_idf_version_major = "4"), + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] esp_openthread_event_t_OPENTHREAD_EVENT_SET_DNS_SERVER => ThreadEvent::DnsServerSet, // Since 5.2.2 #[cfg(any( From 4433e0e25625d389f2e8a8bf4ff4a8c05a27c2fa Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 05:16:20 +0000 Subject: [PATCH 17/60] TOD initialization --- src/thread.rs | 157 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 13 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index ade6bab6fb1..154716b709f 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,10 +1,12 @@ use core::ffi; use core::fmt::{self, Debug}; use core::marker::PhantomData; +use core::mem::MaybeUninit; use log::debug; use crate::eventloop::{EspEventDeserializer, EspEventSource, EspSystemEventLoop}; +use crate::hal::delay; use crate::hal::gpio::{InputPin, OutputPin}; use crate::hal::peripheral::Peripheral; use crate::hal::task::CriticalSection; @@ -47,6 +49,16 @@ const BAUD_RATE: u32 = 74880; #[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))] const BAUD_RATE: u32 = 115200; +macro_rules! ot_esp { + ($err:expr) => {{ + esp!(match $err { + otError_OT_ERROR_NONE => ESP_OK, + otError_OT_ERROR_FAILED => ESP_FAIL, + _ => ESP_FAIL, // For now + }) + }}; +} + static CS: CriticalSection = CriticalSection::new(); /// This struct provides a safe wrapper over the ESP IDF Thread C driver. @@ -74,7 +86,6 @@ pub struct ThreadDriver<'d, T> { impl<'d> ThreadDriver<'d, Host> { // TODO: - // - Prio A: Ways to programmatically set the Thread Operational Dataset (esp_openthread_auto_start / otDatasetSetActiveTlvs / otDatasetSetActive?) // - Prio A: Ways to perform active and energy scan (otLinkActiveScan / otLinkEnergyScan) // - Prio A: Status report (joined the network, device type, more?) // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) @@ -113,9 +124,8 @@ impl<'d> ThreadDriver<'d, Host> { }, port_config: Self::PORT_CONFIG, }; - esp!(unsafe { esp_openthread_init(&cfg) })?; - debug!("Driver initialized"); + Self::init(&cfg, true)?; Ok(Self { mode: Host(cfg), @@ -195,9 +205,8 @@ impl<'d> ThreadDriver<'d, Host> { }, port_config: Self::PORT_CONFIG, }; - esp!(unsafe { esp_openthread_init(&cfg) })?; - debug!("Driver initialized"); + Self::init(&cfg, true)?; Ok(Self { mode: Host(cfg), @@ -275,9 +284,7 @@ impl<'d> ThreadDriver<'d, Host> { port_config: Self::PORT_CONFIG, }; - esp!(unsafe { esp_openthread_init(&cfg) })?; - - debug!("Driver initialized"); + Self::init(&cfg, true)?; Ok(Self { mode: Host(cfg), @@ -286,6 +293,45 @@ impl<'d> ThreadDriver<'d, Host> { _p: PhantomData, }) } + + /// Retrieve the active TOD (Thread Operational Dataset) in the user-supplied buffer + /// + /// Return the size of the TOD data written to the buffer + /// + /// The TOD is in Thread TLV format. + pub fn tod(&self, buf: &mut [u8]) -> Result { + self.internal_tod(true, buf) + } + + /// Retrieve the pending TOD (Thread Operational Dataset) in the user-supplied buffer + /// + /// Return the size of the TOD data written to the buffer + /// + /// The TOD is in Thread TLV format. + pub fn pending_tod(&self, buf: &mut [u8]) -> Result { + self.internal_tod(false, buf) + } + + /// Set the active TOD (Thread Operational Dataset) to the provided data + /// + /// The TOD data should be in Thread TLV format. + pub fn set_tod(&self, tod: &[u8]) -> Result<(), EspError> { + self.internal_set_tod(true, tod) + } + + /// Set the pending TOD (Thread Operational Dataset) to the provided data + /// + /// The TOD data should be in Thread TLV format. + pub fn set_pending_tod(&self, tod: &[u8]) -> Result<(), EspError> { + self.internal_set_tod(false, tod) + } + + /// Set the active TOD (Thread Operational Dataset) according to the + /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app + /// during build (via `sdkconfig*) + pub fn set_active_tod_from_cfg(&self) -> Result<(), EspError> { + ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) + } } #[cfg(esp_idf_soc_ieee802154_supported)] @@ -355,9 +401,8 @@ impl<'d> ThreadDriver<'d, RCP> { }, port_config: Self::PORT_CONFIG, }; - esp!(unsafe { esp_openthread_init(&cfg) })?; - debug!("Driver initialized"); + Self::init(&cfg, false)?; Ok(Self { mode: RCP, @@ -436,9 +481,7 @@ impl<'d> ThreadDriver<'d, RCP> { port_config: Self::PORT_CONFIG, }; - esp!(unsafe { esp_openthread_init(&cfg) })?; - - debug!("Driver initialized"); + Self::init(&cfg, false)?; Ok(Self { mode: RCP, @@ -467,11 +510,99 @@ impl<'d, T> ThreadDriver<'d, T> { esp!(unsafe { esp_openthread_launch_mainloop() }) } + + fn internal_tod(&self, active: bool, buf: &mut [u8]) -> Result { + let _lock = OtLock::acquire()?; + + let mut tlvs = MaybeUninit::::uninit(); // TODO: Large buffer + ot_esp!(unsafe { + if active { + otDatasetGetActiveTlvs(esp_openthread_get_instance(), tlvs.assume_init_mut()) + } else { + otDatasetGetPendingTlvs(esp_openthread_get_instance(), tlvs.assume_init_mut()) + } + })?; + + let tlvs = unsafe { tlvs.assume_init_mut() }; + + let len = tlvs.mLength as usize; + if buf.len() < len { + Err(EspError::from_infallible::())?; + } + + buf[..len].copy_from_slice(&tlvs.mTlvs[..len]); + + Ok(len) + } + + fn internal_set_tod(&self, active: bool, data: &[u8]) -> Result<(), EspError> { + let _lock = OtLock::acquire()?; + + let mut tlvs = MaybeUninit::::uninit(); // TODO: Large buffer + + let tlvs = unsafe { tlvs.assume_init_mut() }; + + if data.len() > core::mem::size_of_val(&tlvs.mTlvs) { + Err(EspError::from_infallible::())?; + } + + tlvs.mLength = data.len() as _; + tlvs.mTlvs[..data.len()].copy_from_slice(data); + + ot_esp!(unsafe { + if active { + otDatasetSetActiveTlvs(esp_openthread_get_instance(), tlvs) + } else { + otDatasetSetPendingTlvs(esp_openthread_get_instance(), tlvs) + } + })?; + + Ok(()) + } + + fn init(cfg: &esp_openthread_platform_config_t, enable: bool) -> Result<(), EspError> { + esp!(unsafe { esp_openthread_init(cfg) })?; + + Self::set_enabled(enable)?; + + debug!("Driver initialized"); + + Ok(()) + } + + fn set_enabled(enabled: bool) -> Result<(), EspError> { + ot_esp!(unsafe { otIp6SetEnabled(esp_openthread_get_instance(), enabled) })?; + ot_esp!(unsafe { otThreadSetEnabled(esp_openthread_get_instance(), enabled) })?; + + Ok(()) + } } impl<'d, T> Drop for ThreadDriver<'d, T> { fn drop(&mut self) { esp!(unsafe { esp_openthread_deinit() }).unwrap(); + + debug!("Driver dropped"); + } +} + +struct OtLock(PhantomData<*const ()>); + +impl OtLock { + pub fn acquire() -> Result { + if !unsafe { esp_openthread_lock_acquire(delay::BLOCK) } { + Err(EspError::from_infallible::())?; + } + + Ok(Self(PhantomData)) + } +} + +impl Drop for OtLock { + fn drop(&mut self) { + unsafe { + esp_openthread_lock_release(); + } } } From 2de828435a2d8e50be84bb625a5d9ebe7d062b20 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 05:30:09 +0000 Subject: [PATCH 18/60] Expose the additional APIs on EspThread too --- src/thread.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index 154716b709f..00a0d98d9f9 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -51,7 +51,7 @@ const BAUD_RATE: u32 = 115200; macro_rules! ot_esp { ($err:expr) => {{ - esp!(match $err { + esp!(match $err as _ { otError_OT_ERROR_NONE => ESP_OK, otError_OT_ERROR_FAILED => ESP_FAIL, _ => ESP_FAIL, // For now @@ -334,6 +334,10 @@ impl<'d> ThreadDriver<'d, Host> { } } +extern "C" { + fn otAppNcpInit(instance: *mut otInstance); +} + #[cfg(esp_idf_soc_ieee802154_supported)] impl<'d> ThreadDriver<'d, RCP> { /// Create a new Thread RCP driver instance utilizing an SPI connection @@ -404,6 +408,10 @@ impl<'d> ThreadDriver<'d, RCP> { Self::init(&cfg, false)?; + unsafe { + otAppNcpInit(esp_openthread_get_instance()); + } + Ok(Self { mode: RCP, _mounted_event_fs: mounted_event_fs, @@ -483,6 +491,10 @@ impl<'d> ThreadDriver<'d, RCP> { Self::init(&cfg, false)?; + unsafe { + otAppNcpInit(esp_openthread_get_instance()); + } + Ok(Self { mode: RCP, _mounted_event_fs: mounted_event_fs, @@ -729,6 +741,45 @@ impl<'d> EspThread<'d> { &mut self.netif } + /// Retrieve the active TOD (Thread Operational Dataset) in the user-supplied buffer + /// + /// Return the size of the TOD data written to the buffer + /// + /// The TOD is in Thread TLV format. + pub fn tod(&self, buf: &mut [u8]) -> Result { + self.driver().tod(buf) + } + + /// Retrieve the pending TOD (Thread Operational Dataset) in the user-supplied buffer + /// + /// Return the size of the TOD data written to the buffer + /// + /// The TOD is in Thread TLV format. + pub fn pending_tod(&self, buf: &mut [u8]) -> Result { + self.driver().pending_tod(buf) + } + + /// Set the active TOD (Thread Operational Dataset) to the provided data + /// + /// The TOD data should be in Thread TLV format. + pub fn set_tod(&self, tod: &[u8]) -> Result<(), EspError> { + self.driver().set_tod(tod) + } + + /// Set the pending TOD (Thread Operational Dataset) to the provided data + /// + /// The TOD data should be in Thread TLV format. + pub fn set_pending_tod(&self, tod: &[u8]) -> Result<(), EspError> { + self.driver().set_pending_tod(tod) + } + + /// Set the active TOD (Thread Operational Dataset) according to the + /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app + /// during build (via `sdkconfig*) + pub fn set_active_tod_from_cfg(&self) -> Result<(), EspError> { + ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) + } + /// Run the Thread stack /// /// The current thread would block while the stack is running From 8d7c9a38f526cb1713b0df5105adc48d1c232a18 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 05:30:37 +0000 Subject: [PATCH 19/60] Fix small typo in docs --- src/thread.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 00a0d98d9f9..8ebd0576728 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -328,7 +328,7 @@ impl<'d> ThreadDriver<'d, Host> { /// Set the active TOD (Thread Operational Dataset) according to the /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app - /// during build (via `sdkconfig*) + /// during build (via `sdkconfig*`) pub fn set_active_tod_from_cfg(&self) -> Result<(), EspError> { ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) } @@ -775,7 +775,7 @@ impl<'d> EspThread<'d> { /// Set the active TOD (Thread Operational Dataset) according to the /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app - /// during build (via `sdkconfig*) + /// during build (via `sdkconfig*`) pub fn set_active_tod_from_cfg(&self) -> Result<(), EspError> { ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) } From 91374b51a0d393896eee4eba4c9146c51c9375e5 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 05:57:59 +0000 Subject: [PATCH 20/60] Re-use the UART config struct of the UART driver --- src/thread.rs | 62 +++++++++++++++------------------------------------ 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 8ebd0576728..09555585c8e 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -43,11 +43,13 @@ impl Debug for Host { } } -#[cfg(all(esp32c2, esp_idf_xtal_freq_26))] -const BAUD_RATE: u32 = 74880; +pub mod config { + #[cfg(all(esp32c2, esp_idf_xtal_freq_26))] + const UART_DEFAULT_BAUD_RATE: u32 = 74880; -#[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))] -const BAUD_RATE: u32 = 115200; + #[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))] + const UART_DEFAULT_BAUD_RATE: u32 = 115200; +} macro_rules! ot_esp { ($err:expr) => {{ @@ -220,8 +222,9 @@ impl<'d> ThreadDriver<'d, Host> { /// to another MCU running the Thread stack in RCP mode. pub fn new_uart( _uart: impl Peripheral

+ 'd, - rx: impl Peripheral

+ 'd, tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + config: &crate::hal::uart::config::Config, _sysloop: EspSystemEventLoop, nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, @@ -234,15 +237,7 @@ impl<'d> ThreadDriver<'d, Host> { radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, radio_uart_config: esp_openthread_uart_config_t { port: U::port() as _, - uart_config: uart_config_t { - baud_rate: BAUD_RATE as _, - data_bits: 8, - parity: 0, - stop_bits: 1, - flow_ctrl: 0, - rx_flow_ctrl_thresh: 0, - ..Default::default() - }, + uart_config: config.into(), rx_pin: rx.pin() as _, tx_pin: tx.pin() as _, }, @@ -262,15 +257,7 @@ impl<'d> ThreadDriver<'d, Host> { __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 { radio_uart_config: esp_openthread_uart_config_t { port: U::port() as _, - uart_config: uart_config_t { - baud_rate: BAUD_RATE as _, - data_bits: 8, - parity: 0, - stop_bits: 1, - flow_ctrl: 0, - rx_flow_ctrl_thresh: 0, - ..Default::default() - }, + uart_config: config.into(), rx_pin: rx.pin() as _, tx_pin: tx.pin() as _, }, @@ -425,8 +412,9 @@ impl<'d> ThreadDriver<'d, RCP> { pub fn new_rcp_uart( _modem: impl Peripheral

+ 'd, _uart: impl Peripheral

+ 'd, - rx: impl Peripheral

+ 'd, tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + config: &crate::hal::uart::config::Config, _sysloop: EspSystemEventLoop, nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, @@ -444,15 +432,7 @@ impl<'d> ThreadDriver<'d, RCP> { esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, host_uart_config: esp_openthread_uart_config_t { port: U::port() as _, - uart_config: uart_config_t { - baud_rate: BAUD_RATE as _, - data_bits: 8, - parity: 0, - stop_bits: 1, - flow_ctrl: 0, - rx_flow_ctrl_thresh: 0, - ..Default::default() - }, + uart_config: config.into(), rx_pin: rx.pin() as _, tx_pin: tx.pin() as _, }, @@ -472,15 +452,7 @@ impl<'d> ThreadDriver<'d, RCP> { __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 { host_uart_config: esp_openthread_uart_config_t { port: U::port() as _, - uart_config: uart_config_t { - baud_rate: BAUD_RATE as _, - data_bits: 8, - parity: 0, - stop_bits: 1, - flow_ctrl: 0, - rx_flow_ctrl_thresh: 0, - ..Default::default() - }, + uart_config: config.into(), rx_pin: rx.pin() as _, tx_pin: tx.pin() as _, }, @@ -679,16 +651,18 @@ impl<'d> EspThread<'d> { /// which is expected to run the Thread RCP driver mode over UART pub fn new_uart( _uart: impl Peripheral

+ 'd, - rx: impl Peripheral

+ 'd, tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + config: &crate::hal::uart::config::Config, _sysloop: EspSystemEventLoop, nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, ) -> Result { Self::wrap(ThreadDriver::new_uart( _uart, - rx, tx, + rx, + config, _sysloop, nvs, mounted_event_fs, From d0c49cb312f3cdb10e96d74d2547c86d8581e710 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 06:15:49 +0000 Subject: [PATCH 21/60] Re-use the SPI config struct of the SPI driver --- src/thread.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 09555585c8e..e02acea2651 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -148,6 +148,7 @@ impl<'d> ThreadDriver<'d, Host> { sclk: impl Peripheral

+ 'd, cs: Option + 'd>, intr: Option + 'd>, + config: &crate::hal::spi::config::Config, _sysloop: EspSystemEventLoop, nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, @@ -170,6 +171,9 @@ impl<'d> ThreadDriver<'d, Host> { -1 }; + let mut icfg: spi_device_interface_config_t = config.into(); + icfg.spics_io_num = cs_pin as _; + let cfg = esp_openthread_platform_config_t { radio_config: esp_openthread_radio_config_t { radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_SPI_RCP, @@ -187,15 +191,7 @@ impl<'d> ThreadDriver<'d, Host> { sclk_io_num: sclk.pin() as _, ..Default::default() }, - spi_device: spi_device_interface_config_t { - spics_io_num: cs_pin as _, - cs_ena_pretrans: 2, - input_delay_ns: 100, - mode: 0, - clock_speed_hz: 2500 * 1000, - queue_size: 5, - ..Default::default() - }, + spi_device: icfg, intr_pin, }, }, @@ -630,6 +626,7 @@ impl<'d> EspThread<'d> { sclk: impl Peripheral

+ 'd, cs: Option + 'd>, intr: Option + 'd>, + config: &crate::hal::spi::config::Config, _sysloop: EspSystemEventLoop, nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, @@ -641,6 +638,7 @@ impl<'d> EspThread<'d> { sclk, cs, intr, + config, _sysloop, nvs, mounted_event_fs, From 15907e947936893205771f05876fa03de234f027 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 06:24:58 +0000 Subject: [PATCH 22/60] Clippy --- src/thread.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index e02acea2651..ceb518e5cf8 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -45,18 +45,23 @@ impl Debug for Host { pub mod config { #[cfg(all(esp32c2, esp_idf_xtal_freq_26))] - const UART_DEFAULT_BAUD_RATE: u32 = 74880; + pub const UART_DEFAULT_BAUD_RATE: u32 = 74880; #[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))] - const UART_DEFAULT_BAUD_RATE: u32 = 115200; + pub const UART_DEFAULT_BAUD_RATE: u32 = 115200; } macro_rules! ot_esp { ($err:expr) => {{ - esp!(match $err as _ { - otError_OT_ERROR_NONE => ESP_OK, - otError_OT_ERROR_FAILED => ESP_FAIL, - _ => ESP_FAIL, // For now + esp!({ + #[allow(non_upper_case_globals, non_snake_case)] + let err = match $err as _ { + otError_OT_ERROR_NONE => ESP_OK, + otError_OT_ERROR_FAILED => ESP_FAIL, + _ => ESP_FAIL, // For now + }; + + err }) }}; } @@ -318,6 +323,7 @@ impl<'d> ThreadDriver<'d, Host> { } extern "C" { + #[allow(dead_code)] fn otAppNcpInit(instance: *mut otInstance); } From ca6dd21b7cda00d5067d7e7f4076c2a912865d7e Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 07:17:17 +0000 Subject: [PATCH 23/60] Options to scan for Thread networks --- src/thread.rs | 176 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 2 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index ceb518e5cf8..10a4b5bade1 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,4 +1,4 @@ -use core::ffi; +use core::ffi::{self, c_void, CStr}; use core::fmt::{self, Debug}; use core::marker::PhantomData; use core::mem::MaybeUninit; @@ -21,6 +21,7 @@ use crate::sys::*; extern crate alloc; +use alloc::boxed::Box; use alloc::sync::Arc; /// The driver will operate in Radio Co-Processor mode @@ -66,6 +67,86 @@ macro_rules! ot_esp { }}; } +/// Active scan result +pub struct ActiveScanResult<'a>(&'a otActiveScanResult); + +impl<'a> ActiveScanResult<'a> { + /// IEEE 802.15.4 Extended Address + pub fn extended_address(&self) -> &'a [u8] { + &self.0.mExtAddress.m8 + } + + /// Thread Network Name + pub fn network_name_cstr(&self) -> &'a CStr { + unsafe { ffi::CStr::from_ptr(&self.0.mNetworkName.m8 as *const _ as *const _) } + } + + /// Thread Extended PAN ID + pub fn extended_pan_id(&self) -> &[u8] { + &self.0.mExtendedPanId.m8 + } + + /// Steering Data + pub fn steering_data(&self) -> &[u8] { + &self.0.mSteeringData.m8 + } + + /// IEEE 802.15.4 PAN ID + pub fn pan_id(&self) -> u16 { + self.0.mPanId + } + + /// Joiner UDP Port + pub fn joiner_udp_port(&self) -> u16 { + self.0.mJoinerUdpPort + } + + /// IEEE 802.15.4 Channel + pub fn channel(&self) -> u8 { + self.0.mChannel + } + + /// The max RSSI (dBm) + pub fn max_rssi(&self) -> i8 { + self.0.mRssi + } + + /// LQI + pub fn lqi(&self) -> u8 { + self.0.mLqi + } + + /// Version + pub fn version(&self) -> u8 { + self.0.mVersion() as _ + } + + /// Native Commissioner + pub fn native_commissioner(&self) -> bool { + self.0.mIsNative() + } + + /// Join permitted + pub fn join_permitted(&self) -> bool { + self.0.mIsJoinable() + } +} + +/// Energy scan result +pub struct EnergyScanResult<'a>(&'a otEnergyScanResult); + +impl<'a> EnergyScanResult<'a> { + /// IEEE 802.15.4 Channel + pub fn channel(&self) -> u8 { + self.0.mChannel + } + + /// The max RSSI (dBm) + pub fn max_rssi(&self) -> i8 { + self.0.mMaxRssi + } +} + static CS: CriticalSection = CriticalSection::new(); /// This struct provides a safe wrapper over the ESP IDF Thread C driver. @@ -93,7 +174,6 @@ pub struct ThreadDriver<'d, T> { impl<'d> ThreadDriver<'d, Host> { // TODO: - // - Prio A: Ways to perform active and energy scan (otLinkActiveScan / otLinkEnergyScan) // - Prio A: Status report (joined the network, device type, more?) // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) // - Prio B: How to control when a device becomes a router? @@ -317,9 +397,101 @@ impl<'d> ThreadDriver<'d, Host> { /// Set the active TOD (Thread Operational Dataset) according to the /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app /// during build (via `sdkconfig*`) + #[cfg(not(esp_idf_version_major = "4"))] pub fn set_active_tod_from_cfg(&self) -> Result<(), EspError> { ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) } + + /// Perform an active scan for Thread networks + /// + /// The callback will be called for each found network + /// At the end of the scan, the callback will be called with `None` + pub fn scan)>(&self, callback: F) -> Result<(), EspError> { + let _lock = OtLock::acquire()?; + + let mut callback: Box)>> = + Box::new(Box::new(callback)); + + ot_esp!(unsafe { + otLinkActiveScan( + esp_openthread_get_instance(), + 0, + 0, + Some(Self::on_active_scan_result), + callback.as_mut() as *mut _ as *mut c_void, + ) + })?; + + Ok(()) + } + + /// Check if an active scan is in progress + pub fn is_scan_in_progress(&self) -> Result { + let _lock = OtLock::acquire()?; + + Ok(unsafe { otLinkIsActiveScanInProgress(esp_openthread_get_instance()) }) + } + + /// Perform an energy scan for Thread networks + /// + /// The callback will be called for each found network + /// At the end of the scan, the callback will be called with `None` + pub fn energy_scan)>( + &self, + callback: F, + ) -> Result<(), EspError> { + let _lock = OtLock::acquire()?; + + let mut callback: Box)>> = + Box::new(Box::new(callback)); + + ot_esp!(unsafe { + otLinkEnergyScan( + esp_openthread_get_instance(), + 0, + 0, + Some(Self::on_energy_scan_result), + callback.as_mut() as *mut _ as *mut c_void, + ) + })?; + + Ok(()) + } + + /// Check if an energy scan is in progress + pub fn is_energy_scan_in_progress(&self) -> Result { + let _lock = OtLock::acquire()?; + + Ok(unsafe { otLinkIsEnergyScanInProgress(esp_openthread_get_instance()) }) + } + + unsafe extern "C" fn on_active_scan_result( + result: *mut otActiveScanResult, + context: *mut c_void, + ) { + let callback = + unsafe { (context as *mut Box)>).as_mut() }.unwrap(); + + if result.is_null() { + callback(None); + } else { + callback(Some(ActiveScanResult(unsafe { result.as_ref() }.unwrap()))); + } + } + + unsafe extern "C" fn on_energy_scan_result( + result: *mut otEnergyScanResult, + context: *mut c_void, + ) { + let callback = + unsafe { (context as *mut Box)>).as_mut() }.unwrap(); + + if result.is_null() { + callback(None); + } else { + callback(Some(EnergyScanResult(unsafe { result.as_ref() }.unwrap()))); + } + } } extern "C" { From 8f4154473c1546c85dd0f9afe74e214568a78c40 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 07:18:40 +0000 Subject: [PATCH 24/60] Expose the scan APIs on EspThread as well --- src/thread.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index 10a4b5bade1..47c14352c16 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -930,6 +930,35 @@ impl<'d> EspThread<'d> { ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) } + /// Perform an active scan for Thread networks + /// The callback will be called for each found network + /// + /// At the end of the scan, the callback will be called with `None` + pub fn scan)>(&self, callback: F) -> Result<(), EspError> { + self.driver().scan(callback) + } + + /// Check if an active scan is in progress + pub fn is_scan_in_progress(&self) -> Result { + self.driver().is_scan_in_progress() + } + + /// Perform an energy scan for Thread networks + /// The callback will be called for each found network + /// + /// At the end of the scan, the callback will be called with `None` + pub fn energy_scan)>( + &self, + callback: F, + ) -> Result<(), EspError> { + self.driver().energy_scan(callback) + } + + /// Check if an energy scan is in progress + pub fn is_energy_scan_in_progress(&self) -> Result { + self.driver().is_energy_scan_in_progress() + } + /// Run the Thread stack /// /// The current thread would block while the stack is running From 0ed03657280f043a80b160d40297142c19c850ec Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 07:27:00 +0000 Subject: [PATCH 25/60] Small rename to match the other methods --- src/thread.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 47c14352c16..c35348422d9 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -398,7 +398,7 @@ impl<'d> ThreadDriver<'d, Host> { /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app /// during build (via `sdkconfig*`) #[cfg(not(esp_idf_version_major = "4"))] - pub fn set_active_tod_from_cfg(&self) -> Result<(), EspError> { + pub fn set_tod_from_cfg(&self) -> Result<(), EspError> { ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) } @@ -926,8 +926,9 @@ impl<'d> EspThread<'d> { /// Set the active TOD (Thread Operational Dataset) according to the /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app /// during build (via `sdkconfig*`) - pub fn set_active_tod_from_cfg(&self) -> Result<(), EspError> { - ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) + #[cfg(not(esp_idf_version_major = "4"))] + pub fn set_tod_from_cfg(&self) -> Result<(), EspError> { + self.driver().set_tod_from_cfg() } /// Perform an active scan for Thread networks From 9c24f3e0fb80a66a28cfcb02854c29e51a061ad7 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 07:34:14 +0000 Subject: [PATCH 26/60] Clippy --- src/thread.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index c35348422d9..02db0aaf52d 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -409,6 +409,7 @@ impl<'d> ThreadDriver<'d, Host> { pub fn scan)>(&self, callback: F) -> Result<(), EspError> { let _lock = OtLock::acquire()?; + #[allow(clippy::type_complexity)] let mut callback: Box)>> = Box::new(Box::new(callback)); @@ -442,6 +443,7 @@ impl<'d> ThreadDriver<'d, Host> { ) -> Result<(), EspError> { let _lock = OtLock::acquire()?; + #[allow(clippy::type_complexity)] let mut callback: Box)>> = Box::new(Box::new(callback)); @@ -469,6 +471,7 @@ impl<'d> ThreadDriver<'d, Host> { result: *mut otActiveScanResult, context: *mut c_void, ) { + #[allow(clippy::type_complexity)] let callback = unsafe { (context as *mut Box)>).as_mut() }.unwrap(); @@ -483,6 +486,7 @@ impl<'d> ThreadDriver<'d, Host> { result: *mut otEnergyScanResult, context: *mut c_void, ) { + #[allow(clippy::type_complexity)] let callback = unsafe { (context as *mut Box)>).as_mut() }.unwrap(); From 3a78011c8025609225b2bfb5f9ebd545358369d8 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 11:08:41 +0000 Subject: [PATCH 27/60] Border router --- .cargo/config.toml | 2 +- .github/configs/sdkconfig.defaults | 9 + src/thread.rs | 772 +++++++++++++++++++---------- 3 files changed, 507 insertions(+), 276 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 0656ef2bf6a..65758519420 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -24,7 +24,7 @@ rustflags = ["--cfg", "espidf_time64"] [env] ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" -ESP_IDF_VERSION = "v5.2.2" +ESP_IDF_VERSION = "v5.3.1" [unstable] build-std = ["std", "panic_abort"] diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index 3dec682b463..b84aa19064b 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -40,3 +40,12 @@ CONFIG_LWIP_PPP_SUPPORT=y #CONFIG_LWIP_SLIP_SUPPORT=y CONFIG_OPENTHREAD_ENABLED=y + +# Border router +CONFIG_OPENTHREAD_BORDER_ROUTER=y +CONFIG_LWIP_IPV6_NUM_ADDRESSES=12 +# All these are also necessary for the Joiner feature +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +CONFIG_MBEDTLS_ECJPAKE_C=y diff --git a/src/thread.rs b/src/thread.rs index 02db0aaf52d..7791dc401bf 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,5 +1,20 @@ +// TODO: +// - Prio A: Status report (joined the network, device type, more?) +// - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) +// - Prio B: Ways to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - no ESP API it seems, just like CONFIG_OPENTHREAD_COMMISSIONER?) +// - Prio B: Think of a minimal example +// - Prio C: How to support the OpenThread CLI (useful for debugging) +// - Prio C: Figure out what these do (bad/missing docu): +// - CONFIG_OPENTHREAD_DNS_CLIENT (can this be enabled programmatically too - does not seem so, and why is this part of OpenThread and not the LwIP ipv6 stack?) +// - CONFIG_OPENTHREAD_DIAG +// - CONFIG_OPENTHREAD_CSL_ENABLE +// - CONFIG_OPENTHREAD_DUA_ENABLE +// - CONFIG_OPENTHREAD_SRP_CLIENT +// - CONFIG_OPENTHREAD_DNS64_CLIENT? "Select this option to acquire NAT64 address from dns servers" why does this require explicit conf +// or in fact why does this has anything to do with the OpenThread client? + use core::ffi::{self, c_void, CStr}; -use core::fmt::{self, Debug}; +use core::fmt::Debug; use core::marker::PhantomData; use core::mem::MaybeUninit; @@ -24,24 +39,42 @@ extern crate alloc; use alloc::boxed::Box; use alloc::sync::Arc; +/// A trait shared between the `Host` and `RCP` modes providing the option for these +/// to do additional initialization. +pub trait Mode { + fn init(); +} + /// The driver will operate in Radio Co-Processor mode /// /// The chip needs to be connected via UART or SPI to the host #[cfg(esp_idf_soc_ieee802154_supported)] #[derive(Debug)] -pub struct RCP; +pub struct RCP(()); + +#[cfg(esp_idf_soc_ieee802154_supported)] +impl Mode for RCP { + fn init() { + extern "C" { + fn otAppNcpInit(instance: *mut otInstance); + } + + unsafe { + otAppNcpInit(esp_openthread_get_instance()); + } + } +} /// The driver will operate as a host /// /// This means that - unless the chip has a native Thread suppoort - /// it needs to be connected via UART or SPI to another chip which does have /// native Thread support and which is configured to operate in RCP mode -pub struct Host(esp_openthread_platform_config_t); +#[derive(Debug)] +pub struct Host(()); -impl Debug for Host { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Host").finish() - } +impl Mode for Host { + fn init() {} } pub mod config { @@ -147,8 +180,6 @@ impl<'a> EnergyScanResult<'a> { } } -static CS: CriticalSection = CriticalSection::new(); - /// This struct provides a safe wrapper over the ESP IDF Thread C driver. /// /// The driver works on Layer 2 (Data Link) in the OSI model, in that it provides @@ -165,59 +196,31 @@ static CS: CriticalSection = CriticalSection::new(); /// - Host mode: The driver operates as a host, and if the chip does not have a Thread radio /// it has to be connected via SPI or USB to a chip which runs the Thread stack in RCP mode pub struct ThreadDriver<'d, T> { - mode: T, - _mounted_event_fs: Arc, + cfg: esp_openthread_platform_config_t, + cs: CriticalSection, //_subscription: EspSubscription<'static, System>, _nvs: EspDefaultNvsPartition, + _mounted_event_fs: Arc, + _mode: T, _p: PhantomData<&'d mut ()>, } impl<'d> ThreadDriver<'d, Host> { - // TODO: - // - Prio A: Status report (joined the network, device type, more?) - // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) - // - Prio B: How to control when a device becomes a router? - // - Prio B: How to support the Border Router case? (esp_openthread_border_router_init / esp_openthread_border_router_deinit? probably also needs CONFIG_OPENTHREAD_BORDER_ROUTER=y) - // - Prio B: Ways to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - no ESP API it seems, just like CONFIG_OPENTHREAD_COMMISSIONER?) - // - Prio B: Think of a minimal example - // - Prio C: How to support the OpenThread CLI (useful for debugging) - // - Prio C: Figure out what these do (bad/missing docu): - // - CONFIG_OPENTHREAD_DNS_CLIENT (can this be enabled programmatically too - does not seem so, and why is this part of OpenThread and not the LwIP ipv6 stack?) - // - CONFIG_OPENTHREAD_DIAG - // - CONFIG_OPENTHREAD_CSL_ENABLE - // - CONFIG_OPENTHREAD_DUA_ENABLE - // - CONFIG_OPENTHREAD_SRP_CLIENT - // - CONFIG_OPENTHREAD_DNS64_CLIENT? "Select this option to acquire NAT64 address from dns servers" why does this require explicit conf - // or in fact why does this has anything to do with the OpenThread client? - /// Create a new Thread Host driver instance utilizing the /// native Thread radio on the MCU #[cfg(esp_idf_soc_ieee802154_supported)] pub fn new( - _modem: impl Peripheral

+ 'd, + modem: impl Peripheral

+ 'd, _sysloop: EspSystemEventLoop, nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, ) -> Result { - let cfg = esp_openthread_platform_config_t { - radio_config: esp_openthread_radio_config_t { - radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, - ..Default::default() - }, - host_config: esp_openthread_host_connection_config_t { - host_connection_mode: - esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, - ..Default::default() - }, - port_config: Self::PORT_CONFIG, - }; - - Self::init(&cfg, true)?; - Ok(Self { - mode: Host(cfg), - _mounted_event_fs: mounted_event_fs, + cfg: Self::native_host_cfg(modem), + cs: CriticalSection::new(), _nvs: nvs, + _mounted_event_fs: mounted_event_fs, + _mode: Host(()), _p: PhantomData, }) } @@ -227,7 +230,7 @@ impl<'d> ThreadDriver<'d, Host> { #[cfg(not(esp_idf_version_major = "4"))] #[allow(clippy::too_many_arguments)] pub fn new_spi( - _spi: impl Peripheral

+ 'd, + spi: impl Peripheral

+ 'd, mosi: impl Peripheral

+ 'd, miso: impl Peripheral

+ 'd, sclk: impl Peripheral

+ 'd, @@ -238,63 +241,12 @@ impl<'d> ThreadDriver<'d, Host> { nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, ) -> Result { - crate::hal::into_ref!(mosi, miso, sclk); - - let cs_pin = if let Some(cs) = cs { - crate::hal::into_ref!(cs); - - cs.pin() as _ - } else { - -1 - }; - - let intr_pin = if let Some(intr) = intr { - crate::hal::into_ref!(intr); - - intr.pin() as _ - } else { - -1 - }; - - let mut icfg: spi_device_interface_config_t = config.into(); - icfg.spics_io_num = cs_pin as _; - - let cfg = esp_openthread_platform_config_t { - radio_config: esp_openthread_radio_config_t { - radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_SPI_RCP, - __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 { - radio_spi_config: esp_openthread_spi_host_config_t { - host_device: S::device() as _, - dma_channel: spi_common_dma_t_SPI_DMA_CH_AUTO, - spi_interface: spi_bus_config_t { - __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 { - mosi_io_num: mosi.pin() as _, - }, - __bindgen_anon_2: spi_bus_config_t__bindgen_ty_2 { - miso_io_num: miso.pin() as _, - }, - sclk_io_num: sclk.pin() as _, - ..Default::default() - }, - spi_device: icfg, - intr_pin, - }, - }, - }, - host_config: esp_openthread_host_connection_config_t { - host_connection_mode: - esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, - ..Default::default() - }, - port_config: Self::PORT_CONFIG, - }; - - Self::init(&cfg, true)?; - Ok(Self { - mode: Host(cfg), - _mounted_event_fs: mounted_event_fs, + cfg: Self::host_spi_cfg(spi, mosi, miso, sclk, cs, intr, config), + cs: CriticalSection::new(), _nvs: nvs, + _mounted_event_fs: mounted_event_fs, + _mode: Host(()), _p: PhantomData, }) } @@ -302,7 +254,7 @@ impl<'d> ThreadDriver<'d, Host> { /// Create a new Thread Host driver instance utilizing a UART connection /// to another MCU running the Thread stack in RCP mode. pub fn new_uart( - _uart: impl Peripheral

+ 'd, + uart: impl Peripheral

+ 'd, tx: impl Peripheral

+ 'd, rx: impl Peripheral

+ 'd, config: &crate::hal::uart::config::Config, @@ -310,54 +262,12 @@ impl<'d> ThreadDriver<'d, Host> { nvs: EspDefaultNvsPartition, mounted_event_fs: Arc, ) -> Result { - crate::hal::into_ref!(rx, tx); - - #[cfg(esp_idf_version_major = "4")] - let cfg = esp_openthread_platform_config_t { - radio_config: esp_openthread_radio_config_t { - radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, - radio_uart_config: esp_openthread_uart_config_t { - port: U::port() as _, - uart_config: config.into(), - rx_pin: rx.pin() as _, - tx_pin: tx.pin() as _, - }, - }, - host_config: esp_openthread_host_connection_config_t { - host_connection_mode: - esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, - ..Default::default() - }, - port_config: Self::PORT_CONFIG, - }; - - #[cfg(not(esp_idf_version_major = "4"))] - let cfg = esp_openthread_platform_config_t { - radio_config: esp_openthread_radio_config_t { - radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, - __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 { - radio_uart_config: esp_openthread_uart_config_t { - port: U::port() as _, - uart_config: config.into(), - rx_pin: rx.pin() as _, - tx_pin: tx.pin() as _, - }, - }, - }, - host_config: esp_openthread_host_connection_config_t { - host_connection_mode: - esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, - ..Default::default() - }, - port_config: Self::PORT_CONFIG, - }; - - Self::init(&cfg, true)?; - Ok(Self { - mode: Host(cfg), - _mounted_event_fs: mounted_event_fs, + cfg: Self::host_uart_cfg(uart, tx, rx, config), + cs: CriticalSection::new(), _nvs: nvs, + _mounted_event_fs: mounted_event_fs, + _mode: Host(()), _p: PhantomData, }) } @@ -496,31 +406,36 @@ impl<'d> ThreadDriver<'d, Host> { callback(Some(EnergyScanResult(unsafe { result.as_ref() }.unwrap()))); } } -} -extern "C" { - #[allow(dead_code)] - fn otAppNcpInit(instance: *mut otInstance); -} + #[cfg(esp_idf_soc_ieee802154_supported)] + fn host_native_cfg( + _modem: impl Peripheral

+ 'd, + ) -> esp_openthread_platform_config_t { + esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, + port_config: Self::PORT_CONFIG, + } + } -#[cfg(esp_idf_soc_ieee802154_supported)] -impl<'d> ThreadDriver<'d, RCP> { - /// Create a new Thread RCP driver instance utilizing an SPI connection - /// to another MCU running the Thread Host stack. #[cfg(not(esp_idf_version_major = "4"))] #[allow(clippy::too_many_arguments)] - pub fn new_rcp_spi( - _modem: impl Peripheral

+ 'd, + fn host_spi_cfg( _spi: impl Peripheral

+ 'd, mosi: impl Peripheral

+ 'd, miso: impl Peripheral

+ 'd, sclk: impl Peripheral

+ 'd, cs: Option + 'd>, intr: Option + 'd>, - _sysloop: EspSystemEventLoop, - nvs: EspDefaultNvsPartition, - mounted_event_fs: Arc, - ) -> Result { + config: &crate::hal::spi::config::Config, + ) -> esp_openthread_platform_config_t { crate::hal::into_ref!(mosi, miso, sclk); let cs_pin = if let Some(cs) = cs { @@ -539,18 +454,17 @@ impl<'d> ThreadDriver<'d, RCP> { -1 }; - let cfg = esp_openthread_platform_config_t { + let mut icfg: spi_device_interface_config_t = config.into(); + icfg.spics_io_num = cs_pin as _; + + esp_openthread_platform_config_t { radio_config: esp_openthread_radio_config_t { - radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, - ..Default::default() - }, - host_config: esp_openthread_host_connection_config_t { - host_connection_mode: - esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, - __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 { - spi_slave_config: esp_openthread_spi_slave_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_SPI_RCP, + __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 { + radio_spi_config: esp_openthread_spi_host_config_t { host_device: S::device() as _, - bus_config: spi_bus_config_t { + dma_channel: spi_common_dma_t_SPI_DMA_CH_AUTO, + spi_interface: spi_bus_config_t { __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 { mosi_io_num: mosi.pin() as _, }, @@ -560,75 +474,53 @@ impl<'d> ThreadDriver<'d, RCP> { sclk_io_num: sclk.pin() as _, ..Default::default() }, - slave_config: spi_slave_interface_config_t { - spics_io_num: cs_pin as _, - ..Default::default() - }, + spi_device: icfg, intr_pin, }, }, }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, port_config: Self::PORT_CONFIG, - }; - - Self::init(&cfg, false)?; - - unsafe { - otAppNcpInit(esp_openthread_get_instance()); } - - Ok(Self { - mode: RCP, - _mounted_event_fs: mounted_event_fs, - _nvs: nvs, - _p: PhantomData, - }) } - /// Create a new Thread RCP driver instance utilizing a UART connection - /// to another MCU running the Thread Host stack. - pub fn new_rcp_uart( - _modem: impl Peripheral

+ 'd, + fn host_uart_cfg( _uart: impl Peripheral

+ 'd, tx: impl Peripheral

+ 'd, rx: impl Peripheral

+ 'd, config: &crate::hal::uart::config::Config, - _sysloop: EspSystemEventLoop, - nvs: EspDefaultNvsPartition, - mounted_event_fs: Arc, - ) -> Result { + ) -> esp_openthread_platform_config_t { crate::hal::into_ref!(rx, tx); #[cfg(esp_idf_version_major = "4")] let cfg = esp_openthread_platform_config_t { radio_config: esp_openthread_radio_config_t { - radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, - ..Default::default() - }, - host_config: esp_openthread_host_connection_config_t { - host_connection_mode: - esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, - host_uart_config: esp_openthread_uart_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, + radio_uart_config: esp_openthread_uart_config_t { port: U::port() as _, uart_config: config.into(), rx_pin: rx.pin() as _, tx_pin: tx.pin() as _, }, }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, port_config: Self::PORT_CONFIG, }; #[cfg(not(esp_idf_version_major = "4"))] let cfg = esp_openthread_platform_config_t { radio_config: esp_openthread_radio_config_t { - radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, - ..Default::default() - }, - host_config: esp_openthread_host_connection_config_t { - host_connection_mode: - esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, - __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 { - host_uart_config: esp_openthread_uart_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP, + __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 { + radio_uart_config: esp_openthread_uart_config_t { port: U::port() as _, uart_config: config.into(), rx_pin: rx.pin() as _, @@ -636,41 +528,15 @@ impl<'d> ThreadDriver<'d, RCP> { }, }, }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE, + ..Default::default() + }, port_config: Self::PORT_CONFIG, }; - Self::init(&cfg, false)?; - - unsafe { - otAppNcpInit(esp_openthread_get_instance()); - } - - Ok(Self { - mode: RCP, - _mounted_event_fs: mounted_event_fs, - _nvs: nvs, - _p: PhantomData, - }) - } -} - -impl<'d, T> ThreadDriver<'d, T> { - const PORT_CONFIG: esp_openthread_port_config_t = esp_openthread_port_config_t { - storage_partition_name: b"nvs\0" as *const _ as *const _, - netif_queue_size: 10, - task_queue_size: 10, - }; - - /// Run the Thread stack - /// - /// The current thread would block while the stack is running - /// Note that the stack will only exit if an error occurs - pub fn run(&self) -> Result<(), EspError> { - // TODO: Figure out how to stop running - - let _cs = CS.enter(); - - esp!(unsafe { esp_openthread_launch_mainloop() }) + cfg } fn internal_tod(&self, active: bool, buf: &mut [u8]) -> Result { @@ -721,30 +587,201 @@ impl<'d, T> ThreadDriver<'d, T> { Ok(()) } +} - fn init(cfg: &esp_openthread_platform_config_t, enable: bool) -> Result<(), EspError> { - esp!(unsafe { esp_openthread_init(cfg) })?; +#[cfg(esp_idf_soc_ieee802154_supported)] +impl<'d> ThreadDriver<'d, RCP> { + /// Create a new Thread RCP driver instance utilizing an SPI connection + /// to another MCU running the Thread Host stack. + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::too_many_arguments)] + pub fn new_rcp_spi( + modem: impl Peripheral

+ 'd, + spi: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + cs: Option + 'd>, + intr: Option + 'd>, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + Ok(Self { + cfg: Self::rcp_spi_cfg(modem, spi, mosi, miso, sclk, cs, intr), + cs: CriticalSection::new(), + _nvs: nvs, + _mounted_event_fs: mounted_event_fs, + _mode: RCP(()), + _p: PhantomData, + }) + } + + /// Create a new Thread RCP driver instance utilizing a UART connection + /// to another MCU running the Thread Host stack. + pub fn new_rcp_uart( + modem: impl Peripheral

+ 'd, + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + config: &crate::hal::uart::config::Config, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + ) -> Result { + Ok(Self { + cfg: Self::rcp_uart_cfg(modem, uart, tx, rx, config), + cs: CriticalSection::new(), + _nvs: nvs, + _mounted_event_fs: mounted_event_fs, + _mode: RCP(()), + _p: PhantomData, + }) + } - Self::set_enabled(enable)?; + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::too_many_arguments)] + fn rcp_spi_cfg( + _modem: impl Peripheral

+ 'd, + _spi: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + cs: Option + 'd>, + intr: Option + 'd>, + ) -> esp_openthread_platform_config_t { + crate::hal::into_ref!(mosi, miso, sclk); - debug!("Driver initialized"); + let cs_pin = if let Some(cs) = cs { + crate::hal::into_ref!(cs); - Ok(()) + cs.pin() as _ + } else { + -1 + }; + + let intr_pin = if let Some(intr) = intr { + crate::hal::into_ref!(intr); + + intr.pin() as _ + } else { + -1 + }; + + esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, + __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 { + spi_slave_config: esp_openthread_spi_slave_config_t { + host_device: S::device() as _, + bus_config: spi_bus_config_t { + __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 { + mosi_io_num: mosi.pin() as _, + }, + __bindgen_anon_2: spi_bus_config_t__bindgen_ty_2 { + miso_io_num: miso.pin() as _, + }, + sclk_io_num: sclk.pin() as _, + ..Default::default() + }, + slave_config: spi_slave_interface_config_t { + spics_io_num: cs_pin as _, + ..Default::default() + }, + intr_pin, + }, + }, + }, + port_config: Self::PORT_CONFIG, + } } - fn set_enabled(enabled: bool) -> Result<(), EspError> { - ot_esp!(unsafe { otIp6SetEnabled(esp_openthread_get_instance(), enabled) })?; - ot_esp!(unsafe { otThreadSetEnabled(esp_openthread_get_instance(), enabled) })?; + fn rcp_uart_cfg( + _modem: impl Peripheral

+ 'd, + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + config: &crate::hal::uart::config::Config, + ) -> esp_openthread_platform_config_t { + crate::hal::into_ref!(rx, tx); - Ok(()) + #[cfg(esp_idf_version_major = "4")] + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, + host_uart_config: esp_openthread_uart_config_t { + port: U::port() as _, + uart_config: config.into(), + rx_pin: rx.pin() as _, + tx_pin: tx.pin() as _, + }, + }, + port_config: Self::PORT_CONFIG, + }; + + #[cfg(not(esp_idf_version_major = "4"))] + let cfg = esp_openthread_platform_config_t { + radio_config: esp_openthread_radio_config_t { + radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE, + ..Default::default() + }, + host_config: esp_openthread_host_connection_config_t { + host_connection_mode: + esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART, + __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 { + host_uart_config: esp_openthread_uart_config_t { + port: U::port() as _, + uart_config: config.into(), + rx_pin: rx.pin() as _, + tx_pin: tx.pin() as _, + }, + }, + }, + port_config: Self::PORT_CONFIG, + }; + + cfg } } -impl<'d, T> Drop for ThreadDriver<'d, T> { - fn drop(&mut self) { - esp!(unsafe { esp_openthread_deinit() }).unwrap(); +impl<'d, T> ThreadDriver<'d, T> +where + T: Mode, +{ + const PORT_CONFIG: esp_openthread_port_config_t = esp_openthread_port_config_t { + storage_partition_name: b"nvs\0" as *const _ as *const _, + netif_queue_size: 10, + task_queue_size: 10, + }; + + /// Run the Thread stack + /// + /// The current thread would block while the stack is running + /// Note that the stack will only exit if an error occurs + pub fn run(&self) -> Result<(), EspError> { + // TODO: Figure out how to stop running - debug!("Driver dropped"); + let _lock = self.cs.enter(); + + esp!(unsafe { esp_openthread_init(&self.cfg) })?; + + T::init(); + + let result = esp!(unsafe { esp_openthread_launch_mainloop() }); + + esp!(unsafe { esp_openthread_deinit() })?; + + result } } @@ -768,6 +805,71 @@ impl Drop for OtLock { } } +/// Trait shared between the modes of operation of the `EspThread` instance +pub trait NetifMode<'d> { + fn driver(&self) -> &ThreadDriver<'d, Host>; + fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host>; +} + +/// The regular mode of operation for the `EspThread` instance +/// +/// This is the only available mode if the Border Router functionality in ESP-IDF is not enabled +pub struct Node<'d>(ThreadDriver<'d, Host>); + +impl<'d> NetifMode<'d> for Node<'d> { + fn driver(&self) -> &ThreadDriver<'d, Host> { + &self.0 + } + + fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { + &mut self.0 + } +} + +/// The Border Router mode of operation for the `EspThread` instance +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +pub struct BorderRouter<'d, N>(ThreadDriver<'d, Host>, N) +where + N: core::borrow::Borrow; + +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +impl<'d, N> BorderRouter<'d, N> +where + N: core::borrow::Borrow, +{ + fn new(driver: ThreadDriver<'d, Host>, netif: N) -> Result { + unsafe { + esp_openthread_set_backbone_netif(netif.borrow().handle()); + } + + esp!(unsafe { esp_openthread_border_router_init() })?; + + debug!("Border router initialized"); + + Ok(Self(driver, netif)) + } +} + +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +impl<'d, N: core::borrow::Borrow> Drop for BorderRouter<'d, N> { + fn drop(&mut self) { + esp!(unsafe { esp_openthread_border_router_deinit() }).unwrap(); + + debug!("Border router dropped"); + } +} + +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +impl<'d, N: core::borrow::Borrow> NetifMode<'d> for BorderRouter<'d, N> { + fn driver(&self) -> &ThreadDriver<'d, Host> { + &self.0 + } + + fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { + &mut self.0 + } +} + /// `EspThread` wraps a `ThreadDriver` Data Link layer instance, and binds the OSI /// Layer 3 (network) facilities of ESP IDF to it. /// @@ -779,13 +881,13 @@ impl Drop for OtLock { /// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the /// ESP IDF Thread radio. #[cfg(esp_idf_comp_esp_netif_enabled)] -pub struct EspThread<'d> { +pub struct EspThread { netif: EspNetif, - driver: ThreadDriver<'d, Host>, + driver: T, } #[cfg(esp_idf_comp_esp_netif_enabled)] -impl<'d> EspThread<'d> { +impl<'d> EspThread> { /// Create a new `EspThread` instance utilizing the native Thread radio on the MCU #[cfg(esp_idf_soc_ieee802154_supported)] pub fn new( @@ -856,33 +958,129 @@ impl<'d> EspThread<'d> { /// Wrap an already created Thread L2 driver instance and a network interface pub fn wrap_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result { - let mut this = Self { driver, netif }; + let mut this = Self { + driver: Node(driver), + netif, + }; this.attach_netif()?; + Self::enable_network(true)?; Ok(this) } +} - /// Replace the network interface with the provided one and return the - /// existing network interface. - pub fn swap_netif(&mut self, netif: EspNetif) -> Result { - self.detach_netif()?; +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +impl<'d, N> EspThread> +where + N: core::borrow::Borrow, +{ + /// Create a new `EspThread` Border Router instance utilizing the native Thread radio on the MCU + #[cfg(esp_idf_soc_ieee802154_supported)] + pub fn new_br( + modem: impl Peripheral

+ 'd, + sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + backbone_netif: N, + ) -> Result { + Self::wrap_br( + ThreadDriver::new(modem, sysloop, nvs, mounted_event_fs)?, + backbone_netif, + ) + } - let old = core::mem::replace(&mut self.netif, netif); + /// Create a new `EspThread` Border Router instance utilizing an SPI connection to another MCU + /// which is expected to run the Thread RCP driver mode over SPI + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::too_many_arguments)] + pub fn new_br_spi( + _spi: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + cs: Option + 'd>, + intr: Option + 'd>, + config: &crate::hal::spi::config::Config, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + backbone_netif: N, + ) -> Result { + Self::wrap_br( + ThreadDriver::new_spi( + _spi, + mosi, + miso, + sclk, + cs, + intr, + config, + _sysloop, + nvs, + mounted_event_fs, + )?, + backbone_netif, + ) + } + + /// Create a new `EspThread` Border Router instance utilizing a UART connection to another MCU + /// which is expected to run the Thread RCP driver mode over UART + #[allow(clippy::too_many_arguments)] + pub fn new_br_uart( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + config: &crate::hal::uart::config::Config, + _sysloop: EspSystemEventLoop, + nvs: EspDefaultNvsPartition, + mounted_event_fs: Arc, + backbone_netif: N, + ) -> Result { + Self::wrap_br( + ThreadDriver::new_uart(_uart, tx, rx, config, _sysloop, nvs, mounted_event_fs)?, + backbone_netif, + ) + } - self.attach_netif()?; + /// Wrap an already created Thread L2 driver instance and a backbone network interface + /// to the outside world + pub fn wrap_br(driver: ThreadDriver<'d, Host>, backbone_netif: N) -> Result { + Self::wrap_br_all(driver, EspNetif::new(NetifStack::Thread)?, backbone_netif) + } - Ok(old) + /// Wrap an already created Thread L2 driver instance, a network interface to be used for the + /// Thread network, and a backbone network interface to the outside world + pub fn wrap_br_all( + driver: ThreadDriver<'d, Host>, + netif: EspNetif, + backbone_netif: N, + ) -> Result { + let mut this = Self { + driver: BorderRouter::new(driver, backbone_netif)?, + netif, + }; + + this.attach_netif()?; + Self::enable_network(true)?; + + Ok(this) } +} +#[cfg(esp_idf_comp_esp_netif_enabled)] +impl<'d, T> EspThread +where + T: NetifMode<'d>, +{ /// Return the underlying [`ThreadDriver`] pub fn driver(&self) -> &ThreadDriver<'d, Host> { - &self.driver + self.driver.driver() } /// Return the underlying [`ThreadDriver`], as mutable pub fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { - &mut self.driver + self.driver.driver_mut() } /// Return the underlying [`EspNetif`] @@ -969,19 +1167,43 @@ impl<'d> EspThread<'d> { /// The current thread would block while the stack is running /// Note that the stack will only exit if an error occurs pub fn run(&self) -> Result<(), EspError> { - self.driver.run() + self.driver.driver().run() + } + + /// Replace the network interface with the provided one and return the + /// existing network interface. + pub fn swap_netif(&mut self, netif: EspNetif) -> Result { + Self::enable_network(false)?; + self.detach_netif()?; + + let old = core::mem::replace(&mut self.netif, netif); + + self.attach_netif()?; + Self::enable_network(true)?; + + Ok(old) } fn attach_netif(&mut self) -> Result<(), EspError> { esp!(unsafe { esp_netif_attach( self.netif.handle() as *mut _, - esp_openthread_netif_glue_init(&self.driver.mode.0), + esp_openthread_netif_glue_init(&self.driver.driver().cfg), ) })?; Ok(()) } +} + +#[cfg(esp_idf_comp_esp_netif_enabled)] +impl EspThread { + fn enable_network(enabled: bool) -> Result<(), EspError> { + ot_esp!(unsafe { otIp6SetEnabled(esp_openthread_get_instance(), enabled) })?; + ot_esp!(unsafe { otThreadSetEnabled(esp_openthread_get_instance(), enabled) })?; + + Ok(()) + } fn detach_netif(&mut self) -> Result<(), EspError> { unsafe { @@ -992,7 +1214,7 @@ impl<'d> EspThread<'d> { } } -impl Drop for EspThread<'_> { +impl Drop for EspThread { fn drop(&mut self) { let _ = self.detach_netif(); } From d86e67669034db9279e98d8c42f46003404d9f92 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 11:27:38 +0000 Subject: [PATCH 28/60] Current device role --- src/thread.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 7791dc401bf..0070e76b0fa 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,7 +1,8 @@ // TODO: -// - Prio A: Status report (joined the network, device type, more?) +// - Prio A: Status report (we have driver::role() now; more, e.g. ipv6 notifications?) // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) -// - Prio B: Ways to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - no ESP API it seems, just like CONFIG_OPENTHREAD_COMMISSIONER?) +// - Prio B: API to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) +// - Prio B: API to to enable the Commissioner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_COMMISSIONER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) // - Prio B: Think of a minimal example // - Prio C: How to support the OpenThread CLI (useful for debugging) // - Prio C: Figure out what these do (bad/missing docu): @@ -180,6 +181,29 @@ impl<'a> EnergyScanResult<'a> { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Role { + Disabled, + Detached, + Child, + Router, + Leader, +} + +#[allow(non_upper_case_globals, non_snake_case)] +impl From for Role { + fn from(role: otDeviceRole) -> Self { + match role { + otDeviceRole_OT_DEVICE_ROLE_DISABLED => Role::Disabled, + otDeviceRole_OT_DEVICE_ROLE_DETACHED => Role::Detached, + otDeviceRole_OT_DEVICE_ROLE_CHILD => Role::Child, + otDeviceRole_OT_DEVICE_ROLE_ROUTER => Role::Router, + otDeviceRole_OT_DEVICE_ROLE_LEADER => Role::Leader, + _ => Role::Disabled, + } + } +} + /// This struct provides a safe wrapper over the ESP IDF Thread C driver. /// /// The driver works on Layer 2 (Data Link) in the OSI model, in that it provides @@ -272,6 +296,13 @@ impl<'d> ThreadDriver<'d, Host> { }) } + /// Retrieve the current role of the device in the Thread network + pub fn role(&self) -> Result { + let _lock = OtLock::acquire()?; + + Ok(unsafe { otThreadGetDeviceRole(esp_openthread_get_instance()) }.into()) + } + /// Retrieve the active TOD (Thread Operational Dataset) in the user-supplied buffer /// /// Return the size of the TOD data written to the buffer @@ -1093,6 +1124,11 @@ where &mut self.netif } + /// Retrieve the current role of the device in the Thread network + pub fn role(&self) -> Result { + self.driver().role() + } + /// Retrieve the active TOD (Thread Operational Dataset) in the user-supplied buffer /// /// Return the size of the TOD data written to the buffer From be5413f28c6bc376fc86406da18a5315a6a3a83e Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 11:31:10 +0000 Subject: [PATCH 29/60] Border router works OK since 5.2 --- src/thread.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 0070e76b0fa..48b5ce89d3b 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -858,12 +858,36 @@ impl<'d> NetifMode<'d> for Node<'d> { } /// The Border Router mode of operation for the `EspThread` instance -#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +// Since 5.2.0 +#[cfg(all( + esp_idf_comp_esp_netif_enabled, + esp_idf_openthread_border_router, + any( + all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1") + ), + ), +))] pub struct BorderRouter<'d, N>(ThreadDriver<'d, Host>, N) where N: core::borrow::Borrow; -#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +// Since 5.2.0 +#[cfg(all( + esp_idf_comp_esp_netif_enabled, + esp_idf_openthread_border_router, + any( + all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1") + ), + ), +))] impl<'d, N> BorderRouter<'d, N> where N: core::borrow::Borrow, @@ -881,7 +905,19 @@ where } } -#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +// Since 5.2.0 +#[cfg(all( + esp_idf_comp_esp_netif_enabled, + esp_idf_openthread_border_router, + any( + all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1") + ), + ), +))] impl<'d, N: core::borrow::Borrow> Drop for BorderRouter<'d, N> { fn drop(&mut self) { esp!(unsafe { esp_openthread_border_router_deinit() }).unwrap(); @@ -890,7 +926,19 @@ impl<'d, N: core::borrow::Borrow> Drop for BorderRouter<'d, N> { } } -#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +// Since 5.2.0 +#[cfg(all( + esp_idf_comp_esp_netif_enabled, + esp_idf_openthread_border_router, + any( + all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1") + ), + ), +))] impl<'d, N: core::borrow::Borrow> NetifMode<'d> for BorderRouter<'d, N> { fn driver(&self) -> &ThreadDriver<'d, Host> { &self.0 @@ -1001,7 +1049,19 @@ impl<'d> EspThread> { } } -#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] +// Since 5.2.0 +#[cfg(all( + esp_idf_comp_esp_netif_enabled, + esp_idf_openthread_border_router, + any( + all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), + all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(esp_idf_version_minor = "1") + ), + ), +))] impl<'d, N> EspThread> where N: core::borrow::Borrow, From 40176f5b0a32c939a50b2550e61688bcb6665b42 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 11:32:19 +0000 Subject: [PATCH 30/60] Use strong typing for roles in the events --- src/thread.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 48b5ce89d3b..149cb5674b1 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1332,8 +1332,8 @@ pub enum ThreadEvent { /// Thread role changed #[cfg(not(esp_idf_version_major = "4"))] RoleChanged { - current_role: otDeviceRole, - previous_role: otDeviceRole, + current_role: Role, + previous_role: Role, }, /// Thread network interface up IfUp, @@ -1425,8 +1425,8 @@ impl EspEventDeserializer for ThreadEvent { .unwrap(); ThreadEvent::RoleChanged { - current_role: payload.current_role, - previous_role: payload.previous_role, + current_role: payload.current_role.into(), + previous_role: payload.previous_role.into(), } } esp_openthread_event_t_OPENTHREAD_EVENT_IF_UP => ThreadEvent::IfUp, From a4eb6f34976858143fb73dcb92c6590d7555ca0e Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 11:37:54 +0000 Subject: [PATCH 31/60] Border router works OK since 5.2 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7565dd46ea..16f128fac51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,8 +26,8 @@ jobs: - xtensa-esp32s2-espidf - xtensa-esp32s3-espidf idf-version: - - v4.4.7 - - v5.1.2 +# - v4.4.7 +# - v5.1.2 - v5.2 steps: - name: Setup | Checkout From 03dd062bffa1ae43f47542ec99e33bd5fc49ac8b Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 12:54:14 +0000 Subject: [PATCH 32/60] Border router works OK since 5.2 --- .github/workflows/ci.yml | 2 +- src/thread.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16f128fac51..13bc3a343ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: idf-version: # - v4.4.7 # - v5.1.2 - - v5.2 + - v5.2.2 steps: - name: Setup | Checkout uses: actions/checkout@v3 diff --git a/src/thread.rs b/src/thread.rs index 149cb5674b1..9fe7f7ae3be 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -806,12 +806,16 @@ where esp!(unsafe { esp_openthread_init(&self.cfg) })?; + debug!("Driver initialized"); + T::init(); let result = esp!(unsafe { esp_openthread_launch_mainloop() }); esp!(unsafe { esp_openthread_deinit() })?; + debug!("Driver deinitialized"); + result } } From 1f914a9d472caa5e39dd0003212ca7b816e1e760 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 13:01:53 +0000 Subject: [PATCH 33/60] Border router works OK since... 5.3 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13bc3a343ac..ed0472e2514 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: idf-version: # - v4.4.7 # - v5.1.2 - - v5.2.2 + - v5.3.0 steps: - name: Setup | Checkout uses: actions/checkout@v3 From 7f64159874b1761b70366c5a6faf658b2faadaf2 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 13:08:40 +0000 Subject: [PATCH 34/60] Border router works OK since... 5.3 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed0472e2514..6ef4fcb1416 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: idf-version: # - v4.4.7 # - v5.1.2 - - v5.3.0 + - v5.3 steps: - name: Setup | Checkout uses: actions/checkout@v3 From f3e6532b297b82a7f02c0767c521285352f4931d Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 13:13:45 +0000 Subject: [PATCH 35/60] Border router works OK since... 5.3 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ef4fcb1416..e363ee806a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: idf-version: # - v4.4.7 # - v5.1.2 - - v5.3 + - v5.3.1 steps: - name: Setup | Checkout uses: actions/checkout@v3 From b7a85e5e5aaf99ec61fc60ba85214275252a0ba4 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 13:19:58 +0000 Subject: [PATCH 36/60] Problem is the MCU, not the ESP IDF version --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e363ee806a0..1c7f600f133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,14 +21,13 @@ jobs: matrix: target: - riscv32imc-esp-espidf - - riscv32imac-esp-espidf +# - riscv32imac-esp-espidf - xtensa-esp32-espidf - xtensa-esp32s2-espidf - xtensa-esp32s3-espidf idf-version: # - v4.4.7 -# - v5.1.2 - - v5.3.1 + - v5.1.2 steps: - name: Setup | Checkout uses: actions/checkout@v3 From 035acc3718be88438f3aac73bcef5e966aecc099 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 13:35:23 +0000 Subject: [PATCH 37/60] Re-enable 4.4.7 to catch BR errors --- .github/workflows/ci.yml | 2 +- src/thread.rs | 64 ++++++++++------------------------------ 2 files changed, 17 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c7f600f133..f62e5026471 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - xtensa-esp32s2-espidf - xtensa-esp32s3-espidf idf-version: -# - v4.4.7 + - v4.4.7 - v5.1.2 steps: - name: Setup | Checkout diff --git a/src/thread.rs b/src/thread.rs index 9fe7f7ae3be..903442c1441 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -862,46 +862,38 @@ impl<'d> NetifMode<'d> for Node<'d> { } /// The Border Router mode of operation for the `EspThread` instance -// Since 5.2.0 #[cfg(all( esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router, - any( - all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), - all( - esp_idf_version_major = "5", - not(esp_idf_version_minor = "0"), - not(esp_idf_version_minor = "1") - ), - ), + not(any(esp32h2, esp32h4)) ))] pub struct BorderRouter<'d, N>(ThreadDriver<'d, Host>, N) where N: core::borrow::Borrow; -// Since 5.2.0 #[cfg(all( esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router, - any( - all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), - all( - esp_idf_version_major = "5", - not(esp_idf_version_minor = "0"), - not(esp_idf_version_minor = "1") - ), - ), + not(any(esp32h2, esp32h4)) ))] impl<'d, N> BorderRouter<'d, N> where N: core::borrow::Borrow, { fn new(driver: ThreadDriver<'d, Host>, netif: N) -> Result { - unsafe { - esp_openthread_set_backbone_netif(netif.borrow().handle()); + #[cfg(esp_idf_version_major = "4")] + { + esp!(unsafe { esp_openthread_border_router_init(netif.borrow().handle()) })?; } - esp!(unsafe { esp_openthread_border_router_init() })?; + #[cfg(not(esp_idf_version_major = "4"))] + { + unsafe { + esp_openthread_set_backbone_netif(netif.borrow().handle()); + } + + esp!(unsafe { esp_openthread_border_router_init() })?; + } debug!("Border router initialized"); @@ -909,18 +901,10 @@ where } } -// Since 5.2.0 #[cfg(all( esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router, - any( - all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), - all( - esp_idf_version_major = "5", - not(esp_idf_version_minor = "0"), - not(esp_idf_version_minor = "1") - ), - ), + not(any(esp32h2, esp32h4)) ))] impl<'d, N: core::borrow::Borrow> Drop for BorderRouter<'d, N> { fn drop(&mut self) { @@ -930,18 +914,10 @@ impl<'d, N: core::borrow::Borrow> Drop for BorderRouter<'d, N> { } } -// Since 5.2.0 #[cfg(all( esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router, - any( - all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), - all( - esp_idf_version_major = "5", - not(esp_idf_version_minor = "0"), - not(esp_idf_version_minor = "1") - ), - ), + not(any(esp32h2, esp32h4)) ))] impl<'d, N: core::borrow::Borrow> NetifMode<'d> for BorderRouter<'d, N> { fn driver(&self) -> &ThreadDriver<'d, Host> { @@ -1053,18 +1029,10 @@ impl<'d> EspThread> { } } -// Since 5.2.0 #[cfg(all( esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router, - any( - all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), - all( - esp_idf_version_major = "5", - not(esp_idf_version_minor = "0"), - not(esp_idf_version_minor = "1") - ), - ), + not(any(esp32h2, esp32h4)) ))] impl<'d, N> EspThread> where From b14d783599f6e2eb2f5abacda1818919b8401061 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 14:12:27 +0000 Subject: [PATCH 38/60] Fix wrong method name --- src/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index 903442c1441..acd871ae6fd 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -240,7 +240,7 @@ impl<'d> ThreadDriver<'d, Host> { mounted_event_fs: Arc, ) -> Result { Ok(Self { - cfg: Self::native_host_cfg(modem), + cfg: Self::host_native_cfg(modem), cs: CriticalSection::new(), _nvs: nvs, _mounted_event_fs: mounted_event_fs, From 8679dedeec176c9d1c8220bd7c1578e60b8ca808 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 16:36:20 +0000 Subject: [PATCH 39/60] Initial examples --- .cargo/config.toml | 3 +- .github/configs/sdkconfig.defaults | 7 +- .gitignore | 1 + Cargo.toml | 3 + examples/thread.rs | 59 ++++++++++++ examples/thread_br.rs | 145 +++++++++++++++++++++++++++++ src/thread.rs | 2 +- 7 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 examples/thread.rs create mode 100644 examples/thread_br.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 65758519420..deb707e6338 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,5 @@ [build] -target = "riscv32imc-esp-espidf" +target = "riscv32imac-esp-espidf" #target = "xtensa-esp32-espidf" [target.xtensa-esp32-espidf] @@ -25,6 +25,7 @@ rustflags = ["--cfg", "espidf_time64"] [env] ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" ESP_IDF_VERSION = "v5.3.1" +MCU = "esp32c6" [unstable] build-std = ["std", "panic_abort"] diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index b84aa19064b..7de71629014 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -39,12 +39,15 @@ CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y CONFIG_LWIP_PPP_SUPPORT=y #CONFIG_LWIP_SLIP_SUPPORT=y +# Generic Thread functionality CONFIG_OPENTHREAD_ENABLED=y -# Border router +# Thread Border Router CONFIG_OPENTHREAD_BORDER_ROUTER=y CONFIG_LWIP_IPV6_NUM_ADDRESSES=12 -# All these are also necessary for the Joiner feature +CONFIG_LWIP_NETIF_STATUS_CALLBACK=y + +# These are also necessary for the Joiner feature CONFIG_MBEDTLS_CMAC_C=y CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y diff --git a/.gitignore b/.gitignore index 06487911946..8daec69797e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ /.embuild /target /Cargo.lock +*.lock **/*.rs.bk /.devcontainer diff --git a/Cargo.toml b/Cargo.toml index aa081adec79..1d1f46002c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ harness = false esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal" } esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys" } +[[package.metadata.esp-idf-sys.extra_components]] +remote_component = { name = "espressif/mdns", version = "1.2" } + [features] default = ["std", "binstart", "experimental"] diff --git a/examples/thread.rs b/examples/thread.rs new file mode 100644 index 00000000000..7e1e7d0a82d --- /dev/null +++ b/examples/thread.rs @@ -0,0 +1,59 @@ +//! Example of using Thread. +//! The example just starts Thread and logs the events, without doing anything else useful. +//! +//! NOTE: This example only works on MCUs that has Thread capabilities, like the ESP32-C6 or ESP32-H2. + +fn main() -> anyhow::Result<()> { + #[cfg(any(esp32h2, esp32c6))] + router::main()?; + + #[cfg(not(any(esp32h2, esp32c6)))] + println!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2."); + + Ok(()) +} + +#[cfg(any(esp32h2, esp32c6))] +mod router { + use std::sync::Arc; + + use esp_idf_svc::eventloop::EspSystemSubscription; + use log::info; + + use esp_idf_svc::hal::prelude::Peripherals; + use esp_idf_svc::io::vfs::MountedEventfs; + use esp_idf_svc::log::EspLogger; + use esp_idf_svc::thread::{EspThread, ThreadEvent}; + use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition}; + + pub fn main() -> anyhow::Result<()> { + esp_idf_svc::sys::link_patches(); + EspLogger::initialize_default(); + + let peripherals = Peripherals::take()?; + let sys_loop = EspSystemEventLoop::take()?; + let nvs = EspDefaultNvsPartition::take()?; + + let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?); + + info!("Running Thread..."); + + let _subscription = log_thread_sysloop(sys_loop.clone())?; + + let thread = EspThread::new(peripherals.modem, sys_loop.clone(), nvs, mounted_event_fs)?; + + thread.run()?; + + Ok(()) + } + + fn log_thread_sysloop( + sys_loop: EspSystemEventLoop, + ) -> Result, anyhow::Error> { + let subscription = sys_loop.subscribe::(|event| { + info!("Got: {:?}", event); + })?; + + Ok(subscription) + } +} diff --git a/examples/thread_br.rs b/examples/thread_br.rs new file mode 100644 index 00000000000..f130f68bfd6 --- /dev/null +++ b/examples/thread_br.rs @@ -0,0 +1,145 @@ +//! Example of a Thread Border Router. +//! +//! This example only works on MCUs that have BOTH Thread and Wifi capabilities, like the ESP32-C6. +//! +//! For other MCUs, you need to use at least one Thread-capable MCU like the ESP32-H2 (which only supports Thread), +//! and then instead of Wifi, you need to use Ethernet via SPI. +//! ... or use a pair of Thread-capable MCU (as the Thread RCP) and Wifi-capable MCU (as the Thread Host) and connect +//! them over UART or SPI. +//! +//! NOTE NOTE NOTE: +//! To build, you need to put the following in your `sdkconfig.defaults`: +//! ```text +//! # Generic Thread functionality +//! CONFIG_OPENTHREAD_ENABLED=y +//! +//! # Thread Border Router +//! CONFIG_OPENTHREAD_BORDER_ROUTER=y +//! CONFIG_LWIP_IPV6_NUM_ADDRESSES=12 +//! CONFIG_LWIP_NETIF_STATUS_CALLBACK=y +//! +//! # (These are also necessary for the Joiner feature) +//! CONFIG_MBEDTLS_CMAC_C=y +//! CONFIG_MBEDTLS_SSL_PROTO_DTLS=y +//! CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +//! CONFIG_MBEDTLS_ECJPAKE_C=y +//! ``` +//! +//! And also the following in your `Cargo.toml`: +//! ```toml +//! [[package.metadata.esp-idf-sys.extra_components]] +//! remote_component = { name = "espressif/mdns", version = "1.2" } +//! ``` + +#![allow(unexpected_cfgs)] + +fn main() -> anyhow::Result<()> { + #[cfg(i_have_done_all_of_the_above)] // Remove this `cfg` when you have done all of the above for the example to compile + #[cfg(esp32c6)] + router::main()?; + + // Remove this whole code block when you have done all of the above for the example to compile + #[cfg(not(i_have_done_all_of_the_above))] + { + println!("Please follow the instructions in the source code."); + } + + Ok(()) +} + +#[cfg(i_have_done_all_of_the_above)] // Remove this `cfg` when you have done all of the above for the example to compile +#[cfg(esp32c6)] +mod router { + use core::convert::TryInto; + + use std::sync::Arc; + + use esp_idf_svc::eventloop::EspSystemSubscription; + use log::info; + + use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration}; + + use esp_idf_svc::hal::prelude::Peripherals; + use esp_idf_svc::io::vfs::MountedEventfs; + use esp_idf_svc::log::EspLogger; + use esp_idf_svc::thread::{EspThread, ThreadEvent}; + use esp_idf_svc::wifi::{BlockingWifi, EspWifi}; + use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition}; + + const SSID: &str = env!("WIFI_SSID"); + const PASSWORD: &str = env!("WIFI_PASS"); + + pub fn main() -> anyhow::Result<()> { + esp_idf_svc::sys::link_patches(); + EspLogger::initialize_default(); + + let peripherals = Peripherals::take()?; + let sys_loop = EspSystemEventLoop::take()?; + let nvs = EspDefaultNvsPartition::take()?; + + let (wifi_modem, thread_modem, _) = peripherals.modem.split(); + + let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?); + + let mut wifi = BlockingWifi::wrap( + EspWifi::new(wifi_modem, sys_loop.clone(), Some(nvs.clone()))?, + sys_loop.clone(), + )?; + + connect_wifi(&mut wifi)?; + + let ip_info = wifi.wifi().sta_netif().get_ip_info()?; + + info!("Wifi DHCP info: {:?}", ip_info); + + info!("Running Thread..."); + + let _subscription = log_thread_sysloop(sys_loop.clone())?; + + let thread = EspThread::new_br( + thread_modem, + sys_loop, + nvs, + mounted_event_fs, + wifi.wifi().sta_netif(), + )?; + + thread.run()?; + + Ok(()) + } + + fn connect_wifi(wifi: &mut BlockingWifi>) -> anyhow::Result<()> { + let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration { + ssid: SSID.try_into().unwrap(), + bssid: None, + auth_method: AuthMethod::WPA2Personal, + password: PASSWORD.try_into().unwrap(), + channel: None, + ..Default::default() + }); + + wifi.set_configuration(&wifi_configuration)?; + + wifi.start()?; + info!("Wifi started"); + + wifi.connect()?; + info!("Wifi connected"); + + wifi.wait_netif_up()?; + info!("Wifi netif up"); + + Ok(()) + } + + fn log_thread_sysloop( + sys_loop: EspSystemEventLoop, + ) -> Result, anyhow::Error> { + let subscription = sys_loop.subscribe::(|event| { + info!("Got: {:?}", event); + })?; + + Ok(subscription) + } +} diff --git a/src/thread.rs b/src/thread.rs index acd871ae6fd..ddf1e45ed2e 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -3,7 +3,6 @@ // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) // - Prio B: API to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) // - Prio B: API to to enable the Commissioner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_COMMISSIONER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) -// - Prio B: Think of a minimal example // - Prio C: How to support the OpenThread CLI (useful for debugging) // - Prio C: Figure out what these do (bad/missing docu): // - CONFIG_OPENTHREAD_DNS_CLIENT (can this be enabled programmatically too - does not seem so, and why is this part of OpenThread and not the LwIP ipv6 stack?) @@ -650,6 +649,7 @@ impl<'d> ThreadDriver<'d, RCP> { /// Create a new Thread RCP driver instance utilizing a UART connection /// to another MCU running the Thread Host stack. + #[allow(clippy::too_many_arguments)] pub fn new_rcp_uart( modem: impl Peripheral

+ 'd, uart: impl Peripheral

+ 'd, From e3d4b300bb550d7fb14358a1eef85bcb80c1f3ae Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 20:29:04 +0000 Subject: [PATCH 40/60] Examples; Node and BorderRouter seem to work (do something) --- .cargo/config.toml | 4 +- .github/configs/sdkconfig.defaults | 28 ++- .github/workflows/ci.yml | 2 +- examples/thread.rs | 26 ++- examples/thread_br.rs | 44 ++-- src/thread.rs | 315 ++++++++++++++++++++--------- 6 files changed, 285 insertions(+), 134 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index deb707e6338..f8ab7ca0ad1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,5 @@ [build] -target = "riscv32imac-esp-espidf" +target = "riscv32imc-esp-espidf" #target = "xtensa-esp32-espidf" [target.xtensa-esp32-espidf] @@ -25,7 +25,7 @@ rustflags = ["--cfg", "espidf_time64"] [env] ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" ESP_IDF_VERSION = "v5.3.1" -MCU = "esp32c6" +MCU = "esp32c3" [unstable] build-std = ["std", "panic_abort"] diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index 7de71629014..bb040473505 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -43,12 +43,26 @@ CONFIG_LWIP_PPP_SUPPORT=y CONFIG_OPENTHREAD_ENABLED=y # Thread Border Router -CONFIG_OPENTHREAD_BORDER_ROUTER=y -CONFIG_LWIP_IPV6_NUM_ADDRESSES=12 -CONFIG_LWIP_NETIF_STATUS_CALLBACK=y +#CONFIG_OPENTHREAD_BORDER_ROUTER=y # These are also necessary for the Joiner feature -CONFIG_MBEDTLS_CMAC_C=y -CONFIG_MBEDTLS_SSL_PROTO_DTLS=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y -CONFIG_MBEDTLS_ECJPAKE_C=y +#CONFIG_MBEDTLS_CMAC_C=y +#CONFIG_MBEDTLS_SSL_PROTO_DTLS=y +#CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +#CONFIG_MBEDTLS_ECJPAKE_C=y + +# Border Router again, LWIP +#CONFIG_LWIP_IPV6_NUM_ADDRESSES=12 +#CONFIG_LWIP_NETIF_STATUS_CALLBACK=y +#CONFIG_LWIP_IPV6_FORWARD=y +#CONFIG_LWIP_MULTICAST_PING=y +#CONFIG_LWIP_NETIF_STATUS_CALLBACK=y +#CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y +#CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y +#CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM=y +#CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM=y +#CONFIG_LWIP_IPV6_AUTOCONFIG=y +#CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096 + +# Border Router again, mDNS +#CONFIG_MDNS_MULTIPLE_INSTANCE=y diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f62e5026471..aeb8450cbdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: matrix: target: - riscv32imc-esp-espidf -# - riscv32imac-esp-espidf + - riscv32imac-esp-espidf - xtensa-esp32-espidf - xtensa-esp32s2-espidf - xtensa-esp32s3-espidf diff --git a/examples/thread.rs b/examples/thread.rs index 7e1e7d0a82d..1efdbcec88e 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -4,43 +4,47 @@ //! NOTE: This example only works on MCUs that has Thread capabilities, like the ESP32-C6 or ESP32-H2. fn main() -> anyhow::Result<()> { + esp_idf_svc::sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + #[cfg(any(esp32h2, esp32c6))] - router::main()?; + example::main()?; #[cfg(not(any(esp32h2, esp32c6)))] - println!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2."); + log::info!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2."); Ok(()) } #[cfg(any(esp32h2, esp32c6))] -mod router { +mod example { use std::sync::Arc; - use esp_idf_svc::eventloop::EspSystemSubscription; use log::info; + use esp_idf_svc::eventloop::EspSystemSubscription; use esp_idf_svc::hal::prelude::Peripherals; use esp_idf_svc::io::vfs::MountedEventfs; - use esp_idf_svc::log::EspLogger; use esp_idf_svc::thread::{EspThread, ThreadEvent}; use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition}; pub fn main() -> anyhow::Result<()> { - esp_idf_svc::sys::link_patches(); - EspLogger::initialize_default(); - let peripherals = Peripherals::take()?; let sys_loop = EspSystemEventLoop::take()?; let nvs = EspDefaultNvsPartition::take()?; - let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?); + let mounted_event_fs = Arc::new(MountedEventfs::mount(6)?); - info!("Running Thread..."); + info!("Initializing Thread..."); let _subscription = log_thread_sysloop(sys_loop.clone())?; - let thread = EspThread::new(peripherals.modem, sys_loop.clone(), nvs, mounted_event_fs)?; + let mut thread = + EspThread::new(peripherals.modem, sys_loop.clone(), nvs, mounted_event_fs)?; + + thread.init()?; + + info!("Thread initialized, now running..."); thread.run()?; diff --git a/examples/thread_br.rs b/examples/thread_br.rs index f130f68bfd6..9374e5f4536 100644 --- a/examples/thread_br.rs +++ b/examples/thread_br.rs @@ -10,19 +10,32 @@ //! NOTE NOTE NOTE: //! To build, you need to put the following in your `sdkconfig.defaults`: //! ```text -//! # Generic Thread functionality //! CONFIG_OPENTHREAD_ENABLED=y //! //! # Thread Border Router //! CONFIG_OPENTHREAD_BORDER_ROUTER=y -//! CONFIG_LWIP_IPV6_NUM_ADDRESSES=12 -//! CONFIG_LWIP_NETIF_STATUS_CALLBACK=y //! -//! # (These are also necessary for the Joiner feature) +//! # These are also necessary for the Joiner feature //! CONFIG_MBEDTLS_CMAC_C=y //! CONFIG_MBEDTLS_SSL_PROTO_DTLS=y //! CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y //! CONFIG_MBEDTLS_ECJPAKE_C=y +//! +//! # Border Router again, lwIP +//! CONFIG_LWIP_IPV6_NUM_ADDRESSES=12 +//! CONFIG_LWIP_NETIF_STATUS_CALLBACK=y +//! CONFIG_LWIP_IPV6_FORWARD=y +//! CONFIG_LWIP_MULTICAST_PING=y +//! CONFIG_LWIP_NETIF_STATUS_CALLBACK=y +//! CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y +//! CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y +//! CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM=y +//! CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM=y +//! CONFIG_LWIP_IPV6_AUTOCONFIG=y +//! CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096 +//! +//! # Border Router again, mDNS +//! CONFIG_MDNS_MULTIPLE_INSTANCE=y //! ``` //! //! And also the following in your `Cargo.toml`: @@ -34,14 +47,17 @@ #![allow(unexpected_cfgs)] fn main() -> anyhow::Result<()> { + esp_idf_svc::sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + #[cfg(i_have_done_all_of_the_above)] // Remove this `cfg` when you have done all of the above for the example to compile #[cfg(esp32c6)] - router::main()?; + example::main()?; // Remove this whole code block when you have done all of the above for the example to compile #[cfg(not(i_have_done_all_of_the_above))] { - println!("Please follow the instructions in the source code."); + log::info!("Please follow the instructions in the source code."); } Ok(()) @@ -49,19 +65,18 @@ fn main() -> anyhow::Result<()> { #[cfg(i_have_done_all_of_the_above)] // Remove this `cfg` when you have done all of the above for the example to compile #[cfg(esp32c6)] -mod router { +mod example { use core::convert::TryInto; use std::sync::Arc; - use esp_idf_svc::eventloop::EspSystemSubscription; use log::info; use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration}; + use esp_idf_svc::eventloop::EspSystemSubscription; use esp_idf_svc::hal::prelude::Peripherals; use esp_idf_svc::io::vfs::MountedEventfs; - use esp_idf_svc::log::EspLogger; use esp_idf_svc::thread::{EspThread, ThreadEvent}; use esp_idf_svc::wifi::{BlockingWifi, EspWifi}; use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition}; @@ -70,9 +85,6 @@ mod router { const PASSWORD: &str = env!("WIFI_PASS"); pub fn main() -> anyhow::Result<()> { - esp_idf_svc::sys::link_patches(); - EspLogger::initialize_default(); - let peripherals = Peripherals::take()?; let sys_loop = EspSystemEventLoop::take()?; let nvs = EspDefaultNvsPartition::take()?; @@ -92,11 +104,11 @@ mod router { info!("Wifi DHCP info: {:?}", ip_info); - info!("Running Thread..."); + info!("Initializing Thread..."); let _subscription = log_thread_sysloop(sys_loop.clone())?; - let thread = EspThread::new_br( + let mut thread = EspThread::new_br( thread_modem, sys_loop, nvs, @@ -104,6 +116,10 @@ mod router { wifi.wifi().sta_netif(), )?; + thread.init()?; + + info!("Thread initialized, now running..."); + thread.run()?; Ok(()) diff --git a/src/thread.rs b/src/thread.rs index ddf1e45ed2e..c04fb65d283 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -218,8 +218,12 @@ impl From for Role { /// of course only supported with MCUs that do have a Thread radio, like esp32c2 and esp32c6 /// - Host mode: The driver operates as a host, and if the chip does not have a Thread radio /// it has to be connected via SPI or USB to a chip which runs the Thread stack in RCP mode -pub struct ThreadDriver<'d, T> { +pub struct ThreadDriver<'d, T> +where + T: Mode, +{ cfg: esp_openthread_platform_config_t, + initialized: bool, cs: CriticalSection, //_subscription: EspSubscription<'static, System>, _nvs: EspDefaultNvsPartition, @@ -240,6 +244,7 @@ impl<'d> ThreadDriver<'d, Host> { ) -> Result { Ok(Self { cfg: Self::host_native_cfg(modem), + initialized: false, cs: CriticalSection::new(), _nvs: nvs, _mounted_event_fs: mounted_event_fs, @@ -266,6 +271,7 @@ impl<'d> ThreadDriver<'d, Host> { ) -> Result { Ok(Self { cfg: Self::host_spi_cfg(spi, mosi, miso, sclk, cs, intr, config), + initialized: false, cs: CriticalSection::new(), _nvs: nvs, _mounted_event_fs: mounted_event_fs, @@ -287,6 +293,7 @@ impl<'d> ThreadDriver<'d, Host> { ) -> Result { Ok(Self { cfg: Self::host_uart_cfg(uart, tx, rx, config), + initialized: false, cs: CriticalSection::new(), _nvs: nvs, _mounted_event_fs: mounted_event_fs, @@ -297,6 +304,8 @@ impl<'d> ThreadDriver<'d, Host> { /// Retrieve the current role of the device in the Thread network pub fn role(&self) -> Result { + self.check_init()?; + let _lock = OtLock::acquire()?; Ok(unsafe { otThreadGetDeviceRole(esp_openthread_get_instance()) }.into()) @@ -339,6 +348,8 @@ impl<'d> ThreadDriver<'d, Host> { /// during build (via `sdkconfig*`) #[cfg(not(esp_idf_version_major = "4"))] pub fn set_tod_from_cfg(&self) -> Result<(), EspError> { + self.check_init()?; + ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) }) } @@ -347,6 +358,8 @@ impl<'d> ThreadDriver<'d, Host> { /// The callback will be called for each found network /// At the end of the scan, the callback will be called with `None` pub fn scan)>(&self, callback: F) -> Result<(), EspError> { + self.check_init()?; + let _lock = OtLock::acquire()?; #[allow(clippy::type_complexity)] @@ -356,8 +369,8 @@ impl<'d> ThreadDriver<'d, Host> { ot_esp!(unsafe { otLinkActiveScan( esp_openthread_get_instance(), - 0, - 0, + 0xffff_ffffu32, // All channels + 200, // ms scan per channel Some(Self::on_active_scan_result), callback.as_mut() as *mut _ as *mut c_void, ) @@ -368,6 +381,8 @@ impl<'d> ThreadDriver<'d, Host> { /// Check if an active scan is in progress pub fn is_scan_in_progress(&self) -> Result { + self.check_init()?; + let _lock = OtLock::acquire()?; Ok(unsafe { otLinkIsActiveScanInProgress(esp_openthread_get_instance()) }) @@ -381,6 +396,8 @@ impl<'d> ThreadDriver<'d, Host> { &self, callback: F, ) -> Result<(), EspError> { + self.check_init()?; + let _lock = OtLock::acquire()?; #[allow(clippy::type_complexity)] @@ -390,8 +407,8 @@ impl<'d> ThreadDriver<'d, Host> { ot_esp!(unsafe { otLinkEnergyScan( esp_openthread_get_instance(), - 0, - 0, + 0xffff_ffffu32, // All channels + 200, // ms scan per channel Some(Self::on_energy_scan_result), callback.as_mut() as *mut _ as *mut c_void, ) @@ -402,6 +419,8 @@ impl<'d> ThreadDriver<'d, Host> { /// Check if an energy scan is in progress pub fn is_energy_scan_in_progress(&self) -> Result { + self.check_init()?; + let _lock = OtLock::acquire()?; Ok(unsafe { otLinkIsEnergyScanInProgress(esp_openthread_get_instance()) }) @@ -570,6 +589,8 @@ impl<'d> ThreadDriver<'d, Host> { } fn internal_tod(&self, active: bool, buf: &mut [u8]) -> Result { + self.check_init()?; + let _lock = OtLock::acquire()?; let mut tlvs = MaybeUninit::::uninit(); // TODO: Large buffer @@ -594,6 +615,8 @@ impl<'d> ThreadDriver<'d, Host> { } fn internal_set_tod(&self, active: bool, data: &[u8]) -> Result<(), EspError> { + self.check_init()?; + let _lock = OtLock::acquire()?; let mut tlvs = MaybeUninit::::uninit(); // TODO: Large buffer @@ -639,6 +662,7 @@ impl<'d> ThreadDriver<'d, RCP> { ) -> Result { Ok(Self { cfg: Self::rcp_spi_cfg(modem, spi, mosi, miso, sclk, cs, intr), + initialized: false, cs: CriticalSection::new(), _nvs: nvs, _mounted_event_fs: mounted_event_fs, @@ -662,6 +686,7 @@ impl<'d> ThreadDriver<'d, RCP> { ) -> Result { Ok(Self { cfg: Self::rcp_uart_cfg(modem, uart, tx, rx, config), + initialized: false, cs: CriticalSection::new(), _nvs: nvs, _mounted_event_fs: mounted_event_fs, @@ -802,21 +827,74 @@ where pub fn run(&self) -> Result<(), EspError> { // TODO: Figure out how to stop running + self.check_init()?; + let _lock = self.cs.enter(); - esp!(unsafe { esp_openthread_init(&self.cfg) })?; + debug!("Driver running"); + + let result = esp!(unsafe { esp_openthread_launch_mainloop() }); + + debug!("Driver stopped running"); + + result + } - debug!("Driver initialized"); + /// Initialize the Thread driver + /// Note that this needs to be done before calling any other driver method. + /// + /// Does nothing if the driver is already initialized. + pub fn init(&mut self) -> Result<(), EspError> { + if self.initialized { + return Ok(()); + } + + let _lock = self.cs.enter(); + + esp!(unsafe { esp_openthread_init(&self.cfg) })?; T::init(); - let result = esp!(unsafe { esp_openthread_launch_mainloop() }); + self.initialized = true; + + Ok(()) + } + + // Deinitialize the Thread driver + pub fn deinit(&mut self) -> Result<(), EspError> { + if !self.initialized { + return Ok(()); + } + + let _lock = self.cs.enter(); esp!(unsafe { esp_openthread_deinit() })?; - debug!("Driver deinitialized"); + self.initialized = false; - result + Ok(()) + } + + /// Return `true` if the driver is already initialized + pub fn is_init(&self) -> Result { + Ok(self.initialized) + } + + fn check_init(&self) -> Result<(), EspError> { + if !self.initialized { + Err(EspError::from_infallible::())?; + } + + Ok(()) + } +} + +impl<'d, T> Drop for ThreadDriver<'d, T> +where + T: Mode, +{ + fn drop(&mut self) { + self.deinit().unwrap(); } } @@ -841,23 +919,23 @@ impl Drop for OtLock { } /// Trait shared between the modes of operation of the `EspThread` instance -pub trait NetifMode<'d> { - fn driver(&self) -> &ThreadDriver<'d, Host>; - fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host>; +pub trait NetifMode { + fn init(&mut self) -> Result<(), EspError>; + fn deinit(&mut self) -> Result<(), EspError>; } /// The regular mode of operation for the `EspThread` instance /// /// This is the only available mode if the Border Router functionality in ESP-IDF is not enabled -pub struct Node<'d>(ThreadDriver<'d, Host>); +pub struct Node(()); -impl<'d> NetifMode<'d> for Node<'d> { - fn driver(&self) -> &ThreadDriver<'d, Host> { - &self.0 +impl NetifMode for Node { + fn init(&mut self) -> Result<(), EspError> { + Ok(()) } - fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { - &mut self.0 + fn deinit(&mut self) -> Result<(), EspError> { + Ok(()) } } @@ -867,7 +945,7 @@ impl<'d> NetifMode<'d> for Node<'d> { esp_idf_openthread_border_router, not(any(esp32h2, esp32h4)) ))] -pub struct BorderRouter<'d, N>(ThreadDriver<'d, Host>, N) +pub struct BorderRouter(N) where N: core::borrow::Borrow; @@ -876,11 +954,8 @@ where esp_idf_openthread_border_router, not(any(esp32h2, esp32h4)) ))] -impl<'d, N> BorderRouter<'d, N> -where - N: core::borrow::Borrow, -{ - fn new(driver: ThreadDriver<'d, Host>, netif: N) -> Result { +impl> NetifMode for BorderRouter { + fn init(&mut self) -> Result<(), EspError> { #[cfg(esp_idf_version_major = "4")] { esp!(unsafe { esp_openthread_border_router_init(netif.borrow().handle()) })?; @@ -888,44 +963,20 @@ where #[cfg(not(esp_idf_version_major = "4"))] { - unsafe { - esp_openthread_set_backbone_netif(netif.borrow().handle()); - } - esp!(unsafe { esp_openthread_border_router_init() })?; } debug!("Border router initialized"); - Ok(Self(driver, netif)) + Ok(()) } -} -#[cfg(all( - esp_idf_comp_esp_netif_enabled, - esp_idf_openthread_border_router, - not(any(esp32h2, esp32h4)) -))] -impl<'d, N: core::borrow::Borrow> Drop for BorderRouter<'d, N> { - fn drop(&mut self) { - esp!(unsafe { esp_openthread_border_router_deinit() }).unwrap(); - - debug!("Border router dropped"); - } -} + fn deinit(&mut self) -> Result<(), EspError> { + esp!(unsafe { esp_openthread_border_router_deinit() })?; -#[cfg(all( - esp_idf_comp_esp_netif_enabled, - esp_idf_openthread_border_router, - not(any(esp32h2, esp32h4)) -))] -impl<'d, N: core::borrow::Borrow> NetifMode<'d> for BorderRouter<'d, N> { - fn driver(&self) -> &ThreadDriver<'d, Host> { - &self.0 - } + debug!("Border router deinitialized"); - fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { - &mut self.0 + Ok(()) } } @@ -940,13 +991,18 @@ impl<'d, N: core::borrow::Borrow> NetifMode<'d> for BorderRouter<'d, N /// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the /// ESP IDF Thread radio. #[cfg(esp_idf_comp_esp_netif_enabled)] -pub struct EspThread { +pub struct EspThread<'d, T> +where + T: NetifMode, +{ netif: EspNetif, - driver: T, + driver: ThreadDriver<'d, Host>, + netif_initialized: bool, + mode: T, } #[cfg(esp_idf_comp_esp_netif_enabled)] -impl<'d> EspThread> { +impl<'d> EspThread<'d, Node> { /// Create a new `EspThread` instance utilizing the native Thread radio on the MCU #[cfg(esp_idf_soc_ieee802154_supported)] pub fn new( @@ -1016,16 +1072,15 @@ impl<'d> EspThread> { } /// Wrap an already created Thread L2 driver instance and a network interface - pub fn wrap_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result { - let mut this = Self { - driver: Node(driver), - netif, - }; - - this.attach_netif()?; - Self::enable_network(true)?; + pub fn wrap_all(mut driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result { + driver.deinit()?; - Ok(this) + Ok(Self { + driver, + netif, + netif_initialized: false, + mode: Node(()), + }) } } @@ -1034,7 +1089,7 @@ impl<'d> EspThread> { esp_idf_openthread_border_router, not(any(esp32h2, esp32h4)) ))] -impl<'d, N> EspThread> +impl<'d, N> EspThread<'d, BorderRouter> where N: core::borrow::Borrow, { @@ -1115,35 +1170,38 @@ where /// Wrap an already created Thread L2 driver instance, a network interface to be used for the /// Thread network, and a backbone network interface to the outside world pub fn wrap_br_all( - driver: ThreadDriver<'d, Host>, + mut driver: ThreadDriver<'d, Host>, netif: EspNetif, backbone_netif: N, ) -> Result { - let mut this = Self { - driver: BorderRouter::new(driver, backbone_netif)?, - netif, - }; + driver.deinit()?; - this.attach_netif()?; - Self::enable_network(true)?; + unsafe { + esp_openthread_set_backbone_netif(backbone_netif.borrow().handle()); + } - Ok(this) + Ok(Self { + driver, + netif, + netif_initialized: false, + mode: BorderRouter(backbone_netif), + }) } } #[cfg(esp_idf_comp_esp_netif_enabled)] -impl<'d, T> EspThread +impl<'d, T> EspThread<'d, T> where - T: NetifMode<'d>, + T: NetifMode, { /// Return the underlying [`ThreadDriver`] pub fn driver(&self) -> &ThreadDriver<'d, Host> { - self.driver.driver() + &self.driver } /// Return the underlying [`ThreadDriver`], as mutable - pub fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { - self.driver.driver_mut() + fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> { + &mut self.driver } /// Return the underlying [`EspNetif`] @@ -1156,6 +1214,30 @@ where &mut self.netif } + /// Initialize the Thread stack + /// + /// This should be called after the `EspThread` instance is created + /// and before any other operation is performed on it + /// + /// If the stack is already initialized, this method does nothing + pub fn init(&mut self) -> Result<(), EspError> { + self.driver_mut().init()?; + self.init_netif() + } + + /// Deinitialize the Thread stack + /// + /// If the stack is already deinitialized, this method does nothing + pub fn deinit(&mut self) -> Result<(), EspError> { + self.deinit_netif()?; + self.driver_mut().deinit() + } + + /// Return `true` if the Thread stack is already initialized + pub fn is_init(&self) -> Result { + Ok(self.netif_initialized && self.driver().is_init()?) + } + /// Retrieve the current role of the device in the Thread network pub fn role(&self) -> Result { self.driver().role() @@ -1235,56 +1317,91 @@ where /// The current thread would block while the stack is running /// Note that the stack will only exit if an error occurs pub fn run(&self) -> Result<(), EspError> { - self.driver.driver().run() + self.driver().run() } /// Replace the network interface with the provided one and return the /// existing network interface. pub fn swap_netif(&mut self, netif: EspNetif) -> Result { - Self::enable_network(false)?; - self.detach_netif()?; + let initialized = self.is_init()?; + + self.deinit()?; let old = core::mem::replace(&mut self.netif, netif); - self.attach_netif()?; - Self::enable_network(true)?; + if initialized { + self.init()?; + } Ok(old) } - fn attach_netif(&mut self) -> Result<(), EspError> { + fn init_netif(&mut self) -> Result<(), EspError> { + if self.netif_initialized { + return Ok(()); + } + + let _lock = OtLock::acquire()?; + + self.ot_attach_netif()?; + self.mode.init()?; + + Self::ot_enable_network(true)?; + + Ok(()) + } + + fn deinit_netif(&mut self) -> Result<(), EspError> { + if !self.netif_initialized { + return Ok(()); + } + + let _lock = OtLock::acquire()?; + + Self::ot_enable_network(false)?; + + self.mode.deinit()?; + self.ot_detach_netif()?; + + Ok(()) + } + + // NOTE: Methods starting with `ot_` have to be called only when the OpenThread lock is held + fn ot_attach_netif(&mut self) -> Result<(), EspError> { esp!(unsafe { esp_netif_attach( self.netif.handle() as *mut _, - esp_openthread_netif_glue_init(&self.driver.driver().cfg), + esp_openthread_netif_glue_init(&self.driver().cfg), ) })?; Ok(()) } -} -#[cfg(esp_idf_comp_esp_netif_enabled)] -impl EspThread { - fn enable_network(enabled: bool) -> Result<(), EspError> { - ot_esp!(unsafe { otIp6SetEnabled(esp_openthread_get_instance(), enabled) })?; - ot_esp!(unsafe { otThreadSetEnabled(esp_openthread_get_instance(), enabled) })?; + fn ot_detach_netif(&mut self) -> Result<(), EspError> { + unsafe { + esp_openthread_netif_glue_deinit(); + } Ok(()) } - fn detach_netif(&mut self) -> Result<(), EspError> { - unsafe { - esp_openthread_netif_glue_deinit(); - } + fn ot_enable_network(enabled: bool) -> Result<(), EspError> { + ot_esp!(unsafe { otIp6SetEnabled(esp_openthread_get_instance(), enabled) })?; + ot_esp!(unsafe { otThreadSetEnabled(esp_openthread_get_instance(), enabled) })?; + + debug!("Network enabled={enabled}"); Ok(()) } } -impl Drop for EspThread { +impl<'d, T> Drop for EspThread<'d, T> +where + T: NetifMode, +{ fn drop(&mut self) { - let _ = self.detach_netif(); + self.deinit_netif().unwrap(); } } From bc23144609dc41cc4a9905c7b0a305a8516d2ca3 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 20:32:23 +0000 Subject: [PATCH 41/60] Restore default build conf --- .cargo/config.toml | 2 +- .github/workflows/ci.yml | 1 + Cargo.toml | 5 +---- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index f8ab7ca0ad1..c6cff6f9570 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -24,7 +24,7 @@ rustflags = ["--cfg", "espidf_time64"] [env] ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" -ESP_IDF_VERSION = "v5.3.1" +ESP_IDF_VERSION = "v5.2.2" MCU = "esp32c3" [unstable] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aeb8450cbdd..f7565dd46ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: idf-version: - v4.4.7 - v5.1.2 + - v5.2 steps: - name: Setup | Checkout uses: actions/checkout@v3 diff --git a/Cargo.toml b/Cargo.toml index 1d1f46002c2..6873bb2650b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,8 @@ harness = false esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal" } esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys" } -[[package.metadata.esp-idf-sys.extra_components]] -remote_component = { name = "espressif/mdns", version = "1.2" } - [features] -default = ["std", "binstart", "experimental"] +default = ["std", "binstart"] std = ["alloc", "log/std", "esp-idf-hal/std", "embedded-svc/std", "futures-io"] alloc = ["esp-idf-hal/alloc", "embedded-svc/alloc", "uncased/alloc"] From b7b460092b7cdae94e326ff3a6a8eb483007b297 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 19 Sep 2024 20:33:38 +0000 Subject: [PATCH 42/60] Restore default build conf --- .cargo/config.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index c6cff6f9570..0656ef2bf6a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -25,7 +25,6 @@ rustflags = ["--cfg", "espidf_time64"] [env] ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" ESP_IDF_VERSION = "v5.2.2" -MCU = "esp32c3" [unstable] build-std = ["std", "panic_abort"] From 924df0e688f6048f40d59a3f7f33be6ae0c6c099 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 20 Sep 2024 06:38:24 +0000 Subject: [PATCH 43/60] Reduce the event_fd handles to 4 to match the other Thread example --- examples/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/thread.rs b/examples/thread.rs index 1efdbcec88e..b965b04c1bc 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -33,7 +33,7 @@ mod example { let sys_loop = EspSystemEventLoop::take()?; let nvs = EspDefaultNvsPartition::take()?; - let mounted_event_fs = Arc::new(MountedEventfs::mount(6)?); + let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?); info!("Initializing Thread..."); From 89bdbcbac5fac93828497c2db5e14ae7599bf157 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 20 Sep 2024 06:39:32 +0000 Subject: [PATCH 44/60] ESP-IDF 4.4 comnpat --- src/thread.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/thread.rs b/src/thread.rs index c04fb65d283..bb9264d7a0a 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1176,6 +1176,7 @@ where ) -> Result { driver.deinit()?; + #[cfg(not(esp_idf_version_major = "4"))] unsafe { esp_openthread_set_backbone_netif(backbone_netif.borrow().handle()); } From b7e855e28523ca68971cc16284f031969d7809ec Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 20 Sep 2024 06:45:57 +0000 Subject: [PATCH 45/60] The driver is actually Send and Sync --- src/thread.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index bb9264d7a0a..70eba6af318 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -898,6 +898,9 @@ where } } +unsafe impl<'d, T> Send for ThreadDriver<'d, T> where T: Mode {} +unsafe impl<'d, T> Sync for ThreadDriver<'d, T> where T: Mode {} + struct OtLock(PhantomData<*const ()>); impl OtLock { @@ -1406,6 +1409,9 @@ where } } +unsafe impl<'d, T> Send for EspThread<'d, T> where T: NetifNode {} +unsafe impl<'d, T> Sync for EspThread<'d, T> where T: NetifNode {} + /// Events reported by the Thread stack on the system event loop #[derive(Copy, Clone, Debug)] pub enum ThreadEvent { From d1c9eb935c6ae82fd046a414653d9a0a25103dda Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 20 Sep 2024 06:46:55 +0000 Subject: [PATCH 46/60] Protect code when netif is not enabled --- src/thread.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index 70eba6af318..1eb0fd4a59c 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1400,6 +1400,7 @@ where } } +#[cfg(esp_idf_comp_esp_netif_enabled)] impl<'d, T> Drop for EspThread<'d, T> where T: NetifMode, @@ -1409,7 +1410,9 @@ where } } +#[cfg(esp_idf_comp_esp_netif_enabled)] unsafe impl<'d, T> Send for EspThread<'d, T> where T: NetifNode {} +#[cfg(esp_idf_comp_esp_netif_enabled)] unsafe impl<'d, T> Sync for EspThread<'d, T> where T: NetifNode {} /// Events reported by the Thread stack on the system event loop From 99d22d8be4133e7534b3fcf759d438451e8a3dea Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 20 Sep 2024 07:01:54 +0000 Subject: [PATCH 47/60] Typo --- src/thread.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 1eb0fd4a59c..dcc41622164 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1411,9 +1411,9 @@ where } #[cfg(esp_idf_comp_esp_netif_enabled)] -unsafe impl<'d, T> Send for EspThread<'d, T> where T: NetifNode {} +unsafe impl<'d, T> Send for EspThread<'d, T> where T: NetifMode {} #[cfg(esp_idf_comp_esp_netif_enabled)] -unsafe impl<'d, T> Sync for EspThread<'d, T> where T: NetifNode {} +unsafe impl<'d, T> Sync for EspThread<'d, T> where T: NetifMode {} /// Events reported by the Thread stack on the system event loop #[derive(Copy, Clone, Debug)] From 6e9f93e2b727fddb6e347932b52508670da67375 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 20 Sep 2024 13:38:54 +0000 Subject: [PATCH 48/60] Raw IPv6 packets RX/TX --- src/thread.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/src/thread.rs b/src/thread.rs index dcc41622164..f8bc13ecb66 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -180,6 +180,7 @@ impl<'a> EnergyScanResult<'a> { } } +/// The current role of the device in the Thread network #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Role { Disabled, @@ -203,6 +204,40 @@ impl From for Role { } } +/// The Ipv6 packet received from Thread via the `ThreadDriver::set_rx_callback` method +pub struct Ipv6Packet<'a>(&'a otMessage); + +impl<'a> Ipv6Packet<'a> { + pub fn raw(&self) -> &otMessage { + self.0 + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + unsafe { otMessageGetLength(self.0) as _ } + } + + pub fn offset(&self) -> usize { + unsafe { otMessageGetOffset(self.0) as _ } + } + + pub fn read(&self, offset: usize, buf: &mut [u8]) -> usize { + let len = self.len(); + + unsafe { otMessageRead(self.0, offset as _, buf.as_mut_ptr() as *mut _, len as _) as _ } + } +} + +/// The incoming Ipv6 data received from Thread via the `ThreadDriver::set_rx_callback` method +pub enum Ipv6Incoming<'a> { + /// A notification that an IPv6 address was added to the device + AddressAdded(core::net::Ipv6Addr), + /// A notification that an IPv6 address was removed from the device + AddressRemoved(core::net::Ipv6Addr), + /// An incoming raw IPv6 packet + Data(Ipv6Packet<'a>), +} + /// This struct provides a safe wrapper over the ESP IDF Thread C driver. /// /// The driver works on Layer 2 (Data Link) in the OSI model, in that it provides @@ -225,6 +260,8 @@ where cfg: esp_openthread_platform_config_t, initialized: bool, cs: CriticalSection, + #[allow(clippy::type_complexity)] + callback: Option>>, //_subscription: EspSubscription<'static, System>, _nvs: EspDefaultNvsPartition, _mounted_event_fs: Arc, @@ -246,6 +283,7 @@ impl<'d> ThreadDriver<'d, Host> { cfg: Self::host_native_cfg(modem), initialized: false, cs: CriticalSection::new(), + callback: None, _nvs: nvs, _mounted_event_fs: mounted_event_fs, _mode: Host(()), @@ -273,6 +311,7 @@ impl<'d> ThreadDriver<'d, Host> { cfg: Self::host_spi_cfg(spi, mosi, miso, sclk, cs, intr, config), initialized: false, cs: CriticalSection::new(), + callback: None, _nvs: nvs, _mounted_event_fs: mounted_event_fs, _mode: Host(()), @@ -295,6 +334,7 @@ impl<'d> ThreadDriver<'d, Host> { cfg: Self::host_uart_cfg(uart, tx, rx, config), initialized: false, cs: CriticalSection::new(), + callback: None, _nvs: nvs, _mounted_event_fs: mounted_event_fs, _mode: Host(()), @@ -426,6 +466,147 @@ impl<'d> ThreadDriver<'d, Host> { Ok(unsafe { otLinkIsEnergyScanInProgress(esp_openthread_get_instance()) }) } + /// Send an Ipv6 raw packet over Thread + pub fn tx(&self, packet: &[u8]) -> Result<(), EspError> { + self.check_init()?; + + let _lock = OtLock::acquire()?; + + let message = + unsafe { otIp6NewMessage(esp_openthread_get_instance(), core::ptr::null_mut()) }; + if message.is_null() { + Err(EspError::from_infallible::())?; + } + + let result = ot_esp!(unsafe { + otMessageAppend(message, packet.as_ptr() as *const _, packet.len() as _) + }) + .and_then(|_| ot_esp!(unsafe { otIp6Send(esp_openthread_get_instance(), message) })); + + unsafe { otMessageFree(message) }; + + result + } + + /// Set a callback function for receiving Ipv6 raw packets from Thread + pub fn set_rx_callback(&mut self, callback: Option) -> Result<(), EspError> + where + R: FnMut(Ipv6Incoming) + Send + 'static, + { + self.internal_set_rx_callback(callback) + } + + /// Set a callback function for receiving Ipv6 raw packets from Thread + /// + /// # Safety + /// + /// This method - in contrast to method `set_rx_callback` - allows the user to pass + /// non-static callback/closure. This enables users to borrow + /// - in the closure - variables that live on the stack - or more generally - in the same + /// scope where the service is created. + /// + /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the service, + /// as that would immediately lead to an UB (crash). + /// Also note that forgetting the service might happen with `Rc` and `Arc` + /// when circular references are introduced: https://github.com/rust-lang/rust/issues/24456 + /// + /// The reason is that the closure is actually sent to a hidden ESP IDF thread. + /// This means that if the service is forgotten, Rust is free to e.g. unwind the stack + /// and the closure now owned by this other thread will end up with references to variables that no longer exist. + /// + /// The destructor of the service takes care - prior to the service being dropped and e.g. + /// the stack being unwind - to remove the closure from the hidden thread and destroy it. + /// Unfortunately, when the service is forgotten, the un-subscription does not happen + /// and invalid references are left dangling. + /// + /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types + /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor). + pub fn set_nonstatic_rx_callback(&mut self, callback: Option) -> Result<(), EspError> + where + R: FnMut(Ipv6Incoming) + Send + 'd, + { + self.internal_set_rx_callback(callback) + } + + fn internal_set_rx_callback(&mut self, callback: Option) -> Result<(), EspError> + where + R: FnMut(Ipv6Incoming) + Send + 'd, + { + self.deinit()?; + + let _lock = OtLock::acquire()?; + + if let Some(callback) = callback { + #[allow(clippy::type_complexity)] + let callback: Box> = + Box::new(Box::new(callback)); + + #[allow(clippy::type_complexity)] + let mut callback: Box> = + unsafe { core::mem::transmute(callback) }; + + let callback_ptr = callback.as_mut() as *mut _ as *mut c_void; + + self.callback = Some(callback); + + unsafe { + otIp6SetAddressCallback( + esp_openthread_get_instance(), + Some(Self::on_address), + callback_ptr, + ); + otIp6SetReceiveCallback( + esp_openthread_get_instance(), + Some(Self::on_packet), + callback_ptr, + ); + otIp6SetReceiveFilterEnabled(esp_openthread_get_instance(), true); + + // TODO otIcmp6SetEchoMode(esp_openthread_get_instance(), OT_ICMP6_ECHO_HANDLER_RLOC_ALOC_ONLY); + } + } else { + unsafe { + otIp6SetAddressCallback(esp_openthread_get_instance(), None, core::ptr::null_mut()); + otIp6SetReceiveCallback(esp_openthread_get_instance(), None, core::ptr::null_mut()); + otIp6SetReceiveFilterEnabled(esp_openthread_get_instance(), true); + // TODO otIcmp6SetEchoMode(esp_openthread_get_instance(), OT_ICMP6_ECHO_HANDLER_RLOC_ALOC_ONLY); + } + + self.callback = None; + } + + Ok(()) + } + + unsafe extern "C" fn on_address( + address_info: *const otIp6AddressInfo, + is_added: bool, + context: *mut c_void, + ) { + let callback = unsafe { (context as *mut Box).as_mut() }.unwrap(); + + let address_info = unsafe { address_info.as_ref() }.unwrap(); + let ot_address = unsafe { address_info.mAddress.as_ref() }.unwrap(); + + let address = core::net::Ipv6Addr::from(ot_address.mFields.m8); + + if is_added { + callback(Ipv6Incoming::AddressAdded(address)); + } else { + callback(Ipv6Incoming::AddressRemoved(address)); + } + } + + unsafe extern "C" fn on_packet(message: *mut otMessage, context: *mut c_void) { + let callback = unsafe { (context as *mut Box).as_mut() }.unwrap(); + + callback(Ipv6Incoming::Data(Ipv6Packet( + unsafe { message.as_ref() }.unwrap(), + ))); + + otMessageFree(message); + } + unsafe extern "C" fn on_active_scan_result( result: *mut otActiveScanResult, context: *mut c_void, @@ -664,6 +845,7 @@ impl<'d> ThreadDriver<'d, RCP> { cfg: Self::rcp_spi_cfg(modem, spi, mosi, miso, sclk, cs, intr), initialized: false, cs: CriticalSection::new(), + callback: None, _nvs: nvs, _mounted_event_fs: mounted_event_fs, _mode: RCP(()), @@ -688,6 +870,7 @@ impl<'d> ThreadDriver<'d, RCP> { cfg: Self::rcp_uart_cfg(modem, uart, tx, rx, config), initialized: false, cs: CriticalSection::new(), + callback: None, _nvs: nvs, _mounted_event_fs: mounted_event_fs, _mode: RCP(()), From 26335c4d62158bca79ef90cffca5c1cfa5759ae3 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 21 Sep 2024 09:32:43 +0000 Subject: [PATCH 49/60] Set log level; option to initialize OT CLI --- src/thread.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/thread.rs b/src/thread.rs index f8bc13ecb66..fe4b73ed0f0 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -3,7 +3,6 @@ // - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) // - Prio B: API to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) // - Prio B: API to to enable the Commissioner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_COMMISSIONER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) -// - Prio C: How to support the OpenThread CLI (useful for debugging) // - Prio C: Figure out what these do (bad/missing docu): // - CONFIG_OPENTHREAD_DNS_CLIENT (can this be enabled programmatically too - does not seem so, and why is this part of OpenThread and not the LwIP ipv6 stack?) // - CONFIG_OPENTHREAD_DIAG @@ -351,6 +350,31 @@ impl<'d> ThreadDriver<'d, Host> { Ok(unsafe { otThreadGetDeviceRole(esp_openthread_get_instance()) }.into()) } + /// Initialize the Thread command-line interface (CLI) for debugging purposes. + /// + /// NOTE: This function can only be called once. + #[cfg(esp_idf_openthread_cli)] + pub fn init_cli(&mut self) -> Result<(), EspError> { + self.init()?; + + // TODO: Can only be called once; track this + + unsafe { + esp_openthread_cli_init(); + } + + #[cfg(esp_idf_openthread_cli_esp_extension)] + unsafe { + esp_cli_custom_command_init(); + } + + unsafe { + esp_openthread_cli_create_task(); + } + + Ok(()) + } + /// Retrieve the active TOD (Thread Operational Dataset) in the user-supplied buffer /// /// Return the size of the TOD data written to the buffer @@ -1036,6 +1060,10 @@ where esp!(unsafe { esp_openthread_init(&self.cfg) })?; + unsafe { + otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL as _); + } + T::init(); self.initialized = true; From 6d7adc837fbc9c1febc7134701c018d1bf1eb9ec Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 21 Sep 2024 10:49:38 +0000 Subject: [PATCH 50/60] Wifi Coexist APIs --- src/thread.rs | 66 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index fe4b73ed0f0..efc9668b079 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1027,26 +1027,6 @@ where task_queue_size: 10, }; - /// Run the Thread stack - /// - /// The current thread would block while the stack is running - /// Note that the stack will only exit if an error occurs - pub fn run(&self) -> Result<(), EspError> { - // TODO: Figure out how to stop running - - self.check_init()?; - - let _lock = self.cs.enter(); - - debug!("Driver running"); - - let result = esp!(unsafe { esp_openthread_launch_mainloop() }); - - debug!("Driver stopped running"); - - result - } - /// Initialize the Thread driver /// Note that this needs to be done before calling any other driver method. /// @@ -1091,6 +1071,31 @@ where Ok(self.initialized) } + /// Initialize the coexistence between the Thread and Wifi radios + pub fn init_wifi_coex(&mut self) -> Result<(), EspError> { + Self::internal_init_wifi_coex() + } + + /// Run the Thread stack + /// + /// The current thread would block while the stack is running + /// Note that the stack will only exit if an error occurs + pub fn run(&self) -> Result<(), EspError> { + // TODO: Figure out how to stop running + + self.check_init()?; + + let _lock = self.cs.enter(); + + debug!("Driver running"); + + let result = esp!(unsafe { esp_openthread_launch_mainloop() }); + + debug!("Driver stopped running"); + + result + } + fn check_init(&self) -> Result<(), EspError> { if !self.initialized { Err(EspError::from_infallible::())?; @@ -1098,6 +1103,22 @@ where Ok(()) } + + fn internal_init_wifi_coex() -> Result<(), EspError> { + #[cfg(not(any(esp32h2, esp32h4)))] + esp!(unsafe { esp_wifi_set_ps(wifi_ps_type_t_WIFI_PS_MAX_MODEM) })?; + + #[cfg(all( + esp_idf_esp_coex_sw_coexist_enable, + esp_idf_openthread_radio_native, + esp_idf_soc_ieee802154_supported + ))] + { + esp!(unsafe { esp_coex_wifi_i154_enable() })?; + } + + Ok(()) + } } impl<'d, T> Drop for ThreadDriver<'d, T> @@ -1453,6 +1474,11 @@ where Ok(self.netif_initialized && self.driver().is_init()?) } + /// Initialize the coexistence between the Thread and Wifi radios + pub fn init_wifi_coex(&mut self) -> Result<(), EspError> { + self.driver_mut().init_wifi_coex() + } + /// Retrieve the current role of the device in the Thread network pub fn role(&self) -> Result { self.driver().role() From b26da684379399e4aa54f0a5d4d5aaa1d47e3984 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 21 Sep 2024 10:58:05 +0000 Subject: [PATCH 51/60] Small enhancements to the examples --- examples/thread_br.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/thread_br.rs b/examples/thread_br.rs index 9374e5f4536..a5475e19b8b 100644 --- a/examples/thread_br.rs +++ b/examples/thread_br.rs @@ -57,7 +57,7 @@ fn main() -> anyhow::Result<()> { // Remove this whole code block when you have done all of the above for the example to compile #[cfg(not(i_have_done_all_of_the_above))] { - log::info!("Please follow the instructions in the source code."); + log::error!("Please follow the instructions in the source code."); } Ok(()) @@ -91,7 +91,7 @@ mod example { let (wifi_modem, thread_modem, _) = peripherals.modem.split(); - let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?); + let mounted_event_fs = Arc::new(MountedEventfs::mount(6)?); let mut wifi = BlockingWifi::wrap( EspWifi::new(wifi_modem, sys_loop.clone(), Some(nvs.clone()))?, @@ -117,6 +117,9 @@ mod example { )?; thread.init()?; + thread.init_wifi_coex()?; + + thread.set_tod_from_cfg()?; info!("Thread initialized, now running..."); From 6645bd64a26c61dbb114ddf7c549f07a4278b1e5 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 21 Sep 2024 14:46:34 +0000 Subject: [PATCH 52/60] Refine the flags for coex modes; fix docu --- src/thread.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index efc9668b079..3fac6e56def 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1071,9 +1071,10 @@ where Ok(self.initialized) } - /// Initialize the coexistence between the Thread and Wifi radios - pub fn init_wifi_coex(&mut self) -> Result<(), EspError> { - Self::internal_init_wifi_coex() + /// Initialize the coexistence between the Thread stack and a Wifi/BT stack on the modem + #[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))] + pub fn init_coex(&mut self) -> Result<(), EspError> { + Self::internal_init_coex() } /// Run the Thread stack @@ -1104,15 +1105,14 @@ where Ok(()) } - fn internal_init_wifi_coex() -> Result<(), EspError> { + #[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))] + fn internal_init_coex() -> Result<(), EspError> { #[cfg(not(any(esp32h2, esp32h4)))] - esp!(unsafe { esp_wifi_set_ps(wifi_ps_type_t_WIFI_PS_MAX_MODEM) })?; + { + esp!(unsafe { esp_wifi_set_ps(wifi_ps_type_t_WIFI_PS_MAX_MODEM) })?; + } - #[cfg(all( - esp_idf_esp_coex_sw_coexist_enable, - esp_idf_openthread_radio_native, - esp_idf_soc_ieee802154_supported - ))] + #[cfg(esp_idf_esp_coex_sw_coexist_enable)] { esp!(unsafe { esp_coex_wifi_i154_enable() })?; } @@ -1474,9 +1474,10 @@ where Ok(self.netif_initialized && self.driver().is_init()?) } - /// Initialize the coexistence between the Thread and Wifi radios - pub fn init_wifi_coex(&mut self) -> Result<(), EspError> { - self.driver_mut().init_wifi_coex() + /// Initialize the coexistence between the Thread stack and a Wifi/BT stack on the modem + #[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))] + pub fn init_coex(&mut self) -> Result<(), EspError> { + self.driver_mut().init_coex() } /// Retrieve the current role of the device in the Thread network From c7706c34536bbc3452c3eab756e02176b0d06c82 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 21 Sep 2024 14:47:31 +0000 Subject: [PATCH 53/60] Refine the flags for coex modes; fix docu --- examples/thread_br.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/thread_br.rs b/examples/thread_br.rs index a5475e19b8b..41d4cb50252 100644 --- a/examples/thread_br.rs +++ b/examples/thread_br.rs @@ -117,7 +117,7 @@ mod example { )?; thread.init()?; - thread.init_wifi_coex()?; + thread.init_coex()?; thread.set_tod_from_cfg()?; From 7a04d9ab5240991656e8616ec05b83f150563085 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 23 Sep 2024 06:12:44 +0000 Subject: [PATCH 54/60] RCP example; bugfixes related to UART --- examples/thread.rs | 11 ++++-- examples/thread_rcp.rs | 78 ++++++++++++++++++++++++++++++++++++++++++ src/thread.rs | 76 ++++++++++++++++++++++++---------------- 3 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 examples/thread_rcp.rs diff --git a/examples/thread.rs b/examples/thread.rs index b965b04c1bc..8ccce990167 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -1,7 +1,12 @@ -//! Example of using Thread. +//! Example of using Thread in "Node" mode. +//! //! The example just starts Thread and logs the events, without doing anything else useful. -//! +//! However, in 99% of the case this is exactly what you want to do. +//! //! NOTE: This example only works on MCUs that has Thread capabilities, like the ESP32-C6 or ESP32-H2. +//! +//! It is however possible to run this example on other MCUs, using the UART or SPI protocols, but then +//! you anyway would need _another_, Thread-capable MCU that runs Thread in RCP mode (see the `thread_rcp`) example. fn main() -> anyhow::Result<()> { esp_idf_svc::sys::link_patches(); @@ -11,7 +16,7 @@ fn main() -> anyhow::Result<()> { example::main()?; #[cfg(not(any(esp32h2, esp32c6)))] - log::info!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2."); + log::error!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2."); Ok(()) } diff --git a/examples/thread_rcp.rs b/examples/thread_rcp.rs new file mode 100644 index 00000000000..c943f4bf7d7 --- /dev/null +++ b/examples/thread_rcp.rs @@ -0,0 +1,78 @@ +//! Example of using Thread in "RCP" mode. +//! +//! The `RCP` mode of `ThreadDriver` is a mode where the Thread stack is running on the MCU, however, +//! it communicates via UART or SPI to another - "master" MCU which - most often than not - does not +//! have a native Thread radio, but has other connectivity like Wifi. It is this other MCU which actually runs +//! Thread as a real "Node", from the POV of the user. +//! +//! NOTE: This example only works on MCUs that has Thread capabilities, like the ESP32-C6 or ESP32-H2. + +fn main() -> anyhow::Result<()> { + esp_idf_svc::sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + + #[cfg(all(any(esp32h2, esp32c6), esp_idf_openthread_radio))] + example::main()?; + + #[cfg(not(any(esp32h2, esp32c6)))] + log::error!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2."); + + #[cfg(not(esp_idf_openthread_radio))] + log::error!("Put `CONFIG_OPENTHREAD_RADIO=y` in your `sdkconfig.defaults`"); + + Ok(()) +} + +#[cfg(all(any(esp32h2, esp32c6), esp_idf_openthread_radio))] +mod example { + use std::sync::Arc; + + use log::info; + + use esp_idf_svc::eventloop::EspSystemSubscription; + use esp_idf_svc::hal::prelude::Peripherals; + use esp_idf_svc::io::vfs::MountedEventfs; + use esp_idf_svc::thread::{ThreadDriver, ThreadEvent}; + use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition}; + + pub fn main() -> anyhow::Result<()> { + let peripherals = Peripherals::take()?; + let sys_loop = EspSystemEventLoop::take()?; + let nvs = EspDefaultNvsPartition::take()?; + + let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?); + + info!("Initializing Thread RCP..."); + + let _subscription = log_thread_sysloop(sys_loop.clone())?; + + let mut thread = ThreadDriver::new_rcp_uart( + peripherals.modem, + peripherals.uart1, + peripherals.pins.gpio10, + peripherals.pins.gpio11, + &esp_idf_svc::thread::config::uart_default_cfg(), + sys_loop.clone(), + nvs, + mounted_event_fs, + )?; + + thread.init()?; + + info!("Thread RCP initialized, now running..."); + + thread.run()?; + + Ok(()) + } + + fn log_thread_sysloop( + sys_loop: EspSystemEventLoop, + ) -> Result, anyhow::Error> { + let subscription = sys_loop.subscribe::(|event| { + info!("Got: {:?}", event); + })?; + + Ok(subscription) + } +} diff --git a/src/thread.rs b/src/thread.rs index 3fac6e56def..d954a39c952 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -25,10 +25,10 @@ use crate::hal::gpio::{InputPin, OutputPin}; use crate::hal::peripheral::Peripheral; use crate::hal::task::CriticalSection; use crate::hal::uart::Uart; -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] use crate::handle::RawHandle; use crate::io::vfs::MountedEventfs; -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] use crate::netif::*; use crate::nvs::EspDefaultNvsPartition; use crate::sys::*; @@ -54,12 +54,15 @@ pub struct RCP(()); #[cfg(esp_idf_soc_ieee802154_supported)] impl Mode for RCP { fn init() { - extern "C" { - fn otAppNcpInit(instance: *mut otInstance); - } + //#[cfg(esp_idf_openthread_ncp_vendor_hook)] + { + extern "C" { + fn otAppNcpInit(instance: *mut otInstance); + } - unsafe { - otAppNcpInit(esp_openthread_get_instance()); + unsafe { + otAppNcpInit(esp_openthread_get_instance()); + } } } } @@ -77,11 +80,27 @@ impl Mode for Host { } pub mod config { + use crate::hal::uart::config::*; + use crate::hal::units::*; + + /// A safe baud rate for the UART #[cfg(all(esp32c2, esp_idf_xtal_freq_26))] - pub const UART_DEFAULT_BAUD_RATE: u32 = 74880; + pub const UART_SAFE_BAUD_RATE: Hertz = Hertz(74880); + /// A safe baud rate for the UART #[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))] - pub const UART_DEFAULT_BAUD_RATE: u32 = 115200; + pub const UART_SAFE_BAUD_RATE: Hertz = Hertz(115200); + + /// A safe default UART configuration + pub fn uart_default_cfg() -> Config { + Config::new() + .baudrate(UART_SAFE_BAUD_RATE) + .data_bits(DataBits::DataBits8) + .parity_none() + .stop_bits(StopBits::STOP1) + .flow_control(FlowControl::None) + .flow_control_rts_threshold(0) + } } macro_rules! ot_esp { @@ -1040,6 +1059,7 @@ where esp!(unsafe { esp_openthread_init(&self.cfg) })?; + #[cfg(not(esp_idf_openthread_radio))] unsafe { otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL as _); } @@ -1175,20 +1195,12 @@ impl NetifMode for Node { } /// The Border Router mode of operation for the `EspThread` instance -#[cfg(all( - esp_idf_comp_esp_netif_enabled, - esp_idf_openthread_border_router, - not(any(esp32h2, esp32h4)) -))] +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] pub struct BorderRouter(N) where N: core::borrow::Borrow; -#[cfg(all( - esp_idf_comp_esp_netif_enabled, - esp_idf_openthread_border_router, - not(any(esp32h2, esp32h4)) -))] +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] impl> NetifMode for BorderRouter { fn init(&mut self) -> Result<(), EspError> { #[cfg(esp_idf_version_major = "4")] @@ -1201,6 +1213,14 @@ impl> NetifMode for BorderRouter { esp!(unsafe { esp_openthread_border_router_init() })?; } + // TODO: This is probably best left to the user to call, as it is + // not strictly necessary for the border router to function + // #[cfg(any(esp_idf_comp_mdns_enabled, esp_idf_comp_espressif__mdns_enabled))] + // { + // esp!(unsafe { mdns_init() })?; + // esp!(unsafe { mdns_hostname_set(b"esp-ot-br\0" as *const _ as *const _) })?; + // } + debug!("Border router initialized"); Ok(()) @@ -1225,7 +1245,7 @@ impl> NetifMode for BorderRouter { /// but the niche one where bypassing the ESP IDF Netif and lwIP stacks is /// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the /// ESP IDF Thread radio. -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] pub struct EspThread<'d, T> where T: NetifMode, @@ -1236,7 +1256,7 @@ where mode: T, } -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] impl<'d> EspThread<'d, Node> { /// Create a new `EspThread` instance utilizing the native Thread radio on the MCU #[cfg(esp_idf_soc_ieee802154_supported)] @@ -1319,11 +1339,7 @@ impl<'d> EspThread<'d, Node> { } } -#[cfg(all( - esp_idf_comp_esp_netif_enabled, - esp_idf_openthread_border_router, - not(any(esp32h2, esp32h4)) -))] +#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))] impl<'d, N> EspThread<'d, BorderRouter> where N: core::borrow::Borrow, @@ -1425,7 +1441,7 @@ where } } -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] impl<'d, T> EspThread<'d, T> where T: NetifMode, @@ -1638,7 +1654,7 @@ where } } -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] impl<'d, T> Drop for EspThread<'d, T> where T: NetifMode, @@ -1648,9 +1664,9 @@ where } } -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] unsafe impl<'d, T> Send for EspThread<'d, T> where T: NetifMode {} -#[cfg(esp_idf_comp_esp_netif_enabled)] +#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))] unsafe impl<'d, T> Sync for EspThread<'d, T> where T: NetifMode {} /// Events reported by the Thread stack on the system event loop From 76b60959175fbe5b2fc7fec3d38e0ac9618a3002 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 23 Sep 2024 06:42:35 +0000 Subject: [PATCH 55/60] fmt --- examples/thread.rs | 6 +++--- examples/thread_rcp.rs | 10 +++++----- src/thread.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/thread.rs b/examples/thread.rs index 8ccce990167..182e7c744cd 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -1,10 +1,10 @@ //! Example of using Thread in "Node" mode. -//! +//! //! The example just starts Thread and logs the events, without doing anything else useful. //! However, in 99% of the case this is exactly what you want to do. -//! +//! //! NOTE: This example only works on MCUs that has Thread capabilities, like the ESP32-C6 or ESP32-H2. -//! +//! //! It is however possible to run this example on other MCUs, using the UART or SPI protocols, but then //! you anyway would need _another_, Thread-capable MCU that runs Thread in RCP mode (see the `thread_rcp`) example. diff --git a/examples/thread_rcp.rs b/examples/thread_rcp.rs index c943f4bf7d7..f6ce4d80b72 100644 --- a/examples/thread_rcp.rs +++ b/examples/thread_rcp.rs @@ -1,6 +1,6 @@ //! Example of using Thread in "RCP" mode. -//! -//! The `RCP` mode of `ThreadDriver` is a mode where the Thread stack is running on the MCU, however, +//! +//! The `RCP` mode of `ThreadDriver` is a mode where the Thread stack is running on the MCU, however, //! it communicates via UART or SPI to another - "master" MCU which - most often than not - does not //! have a native Thread radio, but has other connectivity like Wifi. It is this other MCU which actually runs //! Thread as a real "Node", from the POV of the user. @@ -48,12 +48,12 @@ mod example { let mut thread = ThreadDriver::new_rcp_uart( peripherals.modem, - peripherals.uart1, + peripherals.uart1, peripherals.pins.gpio10, peripherals.pins.gpio11, &esp_idf_svc::thread::config::uart_default_cfg(), - sys_loop.clone(), - nvs, + sys_loop.clone(), + nvs, mounted_event_fs, )?; diff --git a/src/thread.rs b/src/thread.rs index d954a39c952..2d1f32ddaab 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1220,7 +1220,7 @@ impl> NetifMode for BorderRouter { // esp!(unsafe { mdns_init() })?; // esp!(unsafe { mdns_hostname_set(b"esp-ot-br\0" as *const _ as *const _) })?; // } - + debug!("Border router initialized"); Ok(()) From 5143075b860ba5040a3344c3af5439dd55b8508d Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 23 Sep 2024 08:03:04 +0000 Subject: [PATCH 56/60] Thread BR example with UART connection --- examples/thread_br.rs | 62 ++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/examples/thread_br.rs b/examples/thread_br.rs index 41d4cb50252..19ee4e86f99 100644 --- a/examples/thread_br.rs +++ b/examples/thread_br.rs @@ -1,11 +1,9 @@ //! Example of a Thread Border Router. //! -//! This example only works on MCUs that have BOTH Thread and Wifi capabilities, like the ESP32-C6. -//! -//! For other MCUs, you need to use at least one Thread-capable MCU like the ESP32-H2 (which only supports Thread), -//! and then instead of Wifi, you need to use Ethernet via SPI. -//! ... or use a pair of Thread-capable MCU (as the Thread RCP) and Wifi-capable MCU (as the Thread Host) and connect -//! them over UART or SPI. +//! This example only works on MCUs that do have Wifi capabilities, as follows: +//! - On MCUs with both native `Thread` as well as `Wifi` capabilities, the example will run in co-exist mode, on a single MCU; +//! - On MCUs with only `Wifi` capabilities, the example will run in UART mode, so you need to flash the `thread_rcp` example +//! on a separate MCU which does have native `Thread` capabilities, and connect the two via UART. //! //! NOTE NOTE NOTE: //! To build, you need to put the following in your `sdkconfig.defaults`: @@ -50,21 +48,28 @@ fn main() -> anyhow::Result<()> { esp_idf_svc::sys::link_patches(); esp_idf_svc::log::EspLogger::initialize_default(); - #[cfg(i_have_done_all_of_the_above)] // Remove this `cfg` when you have done all of the above for the example to compile - #[cfg(esp32c6)] - example::main()?; + #[cfg(any(esp32h2, esp32h4))] + { + log::error!("This example only works on MCUs which do have Wifi support."); + } - // Remove this whole code block when you have done all of the above for the example to compile - #[cfg(not(i_have_done_all_of_the_above))] + #[cfg(not(any(esp32h2, esp32h4)))] { - log::error!("Please follow the instructions in the source code."); + //#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile + example::main()?; + + // // Remove this whole code block when you have done all of the above for the example to compile + // #[cfg(not(i_have_done_all_configs_from_the_top_comment))] + // { + // log::error!("Please follow the instructions in the source code."); + // } } Ok(()) } -#[cfg(i_have_done_all_of_the_above)] // Remove this `cfg` when you have done all of the above for the example to compile -#[cfg(esp32c6)] +//#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile +#[cfg(not(any(esp32h2, esp32h4)))] mod example { use core::convert::TryInto; @@ -89,7 +94,11 @@ mod example { let sys_loop = EspSystemEventLoop::take()?; let nvs = EspDefaultNvsPartition::take()?; - let (wifi_modem, thread_modem, _) = peripherals.modem.split(); + #[cfg(esp32c6)] + let (wifi_modem, _thread_modem) = { peripherals.modem.split() }; + + #[cfg(not(esp32c6))] + let (wifi_modem, _thread_modem) = { (peripherals.modem, ()) }; let mounted_event_fs = Arc::new(MountedEventfs::mount(6)?); @@ -104,12 +113,27 @@ mod example { info!("Wifi DHCP info: {:?}", ip_info); - info!("Initializing Thread..."); + info!("Initializing Thread Border Router..."); let _subscription = log_thread_sysloop(sys_loop.clone())?; + // On the C6, run the Thread Border Router in co-exist mode + #[cfg(esp32c6)] let mut thread = EspThread::new_br( - thread_modem, + _thread_modem, + sys_loop, + nvs, + mounted_event_fs, + wifi.wifi().sta_netif(), + )?; + + // On all other chips, run the Thread Border Router in UART mode + #[cfg(not(esp32c6))] + let mut thread = EspThread::new_br_uart( + peripherals.uart1, + peripherals.pins.gpio2, + peripherals.pins.gpio3, + &esp_idf_svc::thread::config::uart_default_cfg(), sys_loop, nvs, mounted_event_fs, @@ -117,11 +141,13 @@ mod example { )?; thread.init()?; + + #[cfg(esp32c6)] thread.init_coex()?; thread.set_tod_from_cfg()?; - info!("Thread initialized, now running..."); + info!("Thread Border Router initialized, now running..."); thread.run()?; From 6c9aa4ff1d250a9b35830927c9e0e47eda0077e9 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 23 Sep 2024 08:26:09 +0000 Subject: [PATCH 57/60] Restore the BR example in commented out state --- examples/thread_br.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/thread_br.rs b/examples/thread_br.rs index 19ee4e86f99..49bc279bf05 100644 --- a/examples/thread_br.rs +++ b/examples/thread_br.rs @@ -58,17 +58,17 @@ fn main() -> anyhow::Result<()> { //#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile example::main()?; - // // Remove this whole code block when you have done all of the above for the example to compile - // #[cfg(not(i_have_done_all_configs_from_the_top_comment))] - // { - // log::error!("Please follow the instructions in the source code."); - // } + // Remove this whole code block when you have done all of the above for the example to compile + #[cfg(not(i_have_done_all_configs_from_the_top_comment))] + { + log::error!("Please follow the instructions in the source code."); + } } Ok(()) } -//#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile +#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile #[cfg(not(any(esp32h2, esp32h4)))] mod example { use core::convert::TryInto; From 54b86e8d05903033fb5df3671ba69e0f016f60b3 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 23 Sep 2024 08:26:41 +0000 Subject: [PATCH 58/60] Restore the BR example in commented out state --- examples/thread_br.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/thread_br.rs b/examples/thread_br.rs index 49bc279bf05..68b0332ab1d 100644 --- a/examples/thread_br.rs +++ b/examples/thread_br.rs @@ -55,7 +55,7 @@ fn main() -> anyhow::Result<()> { #[cfg(not(any(esp32h2, esp32h4)))] { - //#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile + #[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile example::main()?; // Remove this whole code block when you have done all of the above for the example to compile From b22aaa4a60827c6d049a0be354f39c9cb5b7810c Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 23 Sep 2024 08:29:28 +0000 Subject: [PATCH 59/60] Remove resolved TODOs --- src/thread.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/thread.rs b/src/thread.rs index 2d1f32ddaab..fe608cd80b8 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,16 +1,7 @@ // TODO: // - Prio A: Status report (we have driver::role() now; more, e.g. ipv6 notifications?) -// - Prio B: Option to switch between FTD (Full Thread Device) and MTD (Minimal Thread Device) (otDatasetCreateNewNetwork? probably needs CONFIG_OPENTHREAD_DEVICE_TYPE=CONFIG_OPENTHREAD_FTD/CONFIG_OPENTHREAD_MTD/CONFIG_OPENTHREAD_RADIO) // - Prio B: API to enable the Joiner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_JOINER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) // - Prio B: API to to enable the Commissioner workflow (need to read on that, but not needed for Matter; CONFIG_OPENTHREAD_COMMISSIONER - also native OpenThread API https://github.com/espressif/esp-idf/issues/13475) -// - Prio C: Figure out what these do (bad/missing docu): -// - CONFIG_OPENTHREAD_DNS_CLIENT (can this be enabled programmatically too - does not seem so, and why is this part of OpenThread and not the LwIP ipv6 stack?) -// - CONFIG_OPENTHREAD_DIAG -// - CONFIG_OPENTHREAD_CSL_ENABLE -// - CONFIG_OPENTHREAD_DUA_ENABLE -// - CONFIG_OPENTHREAD_SRP_CLIENT -// - CONFIG_OPENTHREAD_DNS64_CLIENT? "Select this option to acquire NAT64 address from dns servers" why does this require explicit conf -// or in fact why does this has anything to do with the OpenThread client? use core::ffi::{self, c_void, CStr}; use core::fmt::Debug; From 30bfc7327f146648a6ff3e2d719b4bca968ce391 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 23 Sep 2024 08:35:13 +0000 Subject: [PATCH 60/60] fmt --- examples/thread_br.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/thread_br.rs b/examples/thread_br.rs index 68b0332ab1d..0cdca11942d 100644 --- a/examples/thread_br.rs +++ b/examples/thread_br.rs @@ -55,7 +55,8 @@ fn main() -> anyhow::Result<()> { #[cfg(not(any(esp32h2, esp32h4)))] { - #[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile + #[cfg(i_have_done_all_configs_from_the_top_comment)] + // Remove this `cfg` when you have done all of the above for the example to compile example::main()?; // Remove this whole code block when you have done all of the above for the example to compile