use core::cell::UnsafeCell;
use core::ffi::{self, c_void, CStr};
use core::fmt::Debug;
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};
use core::ptr::addr_of_mut;
use alloc::boxed::Box;
use alloc::sync::Arc;
#[allow(unused)]
use ::log::{debug, info};
use crate::eventloop::{EspEventDeserializer, EspEventSource, EspSystemEventLoop};
use crate::hal::delay;
use crate::hal::gpio::{InputPin, OutputPin};
use crate::hal::uart::Uart;
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
use crate::handle::RawHandle;
use crate::io::vfs::MountedEventfs;
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
use crate::netif::*;
use crate::nvs::EspDefaultNvsPartition;
use crate::private::mutex::{Condvar, Mutex};
use crate::sys::*;
use crate::thread::srp::OtSrp;
pub use srp::*;
extern crate alloc;
mod srp;
pub trait Mode {
fn init();
}
#[cfg(esp_idf_soc_ieee802154_supported)]
#[derive(Debug)]
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());
}
}
}
}
#[derive(Debug)]
pub struct Host(());
impl Mode for Host {
fn init() {}
}
pub mod config {
use crate::hal::uart::config::*;
use crate::hal::units::*;
#[cfg(all(esp32c2, esp_idf_xtal_freq_26))]
pub const UART_SAFE_BAUD_RATE: Hertz = Hertz(74880);
#[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))]
pub const UART_SAFE_BAUD_RATE: Hertz = Hertz(115200);
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 {
($err:expr) => {{
$crate::sys::esp!($crate::thread::ot_esp_code($err as u32))
}};
}
pub(crate) use ot_esp;
#[allow(non_upper_case_globals, non_snake_case)]
pub(crate) const fn ot_esp_code(ot_code: u32) -> esp_err_t {
match ot_code {
crate::sys::otError_OT_ERROR_NONE => crate::sys::ESP_OK as _,
crate::sys::otError_OT_ERROR_FAILED => crate::sys::ESP_FAIL as _,
_ => crate::sys::ESP_FAIL as _, }
}
pub(crate) fn ot_esp_err(ot_code: u32) -> EspError {
EspError::from(ot_esp_code(ot_code)).unwrap()
}
pub struct ActiveScanResult<'a>(&'a otActiveScanResult);
impl<'a> ActiveScanResult<'a> {
pub fn extended_address(&self) -> &'a [u8] {
&self.0.mExtAddress.m8
}
pub fn network_name_cstr(&self) -> &'a CStr {
unsafe { ffi::CStr::from_ptr(&self.0.mNetworkName.m8 as *const _ as *const _) }
}
pub fn extended_pan_id(&self) -> &[u8] {
&self.0.mExtendedPanId.m8
}
pub fn steering_data(&self) -> &[u8] {
&self.0.mSteeringData.m8
}
pub fn pan_id(&self) -> u16 {
self.0.mPanId
}
pub fn joiner_udp_port(&self) -> u16 {
self.0.mJoinerUdpPort
}
pub fn channel(&self) -> u8 {
self.0.mChannel
}
pub fn max_rssi(&self) -> i8 {
self.0.mRssi
}
pub fn lqi(&self) -> u8 {
self.0.mLqi
}
pub fn version(&self) -> u8 {
self.0.mVersion() as _
}
pub fn native_commissioner(&self) -> bool {
self.0.mIsNative()
}
pub fn join_permitted(&self) -> bool {
self.0.mIsJoinable()
}
}
pub struct EnergyScanResult<'a>(&'a otEnergyScanResult);
impl EnergyScanResult<'_> {
pub fn channel(&self) -> u8 {
self.0.mChannel
}
pub fn max_rssi(&self) -> i8 {
self.0.mMaxRssi
}
}
#[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<otDeviceRole> 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,
}
}
}
pub struct Ipv6Packet<'a>(&'a otMessage);
impl Ipv6Packet<'_> {
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 _ }
}
}
pub enum Ipv6Incoming<'a> {
AddressAdded(core::net::Ipv6Addr),
AddressRemoved(core::net::Ipv6Addr),
Data(Ipv6Packet<'a>),
}
pub struct ThreadDriver<'d, T>
where
T: Mode,
{
inner: UnsafeCell<Box<ThreadDriverInner>>,
_nvs: EspDefaultNvsPartition,
_mounted_event_fs: Arc<MountedEventfs>,
_mode: T,
_p: PhantomData<&'d mut ()>,
}
impl<'d> ThreadDriver<'d, Host> {
#[cfg(esp_idf_soc_ieee802154_supported)]
pub fn new<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
modem: M,
sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::internal_new(
Self::host_native_cfg(modem),
sysloop,
nvs,
mounted_event_fs,
Host(()),
)
}
#[cfg(not(esp_idf_version_major = "4"))]
#[allow(clippy::too_many_arguments)]
pub fn new_spi<S: crate::hal::spi::Spi + 'd>(
spi: S,
mosi: impl InputPin + 'd,
miso: impl OutputPin + 'd,
sclk: impl InputPin + OutputPin + 'd,
cs: Option<impl InputPin + OutputPin + 'd>,
intr: Option<impl InputPin + OutputPin + 'd>,
config: &crate::hal::spi::config::Config,
sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::internal_new(
Self::host_spi_cfg(spi, mosi, miso, sclk, cs, intr, config),
sysloop,
nvs,
mounted_event_fs,
Host(()),
)
}
pub fn new_uart<U: Uart + 'd>(
uart: U,
tx: impl OutputPin + 'd,
rx: impl InputPin + 'd,
config: &crate::hal::uart::config::Config,
sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::internal_new(
Self::host_uart_cfg(uart, tx, rx, config),
sysloop,
nvs,
mounted_event_fs,
Host(()),
)
}
pub fn enable_ipv6(&self, enable: bool) -> Result<(), EspError> {
let _lock = self.inner();
ot_esp!(unsafe { otIp6SetEnabled(esp_openthread_get_instance(), enable) })
}
pub fn enable_thread(&self, enable: bool) -> Result<(), EspError> {
let _lock = self.inner();
ot_esp!(unsafe { otThreadSetEnabled(esp_openthread_get_instance(), enable) })
}
pub fn role(&self) -> Result<Role, EspError> {
let _lock = self.inner();
Ok(unsafe { otThreadGetDeviceRole(esp_openthread_get_instance()) }.into())
}
#[cfg(esp_idf_openthread_cli)]
pub fn init_cli(&mut self) -> Result<(), EspError> {
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(())
}
pub fn tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
let mut inner = self.inner();
Self::internal_tod(&mut inner, true, buf)
}
pub fn pending_tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
let mut inner = self.inner();
Self::internal_tod(&mut inner, false, buf)
}
pub fn set_tod(&self, tod: &[u8]) -> Result<(), EspError> {
let mut inner = self.inner();
Self::fill_dataset_tlv(&mut inner.dataset_buf, tod)?;
Self::internal_set_tod(&mut inner, true)
}
pub fn set_pending_tod(&self, tod: &[u8]) -> Result<(), EspError> {
let mut inner = self.inner();
Self::fill_dataset_tlv(&mut inner.dataset_buf, tod)?;
Self::internal_set_tod(&mut inner, false)
}
pub fn set_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
let mut inner = self.inner();
Self::fill_dataset_tlv_hexstr(&mut inner.dataset_buf, tod)?;
Self::internal_set_tod(&mut inner, true)
}
pub fn set_pending_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
let mut inner = self.inner();
Self::fill_dataset_tlv_hexstr(&mut inner.dataset_buf, tod)?;
Self::internal_set_tod(&mut inner, false)
}
#[cfg(not(esp_idf_version_major = "4"))]
pub fn set_tod_from_cfg(&self) -> Result<(), EspError> {
let _lock = self.inner();
ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) })
}
pub fn scan<F: FnMut(Option<ActiveScanResult>) + Send + 'static>(
&self,
callback: F,
) -> Result<(), EspError> {
let mut inner = self.inner();
if inner.scan_cb.is_some() {
return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
}
#[allow(clippy::type_complexity)]
let mut callback: Box<Box<dyn FnMut(Option<ActiveScanResult>) + Send + 'static>> =
Box::new(Box::new(callback));
ot_esp!(unsafe {
otLinkActiveScan(
esp_openthread_get_instance(),
0xffff_ffffu32, 200, Some(Self::on_active_scan_result),
callback.as_mut() as *mut _ as *mut c_void,
)
})?;
inner.scan_cb = Some(callback);
Ok(())
}
pub fn is_scan_in_progress(&self) -> Result<bool, EspError> {
let _lock = self.inner();
Ok(unsafe { otLinkIsActiveScanInProgress(esp_openthread_get_instance()) })
}
pub fn energy_scan<F: FnMut(Option<EnergyScanResult>) + Send + 'static>(
&self,
callback: F,
) -> Result<(), EspError> {
let mut inner = self.inner();
if inner.energy_cb.is_some() {
return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
}
#[allow(clippy::type_complexity)]
let mut callback: Box<Box<dyn FnMut(Option<EnergyScanResult>) + Send + 'static>> =
Box::new(Box::new(callback));
ot_esp!(unsafe {
otLinkEnergyScan(
esp_openthread_get_instance(),
0xffff_ffffu32, 200, Some(Self::on_energy_scan_result),
callback.as_mut() as *mut _ as *mut c_void,
)
})?;
inner.energy_cb = Some(callback);
Ok(())
}
pub fn is_energy_scan_in_progress(&self) -> Result<bool, EspError> {
let _lock = self.inner();
Ok(unsafe { otLinkIsEnergyScanInProgress(esp_openthread_get_instance()) })
}
pub fn tx(&self, packet: &[u8]) -> Result<(), EspError> {
let _lock = self.inner();
let message =
unsafe { otIp6NewMessage(esp_openthread_get_instance(), core::ptr::null_mut()) };
if message.is_null() {
Err(EspError::from_infallible::<ESP_FAIL>())?;
}
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
}
pub fn set_rx_callback<R>(&self, callback: Option<R>) -> Result<(), EspError>
where
R: FnMut(Ipv6Incoming) + Send + 'static,
{
let mut inner = self.inner();
Self::internal_set_rx_callback(&mut inner, callback)
}
pub fn set_nonstatic_rx_callback<R>(&self, callback: Option<R>) -> Result<(), EspError>
where
R: FnMut(Ipv6Incoming) + Send + 'd,
{
let mut inner = self.inner();
Self::internal_set_rx_callback(&mut inner, callback)
}
fn internal_set_rx_callback<R>(
inner: &mut ThreadDriverInner,
callback: Option<R>,
) -> Result<(), EspError>
where
R: FnMut(Ipv6Incoming) + Send + 'd,
{
if let Some(callback) = callback {
#[allow(clippy::type_complexity)]
let callback: Box<Box<dyn FnMut(Ipv6Incoming) + Send + 'd>> =
Box::new(Box::new(callback));
#[allow(clippy::type_complexity)]
let mut callback: Box<Box<dyn FnMut(Ipv6Incoming) + Send + 'static>> =
unsafe { core::mem::transmute(callback) };
let callback_ptr = callback.as_mut() as *mut _ as *mut c_void;
inner.ipv6_cb = 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);
}
} 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);
}
inner.ipv6_cb = None;
}
Ok(())
}
fn internal_tod(
inner: &mut ThreadDriverInner,
active: bool,
buf: &mut [u8],
) -> Result<usize, EspError> {
let dataset_buf = &mut inner.dataset_buf;
ot_esp!(unsafe {
if active {
otDatasetGetActiveTlvs(esp_openthread_get_instance(), dataset_buf)
} else {
otDatasetGetPendingTlvs(esp_openthread_get_instance(), dataset_buf)
}
})?;
let len = dataset_buf.mLength as usize;
if buf.len() < len {
Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>())?;
}
buf[..len].copy_from_slice(&dataset_buf.mTlvs[..len]);
Ok(len)
}
fn internal_set_tod(inner: &mut ThreadDriverInner, active: bool) -> Result<(), EspError> {
ot_esp!(unsafe {
if active {
otDatasetSetActiveTlvs(esp_openthread_get_instance(), &inner.dataset_buf)
} else {
otDatasetSetPendingTlvs(esp_openthread_get_instance(), &inner.dataset_buf)
}
})?;
Ok(())
}
fn fill_dataset_tlv(
dataset_buf: &mut otOperationalDatasetTlvs,
data: &[u8],
) -> Result<(), EspError> {
if data.len() > core::mem::size_of_val(&dataset_buf.mTlvs) {
Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>())?;
}
dataset_buf.mLength = data.len() as _;
dataset_buf.mTlvs[..data.len()].copy_from_slice(data);
Ok(())
}
fn fill_dataset_tlv_hexstr(
dataset_buf: &mut otOperationalDatasetTlvs,
dataset: &str,
) -> Result<(), EspError> {
let dataset = dataset.trim();
let mut offset = 0;
for (chf, chs) in dataset
.chars()
.step_by(2)
.zip(dataset.chars().skip(1).step_by(2))
{
let byte = (chf
.to_digit(16)
.ok_or(ot_esp_err(otError_OT_ERROR_INVALID_ARGS))?
<< 4)
| chs
.to_digit(16)
.ok_or(ot_esp_err(otError_OT_ERROR_INVALID_ARGS))?;
if offset >= dataset_buf.mTlvs.len() {
Err(ot_esp_err(otError_OT_ERROR_NO_BUFS))?;
}
dataset_buf.mTlvs[offset] = byte as _;
offset += 1;
}
dataset_buf.mLength = offset as _;
Ok(())
}
unsafe extern "C" fn on_address(
address_info: *const otIp6AddressInfo,
is_added: bool,
context: *mut c_void,
) {
let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
if let Some(ipv6_cb) = inner.ipv6_cb.as_mut() {
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 {
ipv6_cb(Ipv6Incoming::AddressAdded(address));
} else {
ipv6_cb(Ipv6Incoming::AddressRemoved(address));
}
}
}
unsafe extern "C" fn on_packet(message: *mut otMessage, context: *mut c_void) {
let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
if let Some(ipv6_cb) = inner.ipv6_cb.as_mut() {
ipv6_cb(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,
) {
let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
if let Some(scan_cb) = inner.scan_cb.as_mut() {
if result.is_null() {
scan_cb(None);
} else {
scan_cb(Some(ActiveScanResult(unsafe { result.as_ref() }.unwrap())));
}
}
if result.is_null() {
inner.scan_cb = None;
}
}
unsafe extern "C" fn on_energy_scan_result(
result: *mut otEnergyScanResult,
context: *mut c_void,
) {
let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
if let Some(energy_cb) = inner.energy_cb.as_mut() {
if result.is_null() {
energy_cb(None);
} else {
energy_cb(Some(EnergyScanResult(unsafe { result.as_ref() }.unwrap())));
}
}
if result.is_null() {
inner.energy_cb = None;
}
}
#[cfg(esp_idf_soc_ieee802154_supported)]
fn host_native_cfg<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
_modem: M,
) -> 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(not(esp_idf_version_major = "4"))]
#[allow(clippy::too_many_arguments)]
fn host_spi_cfg<S: crate::hal::spi::Spi + 'd>(
_spi: S,
mosi: impl InputPin + 'd,
miso: impl OutputPin + 'd,
sclk: impl InputPin + OutputPin + 'd,
cs: Option<impl InputPin + OutputPin + 'd>,
intr: Option<impl InputPin + OutputPin + 'd>,
config: &crate::hal::spi::config::Config,
) -> esp_openthread_platform_config_t {
let cs_pin = if let Some(cs) = cs { cs.pin() as _ } else { -1 };
let intr_pin = if let Some(intr) = intr {
intr.pin() as _
} else {
-1
};
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_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,
#[cfg(not(esp_idf_version_at_least_6_0_0))]
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()
},
#[cfg(esp_idf_version_at_least_6_0_0)]
spi_interface: spi_bus_config_t {
__bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 {
__bindgen_anon_1: spi_bus_config_t__bindgen_ty_1__bindgen_ty_1 {
data4_io_num: -1,
data5_io_num: -1,
data6_io_num: -1,
data7_io_num: -1,
__bindgen_anon_1:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_1 {
mosi_io_num: mosi.pin() as _,
},
__bindgen_anon_2:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_2 {
miso_io_num: miso.pin() as _,
},
__bindgen_anon_3:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_3 {
quadwp_io_num: -1,
},
__bindgen_anon_4:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_4 {
quadhd_io_num: -1,
},
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,
}
}
fn host_uart_cfg<U: Uart + 'd>(
_uart: U,
tx: impl OutputPin + 'd,
rx: impl InputPin + 'd,
config: &crate::hal::uart::config::Config,
) -> esp_openthread_platform_config_t {
#[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,
};
cfg
}
}
#[cfg(esp_idf_soc_ieee802154_supported)]
impl<'d> ThreadDriver<'d, RCP> {
#[cfg(not(esp_idf_version_major = "4"))]
#[allow(clippy::too_many_arguments)]
pub fn new_rcp_spi<
M: crate::hal::modem::ThreadModemPeripheral + 'd,
S: crate::hal::spi::Spi + 'd,
>(
modem: M,
spi: S,
mosi: impl InputPin + 'd,
miso: impl OutputPin + 'd,
sclk: impl InputPin + OutputPin + 'd,
cs: Option<impl InputPin + OutputPin + 'd>,
intr: Option<impl InputPin + OutputPin + 'd>,
sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::internal_new(
Self::rcp_spi_cfg(modem, spi, mosi, miso, sclk, cs, intr),
sysloop,
nvs,
mounted_event_fs,
RCP(()),
)
}
#[allow(clippy::too_many_arguments)]
pub fn new_rcp_uart<M: crate::hal::modem::ThreadModemPeripheral + 'd, U: Uart + 'd>(
modem: M,
uart: U,
tx: impl OutputPin + 'd,
rx: impl InputPin + 'd,
config: &crate::hal::uart::config::Config,
sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::internal_new(
Self::rcp_uart_cfg(modem, uart, tx, rx, config),
sysloop,
nvs,
mounted_event_fs,
RCP(()),
)
}
#[cfg(not(esp_idf_version_major = "4"))]
#[allow(clippy::too_many_arguments)]
fn rcp_spi_cfg<
M: crate::hal::modem::ThreadModemPeripheral + 'd,
S: crate::hal::spi::Spi + 'd,
>(
_modem: M,
_spi: S,
mosi: impl InputPin + 'd,
miso: impl OutputPin + 'd,
sclk: impl InputPin + OutputPin + 'd,
cs: Option<impl InputPin + OutputPin + 'd>,
intr: Option<impl InputPin + OutputPin + 'd>,
) -> esp_openthread_platform_config_t {
let cs_pin = if let Some(cs) = cs { cs.pin() as _ } else { -1 };
let intr_pin = if let Some(intr) = 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 _,
#[cfg(not(esp_idf_version_at_least_6_0_0))]
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()
},
#[cfg(esp_idf_version_at_least_6_0_0)]
bus_config: spi_bus_config_t {
__bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 {
__bindgen_anon_1: spi_bus_config_t__bindgen_ty_1__bindgen_ty_1 {
data4_io_num: -1,
data5_io_num: -1,
data6_io_num: -1,
data7_io_num: -1,
__bindgen_anon_1:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_1 {
mosi_io_num: mosi.pin() as _,
},
__bindgen_anon_2:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_2 {
miso_io_num: miso.pin() as _,
},
__bindgen_anon_3:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_3 {
quadwp_io_num: -1,
},
__bindgen_anon_4:
spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_4 {
quadhd_io_num: -1,
},
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 rcp_uart_cfg<M: crate::hal::modem::ThreadModemPeripheral + 'd, U: Uart + 'd>(
_modem: M,
_uart: U,
tx: impl OutputPin + 'd,
rx: impl InputPin + 'd,
config: &crate::hal::uart::config::Config,
) -> esp_openthread_platform_config_t {
#[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<T> ThreadDriver<'_, 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,
};
#[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))]
pub fn init_coex(&mut self) -> Result<(), EspError> {
let _lock = self.inner();
Self::internal_init_coex()
}
pub fn start(&mut self) -> Result<(), EspError> {
{
let mut inner = self.inner();
if *inner.started.lock() {
Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>())?;
}
#[allow(clippy::manual_c_str_literals)]
unsafe {
crate::hal::task::create(
Self::run,
CStr::from_bytes_with_nul_unchecked(b"ThreadDriver Runner\0"),
12288,
&mut *inner as *mut _ as *mut _,
6,
None,
)?;
}
}
loop {
let inner = unsafe { self.inner.get().as_mut().unwrap() };
let started = inner.started.lock();
if *started {
break;
}
inner.started_condvar.wait(started);
}
info!("ThreadDriver started");
Ok(())
}
pub fn stop(&mut self) -> Result<(), EspError> {
{
let inner = self.inner();
if !*inner.started.lock() {
Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>())?;
}
#[allow(unused_mut)]
#[allow(unused_assignments)]
let mut stop_supported = false;
#[cfg(any(
esp_idf_version_at_least_5_5_0,
all(esp_idf_version = "5.1", esp_idf_version_at_least_5_1_7),
all(esp_idf_version = "5.2", esp_idf_version_at_least_5_2_6),
all(esp_idf_version = "5.3", esp_idf_version_at_least_5_3_4),
all(esp_idf_version = "5.4", esp_idf_version_at_least_5_4_3)
))]
{
unsafe {
esp_openthread_mainloop_exit();
}
stop_supported = true;
}
if !stop_supported {
panic!("Stopping the Thread driver is supported since ESP-IDF patch-level 5.3.3+, 5.4.3+ and 5.5.1+. Please update to a newer version or don't call `stop`.")
}
}
loop {
let inner = unsafe { self.inner.get().as_mut().unwrap() };
let started = inner.started.lock();
if !*started {
break;
}
inner.started_condvar.wait(started);
}
info!("ThreadDriver stopped");
Ok(())
}
pub fn is_started(&self) -> Result<bool, EspError> {
let inner = self.inner();
let started = *inner.started.lock();
Ok(started)
}
#[allow(clippy::mut_from_ref)]
fn inner(&self) -> ThreadDriverInnerGuard<'_> {
ThreadDriverInnerGuard {
inner: unsafe { &mut *self.inner.get() },
_lock: OtLock::acquire().unwrap(),
}
}
#[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))]
fn internal_init_coex() -> Result<(), EspError> {
#[cfg(esp_idf_esp_coex_sw_coexist_enable)]
{
esp!(unsafe { esp_coex_wifi_i154_enable() })?;
}
Ok(())
}
fn internal_new(
cfg: esp_openthread_platform_config_t,
_sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
mode: T,
) -> Result<Self, EspError> {
let mut inner = {
let mut inner = Box::new_uninit();
unsafe {
ThreadDriverInner::init(inner.as_mut_ptr(), cfg);
inner.assume_init()
}
};
esp!(unsafe { esp_openthread_init(&inner.cfg) })?;
let instance = unsafe { esp_openthread_get_instance() };
#[cfg(not(esp_idf_openthread_radio))]
unsafe {
otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL as _);
}
let srp = &mut inner.srp;
unsafe {
crate::sys::otSrpClientSetCallback(
instance,
Some(OtSrp::plat_c_srp_state_change_callback),
srp as *mut _ as *mut _,
)
}
T::init();
info!("ThreadDriver initialized");
Ok(Self {
inner: UnsafeCell::new(inner),
_nvs: nvs,
_mounted_event_fs: mounted_event_fs,
_mode: mode,
_p: PhantomData,
})
}
fn internal_deinit(&mut self) -> Result<(), EspError> {
let _ = self.stop();
esp!(unsafe { esp_openthread_deinit() })?;
Ok(())
}
extern "C" fn run(arg: *mut core::ffi::c_void) {
{
let _lock = OtLock::acquire().unwrap();
let inner = unsafe { (arg as *mut ThreadDriverInner).as_mut().unwrap() };
*inner.started.lock() = true;
inner.started_condvar.notify_all();
}
unsafe {
esp_openthread_launch_mainloop();
}
{
let _lock = OtLock::acquire().unwrap();
let inner = unsafe { (arg as *mut ThreadDriverInner).as_mut().unwrap() };
*inner.started.lock() = false;
inner.started_condvar.notify_all();
}
unsafe { crate::hal::task::destroy(core::ptr::null_mut()) }
}
}
impl<T> Drop for ThreadDriver<'_, T>
where
T: Mode,
{
fn drop(&mut self) {
self.internal_deinit().unwrap();
info!("ThreadDriver deinitialized");
}
}
unsafe impl<T> Send for ThreadDriver<'_, T> where T: Mode {}
unsafe impl<T> Sync for ThreadDriver<'_, T> where T: Mode {}
struct ThreadDriverInner {
cfg: esp_openthread_platform_config_t,
dataset_buf: otOperationalDatasetTlvs,
srp: OtSrp,
#[allow(clippy::type_complexity)]
ipv6_cb: Option<Box<Box<dyn FnMut(Ipv6Incoming) + Send + 'static>>>,
#[allow(clippy::type_complexity)]
scan_cb: Option<Box<Box<dyn FnMut(Option<ActiveScanResult>) + Send + 'static>>>,
#[allow(clippy::type_complexity)]
energy_cb: Option<Box<Box<dyn FnMut(Option<EnergyScanResult>) + Send + 'static>>>,
started: Mutex<bool>,
started_condvar: Condvar,
}
impl ThreadDriverInner {
unsafe fn init(this: *mut Self, cfg: esp_openthread_platform_config_t) {
addr_of_mut!((*this).cfg).write(cfg);
addr_of_mut!((*this).dataset_buf).write_bytes(0, 1);
OtSrp::init(addr_of_mut!((*this).srp));
addr_of_mut!((*this).ipv6_cb).write(None);
addr_of_mut!((*this).scan_cb).write(None);
addr_of_mut!((*this).energy_cb).write(None);
addr_of_mut!((*this).started).write(Mutex::new(false));
addr_of_mut!((*this).started_condvar).write(Condvar::new());
}
}
struct ThreadDriverInnerGuard<'a> {
inner: &'a mut ThreadDriverInner,
_lock: OtLock,
}
impl Deref for ThreadDriverInnerGuard<'_> {
type Target = ThreadDriverInner;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl DerefMut for ThreadDriverInnerGuard<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}
struct OtLock(PhantomData<*const ()>);
impl OtLock {
pub fn acquire() -> Result<Self, EspError> {
if !unsafe { esp_openthread_lock_acquire(delay::BLOCK) } {
Err(EspError::from_infallible::<ESP_ERR_TIMEOUT>())?;
}
Ok(Self(PhantomData))
}
}
impl Drop for OtLock {
fn drop(&mut self) {
unsafe {
esp_openthread_lock_release();
}
}
}
pub trait NetifMode {
fn init(&mut self) -> Result<(), EspError>;
fn deinit(&mut self) -> Result<(), EspError>;
}
pub struct Node(());
impl NetifMode for Node {
fn init(&mut self) -> Result<(), EspError> {
Ok(())
}
fn deinit(&mut self) -> Result<(), EspError> {
Ok(())
}
}
#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))]
pub struct BorderRouter(());
#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))]
impl NetifMode for BorderRouter {
fn init(&mut self) -> Result<(), EspError> {
#[cfg(not(esp_idf_version_major = "4"))]
{
esp!(unsafe { esp_openthread_border_router_init() })?;
}
debug!("Border router initialized");
Ok(())
}
fn deinit(&mut self) -> Result<(), EspError> {
esp!(unsafe { esp_openthread_border_router_deinit() })?;
debug!("Border router deinitialized");
Ok(())
}
}
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
pub struct EspThread<'d, T>
where
T: NetifMode,
{
driver: ThreadDriver<'d, Host>,
netif: EspNetif,
mode: T,
}
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
impl<'d> EspThread<'d, Node> {
#[cfg(esp_idf_soc_ieee802154_supported)]
pub fn new<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
modem: M,
sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::wrap(ThreadDriver::new(modem, sysloop, nvs, mounted_event_fs)?)
}
#[cfg(not(esp_idf_version_major = "4"))]
#[allow(clippy::too_many_arguments)]
pub fn new_spi<S: crate::hal::spi::Spi + 'd>(
_spi: S,
mosi: impl InputPin + 'd,
miso: impl OutputPin + 'd,
sclk: impl InputPin + OutputPin + 'd,
cs: Option<impl InputPin + OutputPin + 'd>,
intr: Option<impl InputPin + OutputPin + 'd>,
config: &crate::hal::spi::config::Config,
_sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::wrap(ThreadDriver::new_spi(
_spi,
mosi,
miso,
sclk,
cs,
intr,
config,
_sysloop,
nvs,
mounted_event_fs,
)?)
}
pub fn new_uart<U: Uart + 'd>(
_uart: U,
tx: impl OutputPin + 'd,
rx: impl InputPin + 'd,
config: &crate::hal::uart::config::Config,
_sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::wrap(ThreadDriver::new_uart(
_uart,
tx,
rx,
config,
_sysloop,
nvs,
mounted_event_fs,
)?)
}
pub fn wrap(driver: ThreadDriver<'d, Host>) -> Result<Self, EspError> {
Self::wrap_all(driver, EspNetif::new(NetifStack::Thread)?)
}
pub fn wrap_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result<Self, EspError> {
Self::internal_init(driver, netif, Node(()))
}
}
#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))]
impl<'d> EspThread<'d, BorderRouter> {
#[cfg(not(esp_idf_version_major = "4"))]
pub unsafe fn set_backbone_netif(backbone_netif: Option<&EspNetif>) {
unsafe {
esp_openthread_set_backbone_netif(
backbone_netif
.map(|netif| netif.handle())
.unwrap_or(core::ptr::null_mut()),
);
}
}
#[cfg(esp_idf_soc_ieee802154_supported)]
pub fn new_br<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
modem: M,
sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::wrap_br(ThreadDriver::new(modem, sysloop, nvs, mounted_event_fs)?)
}
#[cfg(not(esp_idf_version_major = "4"))]
#[allow(clippy::too_many_arguments)]
pub fn new_br_spi<S: crate::hal::spi::Spi + 'd>(
_spi: S,
mosi: impl InputPin + 'd,
miso: impl OutputPin + 'd,
sclk: impl InputPin + OutputPin + 'd,
cs: Option<impl InputPin + OutputPin + 'd>,
intr: Option<impl InputPin + OutputPin + 'd>,
config: &crate::hal::spi::config::Config,
_sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::wrap_br(ThreadDriver::new_spi(
_spi,
mosi,
miso,
sclk,
cs,
intr,
config,
_sysloop,
nvs,
mounted_event_fs,
)?)
}
#[allow(clippy::too_many_arguments)]
pub fn new_br_uart<U: Uart + 'd>(
_uart: U,
tx: impl OutputPin + 'd,
rx: impl InputPin + 'd,
config: &crate::hal::uart::config::Config,
_sysloop: EspSystemEventLoop,
nvs: EspDefaultNvsPartition,
mounted_event_fs: Arc<MountedEventfs>,
) -> Result<Self, EspError> {
Self::wrap_br(ThreadDriver::new_uart(
_uart,
tx,
rx,
config,
_sysloop,
nvs,
mounted_event_fs,
)?)
}
pub fn wrap_br(driver: ThreadDriver<'d, Host>) -> Result<Self, EspError> {
Self::wrap_br_all(driver, EspNetif::new(NetifStack::Thread)?)
}
pub fn wrap_br_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result<Self, EspError> {
Self::internal_init(driver, netif, BorderRouter(()))
}
}
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
impl<'d, T> EspThread<'d, T>
where
T: NetifMode,
{
pub fn driver(&self) -> &ThreadDriver<'d, Host> {
&self.driver
}
pub fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> {
&mut self.driver
}
#[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))]
pub fn init_coex(&mut self) -> Result<(), EspError> {
self.driver.init_coex()
}
pub fn netif(&self) -> &EspNetif {
&self.netif
}
pub fn enable_ipv6(&self, enabled: bool) -> Result<(), EspError> {
self.driver().enable_ipv6(enabled)
}
pub fn enable_thread(&self, enabled: bool) -> Result<(), EspError> {
self.driver().enable_thread(enabled)
}
pub fn role(&self) -> Result<Role, EspError> {
self.driver().role()
}
pub fn tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
self.driver().tod(buf)
}
pub fn pending_tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
self.driver().pending_tod(buf)
}
pub fn set_tod(&self, tod: &[u8]) -> Result<(), EspError> {
self.driver().set_tod(tod)
}
pub fn set_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
self.driver().set_tod_hexstr(tod)
}
pub fn set_pending_tod(&self, tod: &[u8]) -> Result<(), EspError> {
self.driver().set_pending_tod(tod)
}
pub fn set_pending_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
self.driver().set_pending_tod_hexstr(tod)
}
#[cfg(not(esp_idf_version_major = "4"))]
pub fn set_tod_from_cfg(&self) -> Result<(), EspError> {
self.driver().set_tod_from_cfg()
}
pub fn scan<F: FnMut(Option<ActiveScanResult>) + Send + 'static>(
&self,
callback: F,
) -> Result<(), EspError> {
self.driver().scan(callback)
}
pub fn is_scan_in_progress(&self) -> Result<bool, EspError> {
self.driver().is_scan_in_progress()
}
pub fn energy_scan<F: FnMut(Option<EnergyScanResult>) + Send + 'static>(
&self,
callback: F,
) -> Result<(), EspError> {
self.driver().energy_scan(callback)
}
pub fn is_energy_scan_in_progress(&self) -> Result<bool, EspError> {
self.driver().is_energy_scan_in_progress()
}
pub fn start(&mut self) -> Result<(), EspError> {
self.driver_mut().start()
}
pub fn stop(&mut self) -> Result<(), EspError> {
self.driver_mut().start()
}
pub fn is_started(&self) -> Result<bool, EspError> {
self.driver().is_started()
}
fn internal_init(
driver: ThreadDriver<'d, Host>,
netif: EspNetif,
mut mode: T,
) -> Result<Self, EspError> {
let inner = driver.inner();
let glue = unsafe { esp_openthread_netif_glue_init(&inner.cfg) };
assert!(!glue.is_null());
esp!(unsafe { esp_netif_attach(netif.handle() as *mut _, glue) })?;
mode.init()?;
info!("EspThread initialized");
Ok(Self {
netif,
mode,
driver,
})
}
fn internal_deinit(&mut self) -> Result<(), EspError> {
let _lock = self.driver.inner();
self.mode.deinit()?;
unsafe {
esp_openthread_netif_glue_deinit();
}
Ok(())
}
}
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
impl<T> Drop for EspThread<'_, T>
where
T: NetifMode,
{
fn drop(&mut self) {
self.internal_deinit().unwrap();
info!("EspThread deinitialized");
}
}
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
unsafe impl<T> Send for EspThread<'_, T> where T: NetifMode {}
#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
unsafe impl<T> Sync for EspThread<'_, T> where T: NetifMode {}
#[derive(Copy, Clone, Debug)]
pub enum ThreadEvent {
Started,
Stopped,
#[cfg(not(esp_idf_version_major = "4"))]
Detached,
#[cfg(not(esp_idf_version_major = "4"))]
Attached,
#[cfg(not(esp_idf_version_major = "4"))]
RoleChanged {
current_role: Role,
previous_role: Role,
},
IfUp,
IfDown,
GotIpv6,
LostIpv6,
MulticastJoined,
MulticastLeft,
#[cfg(not(esp_idf_version_major = "4"))]
TrelIpv6Added,
#[cfg(not(esp_idf_version_major = "4"))]
TrelIpv6Removed,
#[cfg(not(esp_idf_version_major = "4"))]
TrelMulticastJoined,
#[cfg(all(
not(esp_idf_version_major = "4"),
not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))
))]
DnsServerSet,
#[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,
#[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,
#[cfg(any(
esp_idf_version_patch_at_least_5_1_6,
esp_idf_version_patch_at_least_5_2_4,
esp_idf_version_patch_at_least_5_3_2,
esp_idf_version_at_least_5_4_0
))]
DatasetChanged,
}
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(data: &crate::eventloop::EspEvent) -> 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,
#[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 _
as *const esp_openthread_role_changed_event_t)
.as_ref()
}
.unwrap();
ThreadEvent::RoleChanged {
current_role: payload.current_role.into(),
previous_role: payload.previous_role.into(),
}
}
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
}
#[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,
#[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")
)),
),
))]
esp_openthread_event_t_OPENTHREAD_EVENT_PUBLISH_MESHCOP_E => {
ThreadEvent::MeshcopEPublishStarted
}
#[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")
)),
),
))]
esp_openthread_event_t_OPENTHREAD_EVENT_REMOVE_MESHCOP_E => {
ThreadEvent::MeshcopERemoveStarted
}
#[cfg(any(
esp_idf_version_patch_at_least_5_1_6,
esp_idf_version_patch_at_least_5_2_4,
esp_idf_version_patch_at_least_5_3_2,
esp_idf_version_at_least_5_4_0
))]
esp_openthread_event_t_OPENTHREAD_EVENT_DATASET_CHANGED => ThreadEvent::DatasetChanged,
_ => panic!("unknown event ID: {event_id}"),
}
}
}