Skip to main content

esp_idf_svc/
thread.rs

1// TODO:
2// - Prio A: Status report (we have driver::role() now; more, e.g. ipv6 notifications?)
3// - 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)
4// - 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)
5
6use core::cell::UnsafeCell;
7use core::ffi::{self, c_void, CStr};
8use core::fmt::Debug;
9use core::marker::PhantomData;
10use core::ops::{Deref, DerefMut};
11use core::ptr::addr_of_mut;
12
13use alloc::boxed::Box;
14use alloc::sync::Arc;
15
16#[allow(unused)]
17use ::log::{debug, info, warn};
18
19use crate::eventloop::{EspEventDeserializer, EspEventSource, EspSystemEventLoop};
20use crate::hal::delay;
21use crate::hal::gpio::{InputPin, OutputPin};
22use crate::hal::uart::Uart;
23#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
24use crate::handle::RawHandle;
25use crate::io::vfs::MountedEventfs;
26#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
27use crate::netif::*;
28use crate::nvs::EspDefaultNvsPartition;
29use crate::private::mutex::{Condvar, Mutex};
30use crate::sys::*;
31use crate::thread::srp::OtSrp;
32
33pub use srp::*;
34
35extern crate alloc;
36
37mod srp;
38
39/// A trait shared between the `Host` and `RCP` modes providing the option for these
40/// to do additional initialization.
41pub trait Mode {
42    fn init();
43}
44
45/// The driver will operate in Radio Co-Processor mode
46///
47/// The chip needs to be connected via UART or SPI to the host
48#[cfg(esp_idf_soc_ieee802154_supported)]
49#[derive(Debug)]
50pub struct RCP(());
51
52#[cfg(esp_idf_soc_ieee802154_supported)]
53impl Mode for RCP {
54    fn init() {
55        //#[cfg(esp_idf_openthread_ncp_vendor_hook)]
56        {
57            extern "C" {
58                fn otAppNcpInit(instance: *mut otInstance);
59            }
60
61            unsafe {
62                otAppNcpInit(esp_openthread_get_instance());
63            }
64        }
65    }
66}
67
68/// The driver will operate as a host
69///
70/// This means that - unless the chip has a native Thread suppoort -
71/// it needs to be connected via UART or SPI to another chip which does have
72/// native Thread support and which is configured to operate in RCP mode
73#[derive(Debug)]
74pub struct Host(());
75
76impl Mode for Host {
77    fn init() {}
78}
79
80pub mod config {
81    use crate::hal::uart::config::*;
82    use crate::hal::units::*;
83
84    /// A safe baud rate for the UART
85    #[cfg(all(esp32c2, esp_idf_xtal_freq_26))]
86    pub const UART_SAFE_BAUD_RATE: Hertz = Hertz(74880);
87
88    /// A safe baud rate for the UART
89    #[cfg(not(all(esp32c2, esp_idf_xtal_freq_26)))]
90    pub const UART_SAFE_BAUD_RATE: Hertz = Hertz(115200);
91
92    /// A safe default UART configuration
93    pub fn uart_default_cfg() -> Config {
94        Config::new()
95            .baudrate(UART_SAFE_BAUD_RATE)
96            .data_bits(DataBits::DataBits8)
97            .parity_none()
98            .stop_bits(StopBits::STOP1)
99            .flow_control(FlowControl::None)
100            .flow_control_rts_threshold(0)
101    }
102}
103
104macro_rules! ot_esp {
105    ($err:expr) => {{
106        $crate::sys::esp!($crate::thread::ot_esp_code($err as u32))
107    }};
108}
109
110pub(crate) use ot_esp;
111
112#[allow(non_upper_case_globals, non_snake_case)]
113pub(crate) const fn ot_esp_code(ot_code: u32) -> esp_err_t {
114    match ot_code {
115        crate::sys::otError_OT_ERROR_NONE => crate::sys::ESP_OK as _,
116        crate::sys::otError_OT_ERROR_FAILED => crate::sys::ESP_FAIL as _,
117        _ => crate::sys::ESP_FAIL as _, // For now
118    }
119}
120
121pub(crate) fn ot_esp_err(ot_code: u32) -> EspError {
122    EspError::from(ot_esp_code(ot_code)).unwrap()
123}
124
125/// Active scan result
126pub struct ActiveScanResult<'a>(&'a otActiveScanResult);
127
128impl<'a> ActiveScanResult<'a> {
129    /// IEEE 802.15.4 Extended Address
130    pub fn extended_address(&self) -> &'a [u8] {
131        &self.0.mExtAddress.m8
132    }
133
134    /// Thread Network Name
135    pub fn network_name_cstr(&self) -> &'a CStr {
136        unsafe { ffi::CStr::from_ptr(&self.0.mNetworkName.m8 as *const _ as *const _) }
137    }
138
139    /// Thread Extended PAN ID
140    pub fn extended_pan_id(&self) -> &[u8] {
141        &self.0.mExtendedPanId.m8
142    }
143
144    /// Steering Data
145    pub fn steering_data(&self) -> &[u8] {
146        &self.0.mSteeringData.m8
147    }
148
149    /// IEEE 802.15.4 PAN ID
150    pub fn pan_id(&self) -> u16 {
151        self.0.mPanId
152    }
153
154    /// Joiner UDP Port
155    pub fn joiner_udp_port(&self) -> u16 {
156        self.0.mJoinerUdpPort
157    }
158
159    /// IEEE 802.15.4 Channel
160    pub fn channel(&self) -> u8 {
161        self.0.mChannel
162    }
163
164    /// The max RSSI (dBm)
165    pub fn max_rssi(&self) -> i8 {
166        self.0.mRssi
167    }
168
169    /// LQI
170    pub fn lqi(&self) -> u8 {
171        self.0.mLqi
172    }
173
174    /// Version
175    pub fn version(&self) -> u8 {
176        self.0.mVersion() as _
177    }
178
179    /// Native Commissioner
180    pub fn native_commissioner(&self) -> bool {
181        self.0.mIsNative()
182    }
183
184    /// Join permitted
185    pub fn join_permitted(&self) -> bool {
186        self.0.mIsJoinable()
187    }
188}
189
190/// Energy scan result
191pub struct EnergyScanResult<'a>(&'a otEnergyScanResult);
192
193impl EnergyScanResult<'_> {
194    /// IEEE 802.15.4 Channel
195    pub fn channel(&self) -> u8 {
196        self.0.mChannel
197    }
198
199    /// The max RSSI (dBm)
200    pub fn max_rssi(&self) -> i8 {
201        self.0.mMaxRssi
202    }
203}
204
205/// The current role of the device in the Thread network
206#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
207pub enum Role {
208    Disabled,
209    Detached,
210    Child,
211    Router,
212    Leader,
213}
214
215#[allow(non_upper_case_globals, non_snake_case)]
216impl From<otDeviceRole> for Role {
217    fn from(role: otDeviceRole) -> Self {
218        match role {
219            otDeviceRole_OT_DEVICE_ROLE_DISABLED => Role::Disabled,
220            otDeviceRole_OT_DEVICE_ROLE_DETACHED => Role::Detached,
221            otDeviceRole_OT_DEVICE_ROLE_CHILD => Role::Child,
222            otDeviceRole_OT_DEVICE_ROLE_ROUTER => Role::Router,
223            otDeviceRole_OT_DEVICE_ROLE_LEADER => Role::Leader,
224            _ => Role::Disabled,
225        }
226    }
227}
228
229/// The Ipv6 packet received from Thread via the `ThreadDriver::set_rx_callback` method
230pub struct Ipv6Packet<'a>(&'a otMessage);
231
232impl Ipv6Packet<'_> {
233    pub fn raw(&self) -> &otMessage {
234        self.0
235    }
236
237    #[allow(clippy::len_without_is_empty)]
238    pub fn len(&self) -> usize {
239        unsafe { otMessageGetLength(self.0) as _ }
240    }
241
242    pub fn offset(&self) -> usize {
243        unsafe { otMessageGetOffset(self.0) as _ }
244    }
245
246    pub fn read(&self, offset: usize, buf: &mut [u8]) -> usize {
247        let len = self.len();
248
249        unsafe { otMessageRead(self.0, offset as _, buf.as_mut_ptr() as *mut _, len as _) as _ }
250    }
251}
252
253/// The incoming Ipv6 data received from Thread via the `ThreadDriver::set_rx_callback` method
254pub enum Ipv6Incoming<'a> {
255    /// A notification that an IPv6 address was added to the device
256    AddressAdded(core::net::Ipv6Addr),
257    /// A notification that an IPv6 address was removed from the device
258    AddressRemoved(core::net::Ipv6Addr),
259    /// An incoming raw IPv6 packet
260    Data(Ipv6Packet<'a>),
261}
262
263/// This struct provides a safe wrapper over the ESP IDF Thread C driver.
264///
265/// The driver works on Layer 2 (Data Link) in the OSI model, in that it provides
266/// facilities for sending and receiving ethernet packets over the Thread radio.
267///
268/// For most use cases, utilizing `EspThread` - which provides a networking (IP)
269/// layer as well - should be preferred. Using `ThreadDriver` directly is beneficial
270/// only when one would like to utilize a custom, non-STD network stack like `smoltcp`.
271///
272/// The driver can work in two modes:
273/// - RCP (Radio Co-Processor) mode: The driver operates as a co-processor to the host,
274///   which is expected to be another chip connected to ours via SPI or UART. This is
275///   of course only supported with MCUs that do have a Thread radio, like esp32c2 and esp32c6
276/// - Host mode: The driver operates as a host, and if the chip does not have a Thread radio
277///   it has to be connected via SPI or USB to a chip which runs the Thread stack in RCP mode
278pub struct ThreadDriver<'d, T>
279where
280    T: Mode,
281{
282    inner: UnsafeCell<Box<ThreadDriverInner>>,
283    //_subscription: EspSubscription<'static, System>,
284    _nvs: EspDefaultNvsPartition,
285    _mounted_event_fs: Arc<MountedEventfs>,
286    _mode: T,
287    _p: PhantomData<&'d mut ()>,
288}
289
290impl<'d> ThreadDriver<'d, Host> {
291    /// Create a new Thread Host driver instance utilizing the
292    /// native Thread radio on the MCU
293    #[cfg(esp_idf_soc_ieee802154_supported)]
294    pub fn new<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
295        modem: M,
296        sysloop: EspSystemEventLoop,
297        nvs: EspDefaultNvsPartition,
298        mounted_event_fs: Arc<MountedEventfs>,
299    ) -> Result<Self, EspError> {
300        Self::internal_new(
301            Self::host_native_cfg(modem),
302            sysloop,
303            nvs,
304            mounted_event_fs,
305            Host(()),
306        )
307    }
308
309    /// Create a new Thread Host driver instance utilizing an SPI connection
310    /// to another MCU running the Thread stack in RCP mode.
311    #[cfg(not(esp_idf_version_major = "4"))]
312    #[allow(clippy::too_many_arguments)]
313    pub fn new_spi<S: crate::hal::spi::Spi + 'd>(
314        spi: S,
315        mosi: impl InputPin + 'd,
316        miso: impl OutputPin + 'd,
317        sclk: impl InputPin + OutputPin + 'd,
318        cs: Option<impl InputPin + OutputPin + 'd>,
319        intr: Option<impl InputPin + OutputPin + 'd>,
320        config: &crate::hal::spi::config::Config,
321        sysloop: EspSystemEventLoop,
322        nvs: EspDefaultNvsPartition,
323        mounted_event_fs: Arc<MountedEventfs>,
324    ) -> Result<Self, EspError> {
325        Self::internal_new(
326            Self::host_spi_cfg(spi, mosi, miso, sclk, cs, intr, config),
327            sysloop,
328            nvs,
329            mounted_event_fs,
330            Host(()),
331        )
332    }
333
334    /// Create a new Thread Host driver instance utilizing a UART connection
335    /// to another MCU running the Thread stack in RCP mode.
336    pub fn new_uart<U: Uart + 'd>(
337        uart: U,
338        tx: impl OutputPin + 'd,
339        rx: impl InputPin + 'd,
340        config: &crate::hal::uart::config::Config,
341        sysloop: EspSystemEventLoop,
342        nvs: EspDefaultNvsPartition,
343        mounted_event_fs: Arc<MountedEventfs>,
344    ) -> Result<Self, EspError> {
345        Self::internal_new(
346            Self::host_uart_cfg(uart, tx, rx, config),
347            sysloop,
348            nvs,
349            mounted_event_fs,
350            Host(()),
351        )
352    }
353
354    /// Enable or disable the network interface of the Thread driver
355    pub fn enable_ipv6(&self, enable: bool) -> Result<(), EspError> {
356        let _lock = self.inner();
357
358        ot_esp!(unsafe { otIp6SetEnabled(esp_openthread_get_instance(), enable) })
359    }
360
361    /// Enable or disable Thread
362    ///
363    /// When enabling, this should be called after the network interface is enabled
364    pub fn enable_thread(&self, enable: bool) -> Result<(), EspError> {
365        let _lock = self.inner();
366
367        ot_esp!(unsafe { otThreadSetEnabled(esp_openthread_get_instance(), enable) })
368    }
369
370    /// Retrieve the current role of the device in the Thread network
371    pub fn role(&self) -> Result<Role, EspError> {
372        let _lock = self.inner();
373
374        Ok(unsafe { otThreadGetDeviceRole(esp_openthread_get_instance()) }.into())
375    }
376
377    /// Initialize the Thread command-line interface (CLI) for debugging purposes.
378    ///
379    /// NOTE: This function can only be called once.
380    #[cfg(esp_idf_openthread_cli)]
381    pub fn init_cli(&mut self) -> Result<(), EspError> {
382        // TODO: Can only be called once; track this
383
384        unsafe {
385            esp_openthread_cli_init();
386        }
387
388        #[cfg(esp_idf_openthread_cli_esp_extension)]
389        unsafe {
390            esp_cli_custom_command_init();
391        }
392
393        unsafe {
394            esp_openthread_cli_create_task();
395        }
396
397        Ok(())
398    }
399
400    /// Retrieve the active TOD (Thread Operational Dataset) in the user-supplied buffer
401    ///
402    /// Return the size of the TOD data written to the buffer
403    ///
404    /// The TOD is in Thread TLV format.
405    pub fn tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
406        let mut inner = self.inner();
407
408        Self::internal_tod(&mut inner, true, buf)
409    }
410
411    /// Retrieve the pending TOD (Thread Operational Dataset) in the user-supplied buffer
412    ///
413    /// Return the size of the TOD data written to the buffer
414    ///
415    /// The TOD is in Thread TLV format.
416    pub fn pending_tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
417        let mut inner = self.inner();
418
419        Self::internal_tod(&mut inner, false, buf)
420    }
421
422    /// Set the active TOD (Thread Operational Dataset) to the provided data
423    ///
424    /// The TOD data should be in Thread TLV format.
425    pub fn set_tod(&self, tod: &[u8]) -> Result<(), EspError> {
426        let mut inner = self.inner();
427
428        Self::fill_dataset_tlv(&mut inner.dataset_buf, tod)?;
429
430        Self::internal_set_tod(&mut inner, true)
431    }
432
433    /// Set the pending TOD (Thread Operational Dataset) to the provided data
434    ///
435    /// The TOD data should be in Thread TLV format.
436    pub fn set_pending_tod(&self, tod: &[u8]) -> Result<(), EspError> {
437        let mut inner = self.inner();
438
439        Self::fill_dataset_tlv(&mut inner.dataset_buf, tod)?;
440
441        Self::internal_set_tod(&mut inner, false)
442    }
443
444    /// Set the active TOD (Thread Operational Dataset) to the provided data
445    ///
446    /// The TOD data should be in Thread TLV format.
447    pub fn set_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
448        let mut inner = self.inner();
449
450        Self::fill_dataset_tlv_hexstr(&mut inner.dataset_buf, tod)?;
451
452        Self::internal_set_tod(&mut inner, true)
453    }
454
455    /// Set the pending TOD (Thread Operational Dataset) to the provided data
456    ///
457    /// The TOD data should be in Thread TLV format.
458    pub fn set_pending_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
459        let mut inner = self.inner();
460
461        Self::fill_dataset_tlv_hexstr(&mut inner.dataset_buf, tod)?;
462
463        Self::internal_set_tod(&mut inner, false)
464    }
465
466    /// Set the active TOD (Thread Operational Dataset) according to the
467    /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app
468    /// during build (via `sdkconfig*`)
469    #[cfg(not(esp_idf_version_major = "4"))]
470    pub fn set_tod_from_cfg(&self) -> Result<(), EspError> {
471        let _lock = self.inner();
472
473        ot_esp!(unsafe { esp_openthread_auto_start(core::ptr::null_mut()) })
474    }
475
476    /// Perform an active scan for Thread networks
477    ///
478    /// The callback will be called for each found network
479    /// At the end of the scan, the callback will be called with `None`
480    pub fn scan<F: FnMut(Option<ActiveScanResult>) + Send + 'static>(
481        &self,
482        callback: F,
483    ) -> Result<(), EspError> {
484        let mut inner = self.inner();
485
486        if inner.scan_cb.is_some() {
487            return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
488        }
489
490        #[allow(clippy::type_complexity)]
491        let callback: Box<Box<dyn FnMut(Option<ActiveScanResult>) + Send + 'static>> =
492            Box::new(Box::new(callback));
493
494        // Store callback where on_active_scan_result can get to it
495        inner.scan_cb = Some(callback);
496
497        match ot_esp!(unsafe {
498            otLinkActiveScan(
499                esp_openthread_get_instance(),
500                0xffff_ffffu32, // All channels
501                200,            // ms scan per channel
502                Some(Self::on_active_scan_result),
503                &mut *inner as *mut ThreadDriverInner as *mut c_void,
504            )
505        }) {
506            Ok(()) => Ok(()),
507            Err(err) => {
508                // Clean up inner if we fail to start the scan
509                inner.scan_cb = None;
510                Err(err)
511            }
512        }
513    }
514
515    /// Check if an active scan is in progress
516    pub fn is_scan_in_progress(&self) -> Result<bool, EspError> {
517        let _lock = self.inner();
518
519        Ok(unsafe { otLinkIsActiveScanInProgress(esp_openthread_get_instance()) })
520    }
521
522    /// Perform an energy scan for Thread networks
523    ///
524    /// The callback will be called for each found network
525    /// At the end of the scan, the callback will be called with `None`
526    pub fn energy_scan<F: FnMut(Option<EnergyScanResult>) + Send + 'static>(
527        &self,
528        callback: F,
529    ) -> Result<(), EspError> {
530        let mut inner = self.inner();
531
532        if inner.energy_cb.is_some() {
533            return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
534        }
535
536        #[allow(clippy::type_complexity)]
537        let callback: Box<Box<dyn FnMut(Option<EnergyScanResult>) + Send + 'static>> =
538            Box::new(Box::new(callback));
539
540        // Store callback where on_energy_scan_result can get to it
541        inner.energy_cb = Some(callback);
542
543        match ot_esp!(unsafe {
544            otLinkEnergyScan(
545                esp_openthread_get_instance(),
546                0xffff_ffffu32, // All channels
547                200,            // ms scan per channel
548                Some(Self::on_energy_scan_result),
549                &mut *inner as *mut ThreadDriverInner as *mut c_void,
550            )
551        }) {
552            Ok(()) => Ok(()),
553            Err(err) => {
554                // Clean up inner if we fail to start the scan
555                inner.energy_cb = None;
556                Err(err)
557            }
558        }
559    }
560
561    /// Check if an energy scan is in progress
562    pub fn is_energy_scan_in_progress(&self) -> Result<bool, EspError> {
563        let _lock = self.inner();
564
565        Ok(unsafe { otLinkIsEnergyScanInProgress(esp_openthread_get_instance()) })
566    }
567
568    /// Send an Ipv6 raw packet over Thread
569    pub fn tx(&self, packet: &[u8]) -> Result<(), EspError> {
570        let _lock = self.inner();
571
572        let message =
573            unsafe { otIp6NewMessage(esp_openthread_get_instance(), core::ptr::null_mut()) };
574        if message.is_null() {
575            Err(EspError::from_infallible::<ESP_FAIL>())?;
576        }
577
578        let result = ot_esp!(unsafe {
579            otMessageAppend(message, packet.as_ptr() as *const _, packet.len() as _)
580        })
581        .and_then(|_| ot_esp!(unsafe { otIp6Send(esp_openthread_get_instance(), message) }));
582
583        unsafe { otMessageFree(message) };
584
585        result
586    }
587
588    /// Set a callback function for receiving Ipv6 raw packets from Thread
589    pub fn set_rx_callback<R>(&self, callback: Option<R>) -> Result<(), EspError>
590    where
591        R: FnMut(Ipv6Incoming) + Send + 'static,
592    {
593        let mut inner = self.inner();
594
595        Self::internal_set_rx_callback(&mut inner, callback)
596    }
597
598    /// Set a callback function for receiving Ipv6 raw packets from Thread
599    ///
600    /// # Safety
601    ///
602    /// This method - in contrast to method `set_rx_callback` - allows the user to pass
603    /// non-static callback/closure. This enables users to borrow
604    /// - in the closure - variables that live on the stack - or more generally - in the same
605    ///   scope where the service is created.
606    ///
607    /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the service,
608    /// as that would immediately lead to an UB (crash).
609    /// Also note that forgetting the service might happen with `Rc` and `Arc`
610    /// when circular references are introduced: <https://github.com/rust-lang/rust/issues/24456>
611    ///
612    /// The reason is that the closure is actually sent to a hidden ESP IDF thread.
613    /// This means that if the service is forgotten, Rust is free to e.g. unwind the stack
614    /// and the closure now owned by this other thread will end up with references to variables that no longer exist.
615    ///
616    /// The destructor of the service takes care - prior to the service being dropped and e.g.
617    /// the stack being unwind - to remove the closure from the hidden thread and destroy it.
618    /// Unfortunately, when the service is forgotten, the un-subscription does not happen
619    /// and invalid references are left dangling.
620    ///
621    /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types
622    /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor).
623    pub fn set_nonstatic_rx_callback<R>(&self, callback: Option<R>) -> Result<(), EspError>
624    where
625        R: FnMut(Ipv6Incoming) + Send + 'd,
626    {
627        let mut inner = self.inner();
628
629        Self::internal_set_rx_callback(&mut inner, callback)
630    }
631
632    // NOTE: Methods starting with `internal_` have to be called only when the OpenThread lock is held
633
634    fn internal_set_rx_callback<R>(
635        inner: &mut ThreadDriverInner,
636        callback: Option<R>,
637    ) -> Result<(), EspError>
638    where
639        R: FnMut(Ipv6Incoming) + Send + 'd,
640    {
641        if let Some(callback) = callback {
642            #[allow(clippy::type_complexity)]
643            let callback: Box<Box<dyn FnMut(Ipv6Incoming) + Send + 'd>> =
644                Box::new(Box::new(callback));
645
646            #[allow(clippy::type_complexity)]
647            let callback: Box<Box<dyn FnMut(Ipv6Incoming) + Send + 'static>> =
648                unsafe { core::mem::transmute(callback) };
649
650            // Stick the callback where Self::on_address/Self::on_packet can get to it
651            inner.ipv6_cb = Some(callback);
652
653            unsafe {
654                otIp6SetAddressCallback(
655                    esp_openthread_get_instance(),
656                    Some(Self::on_address),
657                    &mut *inner as *mut ThreadDriverInner as *mut c_void,
658                );
659                otIp6SetReceiveCallback(
660                    esp_openthread_get_instance(),
661                    Some(Self::on_packet),
662                    &mut *inner as *mut ThreadDriverInner as *mut c_void,
663                );
664                otIp6SetReceiveFilterEnabled(esp_openthread_get_instance(), true);
665
666                // TODO otIcmp6SetEchoMode(esp_openthread_get_instance(), OT_ICMP6_ECHO_HANDLER_RLOC_ALOC_ONLY);
667            }
668        } else {
669            unsafe {
670                otIp6SetAddressCallback(esp_openthread_get_instance(), None, core::ptr::null_mut());
671                otIp6SetReceiveCallback(esp_openthread_get_instance(), None, core::ptr::null_mut());
672                otIp6SetReceiveFilterEnabled(esp_openthread_get_instance(), true);
673                // TODO otIcmp6SetEchoMode(esp_openthread_get_instance(), OT_ICMP6_ECHO_HANDLER_RLOC_ALOC_ONLY);
674            }
675
676            inner.ipv6_cb = None;
677        }
678
679        Ok(())
680    }
681
682    fn internal_tod(
683        inner: &mut ThreadDriverInner,
684        active: bool,
685        buf: &mut [u8],
686    ) -> Result<usize, EspError> {
687        let dataset_buf = &mut inner.dataset_buf;
688
689        ot_esp!(unsafe {
690            if active {
691                otDatasetGetActiveTlvs(esp_openthread_get_instance(), dataset_buf)
692            } else {
693                otDatasetGetPendingTlvs(esp_openthread_get_instance(), dataset_buf)
694            }
695        })?;
696
697        let len = dataset_buf.mLength as usize;
698        if buf.len() < len {
699            Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>())?;
700        }
701
702        buf[..len].copy_from_slice(&dataset_buf.mTlvs[..len]);
703
704        Ok(len)
705    }
706
707    fn internal_set_tod(inner: &mut ThreadDriverInner, active: bool) -> Result<(), EspError> {
708        ot_esp!(unsafe {
709            if active {
710                otDatasetSetActiveTlvs(esp_openthread_get_instance(), &inner.dataset_buf)
711            } else {
712                otDatasetSetPendingTlvs(esp_openthread_get_instance(), &inner.dataset_buf)
713            }
714        })?;
715
716        Ok(())
717    }
718
719    fn fill_dataset_tlv(
720        dataset_buf: &mut otOperationalDatasetTlvs,
721        data: &[u8],
722    ) -> Result<(), EspError> {
723        if data.len() > core::mem::size_of_val(&dataset_buf.mTlvs) {
724            Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>())?;
725        }
726
727        dataset_buf.mLength = data.len() as _;
728        dataset_buf.mTlvs[..data.len()].copy_from_slice(data);
729
730        Ok(())
731    }
732
733    /// Populates the internal OT TLV dataset structure with the given dataset in HEX-TLV str format.
734    fn fill_dataset_tlv_hexstr(
735        dataset_buf: &mut otOperationalDatasetTlvs,
736        dataset: &str,
737    ) -> Result<(), EspError> {
738        let dataset = dataset.trim();
739        let mut offset = 0;
740
741        for (chf, chs) in dataset
742            .chars()
743            .step_by(2)
744            .zip(dataset.chars().skip(1).step_by(2))
745        {
746            let byte = (chf
747                .to_digit(16)
748                .ok_or(ot_esp_err(otError_OT_ERROR_INVALID_ARGS))?
749                << 4)
750                | chs
751                    .to_digit(16)
752                    .ok_or(ot_esp_err(otError_OT_ERROR_INVALID_ARGS))?;
753
754            if offset >= dataset_buf.mTlvs.len() {
755                Err(ot_esp_err(otError_OT_ERROR_NO_BUFS))?;
756            }
757
758            dataset_buf.mTlvs[offset] = byte as _;
759            offset += 1;
760        }
761
762        dataset_buf.mLength = offset as _;
763
764        Ok(())
765    }
766
767    unsafe extern "C" fn on_address(
768        address_info: *const otIp6AddressInfo,
769        is_added: bool,
770        context: *mut c_void,
771    ) {
772        let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
773
774        if let Some(ipv6_cb) = inner.ipv6_cb.as_mut() {
775            let address_info = unsafe { address_info.as_ref() }.unwrap();
776            let ot_address = unsafe { address_info.mAddress.as_ref() }.unwrap();
777
778            let address = core::net::Ipv6Addr::from(ot_address.mFields.m8);
779
780            if is_added {
781                ipv6_cb(Ipv6Incoming::AddressAdded(address));
782            } else {
783                ipv6_cb(Ipv6Incoming::AddressRemoved(address));
784            }
785        }
786    }
787
788    unsafe extern "C" fn on_packet(message: *mut otMessage, context: *mut c_void) {
789        let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
790
791        if let Some(ipv6_cb) = inner.ipv6_cb.as_mut() {
792            ipv6_cb(Ipv6Incoming::Data(Ipv6Packet(
793                unsafe { message.as_ref() }.unwrap(),
794            )));
795        }
796
797        otMessageFree(message);
798    }
799
800    unsafe extern "C" fn on_active_scan_result(
801        result: *mut otActiveScanResult,
802        context: *mut c_void,
803    ) {
804        let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
805
806        if let Some(scan_cb) = inner.scan_cb.as_mut() {
807            if result.is_null() {
808                scan_cb(None);
809            } else {
810                scan_cb(Some(ActiveScanResult(unsafe { result.as_ref() }.unwrap())));
811            }
812        }
813
814        if result.is_null() {
815            inner.scan_cb = None;
816        }
817    }
818
819    unsafe extern "C" fn on_energy_scan_result(
820        result: *mut otEnergyScanResult,
821        context: *mut c_void,
822    ) {
823        let inner = unsafe { (context as *mut ThreadDriverInner).as_mut().unwrap() };
824
825        if let Some(energy_cb) = inner.energy_cb.as_mut() {
826            if result.is_null() {
827                energy_cb(None);
828            } else {
829                energy_cb(Some(EnergyScanResult(unsafe { result.as_ref() }.unwrap())));
830            }
831        }
832
833        if result.is_null() {
834            inner.energy_cb = None;
835        }
836    }
837
838    #[cfg(esp_idf_soc_ieee802154_supported)]
839    fn host_native_cfg<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
840        _modem: M,
841    ) -> esp_openthread_platform_config_t {
842        esp_openthread_platform_config_t {
843            radio_config: esp_openthread_radio_config_t {
844                radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE,
845                ..Default::default()
846            },
847            host_config: esp_openthread_host_connection_config_t {
848                host_connection_mode:
849                    esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE,
850                ..Default::default()
851            },
852            port_config: Self::PORT_CONFIG,
853        }
854    }
855
856    #[cfg(not(esp_idf_version_major = "4"))]
857    #[allow(clippy::too_many_arguments)]
858    fn host_spi_cfg<S: crate::hal::spi::Spi + 'd>(
859        _spi: S,
860        mosi: impl InputPin + 'd,
861        miso: impl OutputPin + 'd,
862        sclk: impl InputPin + OutputPin + 'd,
863        cs: Option<impl InputPin + OutputPin + 'd>,
864        intr: Option<impl InputPin + OutputPin + 'd>,
865        config: &crate::hal::spi::config::Config,
866    ) -> esp_openthread_platform_config_t {
867        let cs_pin = if let Some(cs) = cs { cs.pin() as _ } else { -1 };
868
869        let intr_pin = if let Some(intr) = intr {
870            intr.pin() as _
871        } else {
872            -1
873        };
874
875        let mut icfg: spi_device_interface_config_t = config.into();
876        icfg.spics_io_num = cs_pin as _;
877
878        esp_openthread_platform_config_t {
879            radio_config: esp_openthread_radio_config_t {
880                radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_SPI_RCP,
881                __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 {
882                    radio_spi_config: esp_openthread_spi_host_config_t {
883                        host_device: S::device() as _,
884                        dma_channel: spi_common_dma_t_SPI_DMA_CH_AUTO,
885                        #[cfg(not(esp_idf_version_at_least_6_0_0))]
886                        spi_interface: spi_bus_config_t {
887                            __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 {
888                                mosi_io_num: mosi.pin() as _,
889                            },
890                            __bindgen_anon_2: spi_bus_config_t__bindgen_ty_2 {
891                                miso_io_num: miso.pin() as _,
892                            },
893                            sclk_io_num: sclk.pin() as _,
894                            ..Default::default()
895                        },
896                        #[cfg(esp_idf_version_at_least_6_0_0)]
897                        spi_interface: spi_bus_config_t {
898                            __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 {
899                                __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1__bindgen_ty_1 {
900                                    data4_io_num: -1,
901                                    data5_io_num: -1,
902                                    data6_io_num: -1,
903                                    data7_io_num: -1,
904                                    __bindgen_anon_1:
905                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_1 {
906                                            mosi_io_num: mosi.pin() as _,
907                                        },
908                                    __bindgen_anon_2:
909                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_2 {
910                                            miso_io_num: miso.pin() as _,
911                                        },
912                                    __bindgen_anon_3:
913                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_3 {
914                                            quadwp_io_num: -1,
915                                        },
916                                    __bindgen_anon_4:
917                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_4 {
918                                            quadhd_io_num: -1,
919                                        },
920                                    sclk_io_num: sclk.pin() as _,
921                                },
922                            },
923                            ..Default::default()
924                        },
925                        spi_device: icfg,
926                        intr_pin,
927                    },
928                },
929            },
930            host_config: esp_openthread_host_connection_config_t {
931                host_connection_mode:
932                    esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE,
933                ..Default::default()
934            },
935            port_config: Self::PORT_CONFIG,
936        }
937    }
938
939    fn host_uart_cfg<U: Uart + 'd>(
940        _uart: U,
941        tx: impl OutputPin + 'd,
942        rx: impl InputPin + 'd,
943        config: &crate::hal::uart::config::Config,
944    ) -> esp_openthread_platform_config_t {
945        #[cfg(esp_idf_version_major = "4")]
946        let cfg = esp_openthread_platform_config_t {
947            radio_config: esp_openthread_radio_config_t {
948                radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP,
949                radio_uart_config: esp_openthread_uart_config_t {
950                    port: U::port() as _,
951                    uart_config: config.into(),
952                    rx_pin: rx.pin() as _,
953                    tx_pin: tx.pin() as _,
954                },
955            },
956            host_config: esp_openthread_host_connection_config_t {
957                host_connection_mode:
958                    esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE,
959                ..Default::default()
960            },
961            port_config: Self::PORT_CONFIG,
962        };
963
964        #[cfg(not(esp_idf_version_major = "4"))]
965        let cfg = esp_openthread_platform_config_t {
966            radio_config: esp_openthread_radio_config_t {
967                radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_UART_RCP,
968                __bindgen_anon_1: esp_openthread_radio_config_t__bindgen_ty_1 {
969                    radio_uart_config: esp_openthread_uart_config_t {
970                        port: U::port() as _,
971                        uart_config: config.into(),
972                        rx_pin: rx.pin() as _,
973                        tx_pin: tx.pin() as _,
974                    },
975                },
976            },
977            host_config: esp_openthread_host_connection_config_t {
978                host_connection_mode:
979                    esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_NONE,
980                ..Default::default()
981            },
982            port_config: Self::PORT_CONFIG,
983        };
984
985        cfg
986    }
987}
988
989#[cfg(esp_idf_soc_ieee802154_supported)]
990impl<'d> ThreadDriver<'d, RCP> {
991    /// Create a new Thread RCP driver instance utilizing an SPI connection
992    /// to another MCU running the Thread Host stack.
993    #[cfg(not(esp_idf_version_major = "4"))]
994    #[allow(clippy::too_many_arguments)]
995    pub fn new_rcp_spi<
996        M: crate::hal::modem::ThreadModemPeripheral + 'd,
997        S: crate::hal::spi::Spi + 'd,
998    >(
999        modem: M,
1000        spi: S,
1001        mosi: impl InputPin + 'd,
1002        miso: impl OutputPin + 'd,
1003        sclk: impl InputPin + OutputPin + 'd,
1004        cs: Option<impl InputPin + OutputPin + 'd>,
1005        intr: Option<impl InputPin + OutputPin + 'd>,
1006        sysloop: EspSystemEventLoop,
1007        nvs: EspDefaultNvsPartition,
1008        mounted_event_fs: Arc<MountedEventfs>,
1009    ) -> Result<Self, EspError> {
1010        Self::internal_new(
1011            Self::rcp_spi_cfg(modem, spi, mosi, miso, sclk, cs, intr),
1012            sysloop,
1013            nvs,
1014            mounted_event_fs,
1015            RCP(()),
1016        )
1017    }
1018
1019    /// Create a new Thread RCP driver instance utilizing a UART connection
1020    /// to another MCU running the Thread Host stack.
1021    #[allow(clippy::too_many_arguments)]
1022    pub fn new_rcp_uart<M: crate::hal::modem::ThreadModemPeripheral + 'd, U: Uart + 'd>(
1023        modem: M,
1024        uart: U,
1025        tx: impl OutputPin + 'd,
1026        rx: impl InputPin + 'd,
1027        config: &crate::hal::uart::config::Config,
1028        sysloop: EspSystemEventLoop,
1029        nvs: EspDefaultNvsPartition,
1030        mounted_event_fs: Arc<MountedEventfs>,
1031    ) -> Result<Self, EspError> {
1032        Self::internal_new(
1033            Self::rcp_uart_cfg(modem, uart, tx, rx, config),
1034            sysloop,
1035            nvs,
1036            mounted_event_fs,
1037            RCP(()),
1038        )
1039    }
1040
1041    #[cfg(not(esp_idf_version_major = "4"))]
1042    #[allow(clippy::too_many_arguments)]
1043    fn rcp_spi_cfg<
1044        M: crate::hal::modem::ThreadModemPeripheral + 'd,
1045        S: crate::hal::spi::Spi + 'd,
1046    >(
1047        _modem: M,
1048        _spi: S,
1049        mosi: impl InputPin + 'd,
1050        miso: impl OutputPin + 'd,
1051        sclk: impl InputPin + OutputPin + 'd,
1052        cs: Option<impl InputPin + OutputPin + 'd>,
1053        intr: Option<impl InputPin + OutputPin + 'd>,
1054    ) -> esp_openthread_platform_config_t {
1055        let cs_pin = if let Some(cs) = cs { cs.pin() as _ } else { -1 };
1056
1057        let intr_pin = if let Some(intr) = intr {
1058            intr.pin() as _
1059        } else {
1060            -1
1061        };
1062
1063        esp_openthread_platform_config_t {
1064            radio_config: esp_openthread_radio_config_t {
1065                radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE,
1066                ..Default::default()
1067            },
1068            host_config: esp_openthread_host_connection_config_t {
1069                host_connection_mode:
1070                    esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART,
1071                __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 {
1072                    spi_slave_config: esp_openthread_spi_slave_config_t {
1073                        host_device: S::device() as _,
1074                        #[cfg(not(esp_idf_version_at_least_6_0_0))]
1075                        bus_config: spi_bus_config_t {
1076                            __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 {
1077                                mosi_io_num: mosi.pin() as _,
1078                            },
1079                            __bindgen_anon_2: spi_bus_config_t__bindgen_ty_2 {
1080                                miso_io_num: miso.pin() as _,
1081                            },
1082                            sclk_io_num: sclk.pin() as _,
1083                            ..Default::default()
1084                        },
1085                        #[cfg(esp_idf_version_at_least_6_0_0)]
1086                        bus_config: spi_bus_config_t {
1087                            __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 {
1088                                __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1__bindgen_ty_1 {
1089                                    data4_io_num: -1,
1090                                    data5_io_num: -1,
1091                                    data6_io_num: -1,
1092                                    data7_io_num: -1,
1093                                    __bindgen_anon_1:
1094                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_1 {
1095                                            mosi_io_num: mosi.pin() as _,
1096                                        },
1097                                    __bindgen_anon_2:
1098                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_2 {
1099                                            miso_io_num: miso.pin() as _,
1100                                        },
1101                                    __bindgen_anon_3:
1102                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_3 {
1103                                            quadwp_io_num: -1,
1104                                        },
1105                                    __bindgen_anon_4:
1106                                        spi_bus_config_t__bindgen_ty_1__bindgen_ty_1__bindgen_ty_4 {
1107                                            quadhd_io_num: -1,
1108                                        },
1109                                    sclk_io_num: sclk.pin() as _,
1110                                },
1111                            },
1112                            ..Default::default()
1113                        },
1114                        slave_config: spi_slave_interface_config_t {
1115                            spics_io_num: cs_pin as _,
1116                            ..Default::default()
1117                        },
1118                        intr_pin,
1119                    },
1120                },
1121            },
1122            port_config: Self::PORT_CONFIG,
1123        }
1124    }
1125
1126    fn rcp_uart_cfg<M: crate::hal::modem::ThreadModemPeripheral + 'd, U: Uart + 'd>(
1127        _modem: M,
1128        _uart: U,
1129        tx: impl OutputPin + 'd,
1130        rx: impl InputPin + 'd,
1131        config: &crate::hal::uart::config::Config,
1132    ) -> esp_openthread_platform_config_t {
1133        #[cfg(esp_idf_version_major = "4")]
1134        let cfg = esp_openthread_platform_config_t {
1135            radio_config: esp_openthread_radio_config_t {
1136                radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE,
1137                ..Default::default()
1138            },
1139            host_config: esp_openthread_host_connection_config_t {
1140                host_connection_mode:
1141                    esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART,
1142                host_uart_config: esp_openthread_uart_config_t {
1143                    port: U::port() as _,
1144                    uart_config: config.into(),
1145                    rx_pin: rx.pin() as _,
1146                    tx_pin: tx.pin() as _,
1147                },
1148            },
1149            port_config: Self::PORT_CONFIG,
1150        };
1151
1152        #[cfg(not(esp_idf_version_major = "4"))]
1153        let cfg = esp_openthread_platform_config_t {
1154            radio_config: esp_openthread_radio_config_t {
1155                radio_mode: esp_openthread_radio_mode_t_RADIO_MODE_NATIVE,
1156                ..Default::default()
1157            },
1158            host_config: esp_openthread_host_connection_config_t {
1159                host_connection_mode:
1160                    esp_openthread_host_connection_mode_t_HOST_CONNECTION_MODE_RCP_UART,
1161                __bindgen_anon_1: esp_openthread_host_connection_config_t__bindgen_ty_1 {
1162                    host_uart_config: esp_openthread_uart_config_t {
1163                        port: U::port() as _,
1164                        uart_config: config.into(),
1165                        rx_pin: rx.pin() as _,
1166                        tx_pin: tx.pin() as _,
1167                    },
1168                },
1169            },
1170            port_config: Self::PORT_CONFIG,
1171        };
1172
1173        cfg
1174    }
1175}
1176
1177impl<T> ThreadDriver<'_, T>
1178where
1179    T: Mode,
1180{
1181    const PORT_CONFIG: esp_openthread_port_config_t = esp_openthread_port_config_t {
1182        storage_partition_name: b"nvs\0" as *const _ as *const _,
1183        netif_queue_size: 10,
1184        task_queue_size: 10,
1185    };
1186
1187    /// Initialize the coexistence between the Thread stack and a Wifi/BT stack on the modem
1188    #[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))]
1189    pub fn init_coex(&mut self) -> Result<(), EspError> {
1190        let _lock = self.inner();
1191
1192        Self::internal_init_coex()
1193    }
1194
1195    /// Start the Thread driver
1196    ///
1197    /// If the driver is already started, an error is returned.
1198    pub fn start(&mut self) -> Result<(), EspError> {
1199        {
1200            let mut inner = self.inner();
1201
1202            if *inner.started.lock() {
1203                Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>())?;
1204            }
1205
1206            #[allow(clippy::manual_c_str_literals)]
1207            unsafe {
1208                crate::hal::task::create(
1209                    Self::run,
1210                    CStr::from_bytes_with_nul_unchecked(b"ThreadDriver Runner\0"),
1211                    12288,
1212                    &mut *inner as *mut _ as *mut _,
1213                    6,
1214                    None,
1215                )?;
1216            }
1217        }
1218
1219        loop {
1220            let inner = unsafe { self.inner.get().as_mut().unwrap() };
1221
1222            let started = inner.started.lock();
1223
1224            if *started {
1225                break;
1226            }
1227
1228            inner.started_condvar.wait(started);
1229        }
1230
1231        info!("ThreadDriver started");
1232
1233        Ok(())
1234    }
1235
1236    /// Stop the Thread driver
1237    ///
1238    /// If the driver is not started, an error is returned.
1239    pub fn stop(&mut self) -> Result<(), EspError> {
1240        {
1241            let inner = self.inner();
1242
1243            if !*inner.started.lock() {
1244                Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>())?;
1245            }
1246
1247            #[allow(unused_mut)]
1248            #[allow(unused_assignments)]
1249            let mut stop_supported = false;
1250
1251            #[cfg(any(
1252                esp_idf_version_at_least_5_5_0,
1253                all(esp_idf_version = "5.1", esp_idf_version_at_least_5_1_7),
1254                all(esp_idf_version = "5.2", esp_idf_version_at_least_5_2_6),
1255                all(esp_idf_version = "5.3", esp_idf_version_at_least_5_3_4),
1256                all(esp_idf_version = "5.4", esp_idf_version_at_least_5_4_3)
1257            ))]
1258            {
1259                unsafe {
1260                    esp_openthread_mainloop_exit();
1261                }
1262
1263                stop_supported = true;
1264            }
1265
1266            if !stop_supported {
1267                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`.")
1268            }
1269        }
1270
1271        loop {
1272            let inner = unsafe { self.inner.get().as_mut().unwrap() };
1273
1274            let started = inner.started.lock();
1275
1276            if !*started {
1277                break;
1278            }
1279
1280            inner.started_condvar.wait(started);
1281        }
1282
1283        info!("ThreadDriver stopped");
1284
1285        Ok(())
1286    }
1287
1288    /// Check if the Thread driver is started
1289    pub fn is_started(&self) -> Result<bool, EspError> {
1290        let inner = self.inner();
1291
1292        let started = *inner.started.lock();
1293
1294        Ok(started)
1295    }
1296
1297    /// Return a mutable reference to the inner driver state
1298    /// by locking the OpenThread lock first.
1299    #[allow(clippy::mut_from_ref)]
1300    fn inner(&self) -> ThreadDriverInnerGuard<'_> {
1301        ThreadDriverInnerGuard {
1302            inner: unsafe { &mut *self.inner.get() },
1303            _lock: OtLock::acquire().unwrap(),
1304        }
1305    }
1306
1307    // NOTE: Methods starting with `internal_` have to be called only when the OpenThread lock is held
1308
1309    #[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))]
1310    fn internal_init_coex() -> Result<(), EspError> {
1311        #[cfg(esp_idf_esp_coex_sw_coexist_enable)]
1312        {
1313            esp!(unsafe { esp_coex_wifi_i154_enable() })?;
1314        }
1315
1316        Ok(())
1317    }
1318
1319    fn internal_new(
1320        cfg: esp_openthread_platform_config_t,
1321        _sysloop: EspSystemEventLoop,
1322        nvs: EspDefaultNvsPartition,
1323        mounted_event_fs: Arc<MountedEventfs>,
1324        mode: T,
1325    ) -> Result<Self, EspError> {
1326        let mut inner = {
1327            let mut inner = Box::new_uninit();
1328
1329            unsafe {
1330                ThreadDriverInner::init(inner.as_mut_ptr(), cfg);
1331
1332                inner.assume_init()
1333            }
1334        };
1335
1336        esp!(unsafe { esp_openthread_init(&inner.cfg) })?;
1337
1338        let instance = unsafe { esp_openthread_get_instance() };
1339
1340        #[cfg(not(esp_idf_openthread_radio))]
1341        unsafe {
1342            otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL as _);
1343        }
1344
1345        let srp = &mut inner.srp;
1346
1347        unsafe {
1348            crate::sys::otSrpClientSetCallback(
1349                instance,
1350                Some(OtSrp::plat_c_srp_state_change_callback),
1351                srp as *mut _ as *mut _,
1352            )
1353        }
1354
1355        T::init();
1356
1357        info!("ThreadDriver initialized");
1358
1359        Ok(Self {
1360            inner: UnsafeCell::new(inner),
1361            _nvs: nvs,
1362            _mounted_event_fs: mounted_event_fs,
1363            _mode: mode,
1364            _p: PhantomData,
1365        })
1366    }
1367
1368    fn internal_deinit(&mut self) -> Result<(), EspError> {
1369        let _ = self.stop();
1370
1371        esp!(unsafe { esp_openthread_deinit() })?;
1372
1373        Ok(())
1374    }
1375
1376    extern "C" fn run(arg: *mut core::ffi::c_void) {
1377        {
1378            let _lock = OtLock::acquire().unwrap();
1379
1380            let inner = unsafe { (arg as *mut ThreadDriverInner).as_mut().unwrap() };
1381
1382            *inner.started.lock() = true;
1383            inner.started_condvar.notify_all();
1384        }
1385
1386        unsafe {
1387            esp_openthread_launch_mainloop();
1388        }
1389
1390        {
1391            let _lock = OtLock::acquire().unwrap();
1392
1393            let inner = unsafe { (arg as *mut ThreadDriverInner).as_mut().unwrap() };
1394
1395            *inner.started.lock() = false;
1396            inner.started_condvar.notify_all();
1397        }
1398
1399        unsafe { crate::hal::task::destroy(core::ptr::null_mut()) }
1400    }
1401}
1402
1403impl<T> Drop for ThreadDriver<'_, T>
1404where
1405    T: Mode,
1406{
1407    fn drop(&mut self) {
1408        self.internal_deinit().unwrap();
1409        info!("ThreadDriver deinitialized");
1410    }
1411}
1412
1413unsafe impl<T> Send for ThreadDriver<'_, T> where T: Mode {}
1414unsafe impl<T> Sync for ThreadDriver<'_, T> where T: Mode {}
1415
1416struct ThreadDriverInner {
1417    cfg: esp_openthread_platform_config_t,
1418    dataset_buf: otOperationalDatasetTlvs,
1419    srp: OtSrp,
1420    #[allow(clippy::type_complexity)]
1421    ipv6_cb: Option<Box<Box<dyn FnMut(Ipv6Incoming) + Send + 'static>>>,
1422    #[allow(clippy::type_complexity)]
1423    scan_cb: Option<Box<Box<dyn FnMut(Option<ActiveScanResult>) + Send + 'static>>>,
1424    #[allow(clippy::type_complexity)]
1425    energy_cb: Option<Box<Box<dyn FnMut(Option<EnergyScanResult>) + Send + 'static>>>,
1426    started: Mutex<bool>,
1427    started_condvar: Condvar,
1428}
1429
1430impl ThreadDriverInner {
1431    unsafe fn init(this: *mut Self, cfg: esp_openthread_platform_config_t) {
1432        addr_of_mut!((*this).cfg).write(cfg);
1433        addr_of_mut!((*this).dataset_buf).write_bytes(0, 1);
1434
1435        OtSrp::init(addr_of_mut!((*this).srp));
1436
1437        addr_of_mut!((*this).ipv6_cb).write(None);
1438        addr_of_mut!((*this).scan_cb).write(None);
1439        addr_of_mut!((*this).energy_cb).write(None);
1440        addr_of_mut!((*this).started).write(Mutex::new(false));
1441        addr_of_mut!((*this).started_condvar).write(Condvar::new());
1442    }
1443}
1444
1445struct ThreadDriverInnerGuard<'a> {
1446    inner: &'a mut ThreadDriverInner,
1447    _lock: OtLock,
1448}
1449
1450impl Deref for ThreadDriverInnerGuard<'_> {
1451    type Target = ThreadDriverInner;
1452
1453    fn deref(&self) -> &Self::Target {
1454        self.inner
1455    }
1456}
1457
1458impl DerefMut for ThreadDriverInnerGuard<'_> {
1459    fn deref_mut(&mut self) -> &mut Self::Target {
1460        self.inner
1461    }
1462}
1463
1464struct OtLock(PhantomData<*const ()>);
1465
1466impl OtLock {
1467    pub fn acquire() -> Result<Self, EspError> {
1468        if !unsafe { esp_openthread_lock_acquire(delay::BLOCK) } {
1469            Err(EspError::from_infallible::<ESP_ERR_TIMEOUT>())?;
1470        }
1471
1472        Ok(Self(PhantomData))
1473    }
1474}
1475
1476impl Drop for OtLock {
1477    fn drop(&mut self) {
1478        unsafe {
1479            esp_openthread_lock_release();
1480        }
1481    }
1482}
1483
1484/// Trait shared between the modes of operation of the `EspThread` instance
1485pub trait NetifMode {
1486    fn init(&mut self) -> Result<(), EspError>;
1487    fn deinit(&mut self) -> Result<(), EspError>;
1488}
1489
1490/// The regular mode of operation for the `EspThread` instance
1491///
1492/// This is the only available mode if the Border Router functionality in ESP-IDF is not enabled
1493pub struct Node(());
1494
1495impl NetifMode for Node {
1496    fn init(&mut self) -> Result<(), EspError> {
1497        Ok(())
1498    }
1499
1500    fn deinit(&mut self) -> Result<(), EspError> {
1501        Ok(())
1502    }
1503}
1504
1505/// The Border Router mode of operation for the `EspThread` instance
1506#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))]
1507pub struct BorderRouter(());
1508
1509#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))]
1510impl NetifMode for BorderRouter {
1511    fn init(&mut self) -> Result<(), EspError> {
1512        #[cfg(not(esp_idf_version_major = "4"))]
1513        {
1514            esp!(unsafe { esp_openthread_border_router_init() })?;
1515        }
1516
1517        // TODO: This is probably best left to the user to call, as it is
1518        // not strictly necessary for the border router to function
1519        // #[cfg(any(esp_idf_comp_mdns_enabled, esp_idf_comp_espressif__mdns_enabled))]
1520        // {
1521        //     esp!(unsafe { mdns_init() })?;
1522        //     esp!(unsafe { mdns_hostname_set(b"esp-ot-br\0" as *const _ as *const _) })?;
1523        // }
1524
1525        debug!("Border router initialized");
1526
1527        Ok(())
1528    }
1529
1530    fn deinit(&mut self) -> Result<(), EspError> {
1531        esp!(unsafe { esp_openthread_border_router_deinit() })?;
1532
1533        debug!("Border router deinitialized");
1534
1535        Ok(())
1536    }
1537}
1538
1539/// `EspThread` wraps a `ThreadDriver` Data Link layer instance, and binds the OSI
1540/// Layer 3 (network) facilities of ESP IDF to it.
1541///
1542/// In other words, it connects the ESP IDF Netif interface to the Thread driver.
1543/// This allows users to utilize the Rust STD APIs for working with TCP and UDP sockets.
1544///
1545/// This struct should be the default option for a Thread driver in all use cases
1546/// but the niche one where bypassing the ESP IDF Netif and lwIP stacks is
1547/// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the
1548/// ESP IDF Thread radio.
1549#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
1550pub struct EspThread<'d, T>
1551where
1552    T: NetifMode,
1553{
1554    driver: ThreadDriver<'d, Host>,
1555    netif: EspNetif,
1556    mode: T,
1557}
1558
1559#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
1560impl<'d> EspThread<'d, Node> {
1561    /// Create a new `EspThread` instance utilizing the native Thread radio on the MCU
1562    #[cfg(esp_idf_soc_ieee802154_supported)]
1563    pub fn new<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
1564        modem: M,
1565        sysloop: EspSystemEventLoop,
1566        nvs: EspDefaultNvsPartition,
1567        mounted_event_fs: Arc<MountedEventfs>,
1568    ) -> Result<Self, EspError> {
1569        Self::wrap(ThreadDriver::new(modem, sysloop, nvs, mounted_event_fs)?)
1570    }
1571
1572    /// Create a new `EspThread` instance utilizing an SPI connection to another MCU
1573    /// which is expected to run the Thread RCP driver mode over SPI
1574    #[cfg(not(esp_idf_version_major = "4"))]
1575    #[allow(clippy::too_many_arguments)]
1576    pub fn new_spi<S: crate::hal::spi::Spi + 'd>(
1577        _spi: S,
1578        mosi: impl InputPin + 'd,
1579        miso: impl OutputPin + 'd,
1580        sclk: impl InputPin + OutputPin + 'd,
1581        cs: Option<impl InputPin + OutputPin + 'd>,
1582        intr: Option<impl InputPin + OutputPin + 'd>,
1583        config: &crate::hal::spi::config::Config,
1584        _sysloop: EspSystemEventLoop,
1585        nvs: EspDefaultNvsPartition,
1586        mounted_event_fs: Arc<MountedEventfs>,
1587    ) -> Result<Self, EspError> {
1588        Self::wrap(ThreadDriver::new_spi(
1589            _spi,
1590            mosi,
1591            miso,
1592            sclk,
1593            cs,
1594            intr,
1595            config,
1596            _sysloop,
1597            nvs,
1598            mounted_event_fs,
1599        )?)
1600    }
1601
1602    /// Create a new `EspThread` instance utilizing a UART connection to another MCU
1603    /// which is expected to run the Thread RCP driver mode over UART
1604    pub fn new_uart<U: Uart + 'd>(
1605        _uart: U,
1606        tx: impl OutputPin + 'd,
1607        rx: impl InputPin + 'd,
1608        config: &crate::hal::uart::config::Config,
1609        _sysloop: EspSystemEventLoop,
1610        nvs: EspDefaultNvsPartition,
1611        mounted_event_fs: Arc<MountedEventfs>,
1612    ) -> Result<Self, EspError> {
1613        Self::wrap(ThreadDriver::new_uart(
1614            _uart,
1615            tx,
1616            rx,
1617            config,
1618            _sysloop,
1619            nvs,
1620            mounted_event_fs,
1621        )?)
1622    }
1623
1624    /// Wrap an already created Thread L2 driver instance
1625    pub fn wrap(driver: ThreadDriver<'d, Host>) -> Result<Self, EspError> {
1626        Self::wrap_all(driver, EspNetif::new(NetifStack::Thread)?)
1627    }
1628
1629    /// Wrap an already created Thread L2 driver instance and a network interface
1630    pub fn wrap_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result<Self, EspError> {
1631        Self::internal_init(driver, netif, Node(()))
1632    }
1633}
1634
1635#[cfg(all(esp_idf_comp_esp_netif_enabled, esp_idf_openthread_border_router))]
1636impl<'d> EspThread<'d, BorderRouter> {
1637    /// Set or clear the backbone network interface to be used by the Border Router instance.
1638    ///
1639    /// This method _must_ be called _before_ the Border Router is constructed
1640    /// and _after_ the Border Router is dropped.
1641    ///
1642    /// # Safety
1643    ///
1644    /// This method is unsafe, because the framework will internally store a raw pointer
1645    /// to the provided `EspNetif` instance, and use it later when the Border Router
1646    /// is initialized. If the provided `EspNetif` instance is dropped before
1647    /// the Border Router is dropped, a use-after-free will occur.
1648    ///
1649    /// Make sure that the following conditions are met:
1650    /// - The provided `EspNetif` instance outlives the Thread Border Router instance;
1651    /// - The method is called _before_ both the Thread driver (`ThreadDriver`) and the `EspThread` instances are constructed;
1652    /// - Additionally, that the driver behind the provided `EspNetif` instance is _already started_ (e.g. `EspWifi::start()` or `EspEth::start()` had been called).
1653    #[cfg(not(esp_idf_version_major = "4"))]
1654    pub unsafe fn set_backbone_netif(backbone_netif: Option<&EspNetif>) {
1655        unsafe {
1656            esp_openthread_set_backbone_netif(
1657                backbone_netif
1658                    .map(|netif| netif.handle())
1659                    .unwrap_or(core::ptr::null_mut()),
1660            );
1661        }
1662    }
1663
1664    /// Create a new `EspThread` Border Router instance utilizing the native Thread radio on the MCU
1665    #[cfg(esp_idf_soc_ieee802154_supported)]
1666    pub fn new_br<M: crate::hal::modem::ThreadModemPeripheral + 'd>(
1667        modem: M,
1668        sysloop: EspSystemEventLoop,
1669        nvs: EspDefaultNvsPartition,
1670        mounted_event_fs: Arc<MountedEventfs>,
1671    ) -> Result<Self, EspError> {
1672        Self::wrap_br(ThreadDriver::new(modem, sysloop, nvs, mounted_event_fs)?)
1673    }
1674
1675    /// Create a new `EspThread` Border Router instance utilizing an SPI connection to another MCU
1676    /// which is expected to run the Thread RCP driver mode over SPI
1677    #[cfg(not(esp_idf_version_major = "4"))]
1678    #[allow(clippy::too_many_arguments)]
1679    pub fn new_br_spi<S: crate::hal::spi::Spi + 'd>(
1680        _spi: S,
1681        mosi: impl InputPin + 'd,
1682        miso: impl OutputPin + 'd,
1683        sclk: impl InputPin + OutputPin + 'd,
1684        cs: Option<impl InputPin + OutputPin + 'd>,
1685        intr: Option<impl InputPin + OutputPin + 'd>,
1686        config: &crate::hal::spi::config::Config,
1687        _sysloop: EspSystemEventLoop,
1688        nvs: EspDefaultNvsPartition,
1689        mounted_event_fs: Arc<MountedEventfs>,
1690    ) -> Result<Self, EspError> {
1691        Self::wrap_br(ThreadDriver::new_spi(
1692            _spi,
1693            mosi,
1694            miso,
1695            sclk,
1696            cs,
1697            intr,
1698            config,
1699            _sysloop,
1700            nvs,
1701            mounted_event_fs,
1702        )?)
1703    }
1704
1705    /// Create a new `EspThread` Border Router instance utilizing a UART connection to another MCU
1706    /// which is expected to run the Thread RCP driver mode over UART
1707    #[allow(clippy::too_many_arguments)]
1708    pub fn new_br_uart<U: Uart + 'd>(
1709        _uart: U,
1710        tx: impl OutputPin + 'd,
1711        rx: impl InputPin + 'd,
1712        config: &crate::hal::uart::config::Config,
1713        _sysloop: EspSystemEventLoop,
1714        nvs: EspDefaultNvsPartition,
1715        mounted_event_fs: Arc<MountedEventfs>,
1716    ) -> Result<Self, EspError> {
1717        Self::wrap_br(ThreadDriver::new_uart(
1718            _uart,
1719            tx,
1720            rx,
1721            config,
1722            _sysloop,
1723            nvs,
1724            mounted_event_fs,
1725        )?)
1726    }
1727
1728    /// Wrap an already created Thread L2 driver instance and a backbone network interface
1729    /// to the outside world
1730    pub fn wrap_br(driver: ThreadDriver<'d, Host>) -> Result<Self, EspError> {
1731        Self::wrap_br_all(driver, EspNetif::new(NetifStack::Thread)?)
1732    }
1733
1734    /// Wrap an already created Thread L2 driver instance, a network interface to be used for the
1735    /// Thread network, and a backbone network interface to the outside world
1736    pub fn wrap_br_all(driver: ThreadDriver<'d, Host>, netif: EspNetif) -> Result<Self, EspError> {
1737        Self::internal_init(driver, netif, BorderRouter(()))
1738    }
1739}
1740
1741#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
1742impl<'d, T> EspThread<'d, T>
1743where
1744    T: NetifMode,
1745{
1746    /// Return a reference to the underlying [`ThreadDriver`]
1747    pub fn driver(&self) -> &ThreadDriver<'d, Host> {
1748        &self.driver
1749    }
1750
1751    /// Return a mutable reference to the underlying [`ThreadDriver`]
1752    pub fn driver_mut(&mut self) -> &mut ThreadDriver<'d, Host> {
1753        &mut self.driver
1754    }
1755
1756    /// Initialize the coexistence between the Thread stack and a Wifi/BT stack on the modem
1757    #[cfg(all(esp_idf_openthread_radio_native, esp_idf_soc_ieee802154_supported))]
1758    pub fn init_coex(&mut self) -> Result<(), EspError> {
1759        self.driver.init_coex()
1760    }
1761
1762    /// Return a reference to the underlying [`EspNetif`]
1763    pub fn netif(&self) -> &EspNetif {
1764        &self.netif
1765    }
1766
1767    /// Enable or disable the Thread network interface
1768    pub fn enable_ipv6(&self, enabled: bool) -> Result<(), EspError> {
1769        self.driver().enable_ipv6(enabled)
1770    }
1771
1772    /// Enable or disable Thread
1773    ///
1774    /// When enabling, this should be called after the network interface is enabled
1775    pub fn enable_thread(&self, enabled: bool) -> Result<(), EspError> {
1776        self.driver().enable_thread(enabled)
1777    }
1778
1779    /// Retrieve the current role of the device in the Thread network
1780    pub fn role(&self) -> Result<Role, EspError> {
1781        self.driver().role()
1782    }
1783
1784    /// Retrieve the active TOD (Thread Operational Dataset) in the user-supplied buffer
1785    ///
1786    /// Return the size of the TOD data written to the buffer
1787    ///
1788    /// The TOD is in Thread TLV format.
1789    pub fn tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
1790        self.driver().tod(buf)
1791    }
1792
1793    /// Retrieve the pending TOD (Thread Operational Dataset) in the user-supplied buffer
1794    ///
1795    /// Return the size of the TOD data written to the buffer
1796    ///
1797    /// The TOD is in Thread TLV format.
1798    pub fn pending_tod(&self, buf: &mut [u8]) -> Result<usize, EspError> {
1799        self.driver().pending_tod(buf)
1800    }
1801
1802    /// Set the active TOD (Thread Operational Dataset) to the provided data
1803    ///
1804    /// The TOD data should be in Thread TLV format.
1805    pub fn set_tod(&self, tod: &[u8]) -> Result<(), EspError> {
1806        self.driver().set_tod(tod)
1807    }
1808
1809    /// Set the active TOD (Thread Operational Dataset) to the provided data
1810    ///
1811    /// The TOD data should be in Thread TLV format.
1812    pub fn set_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
1813        self.driver().set_tod_hexstr(tod)
1814    }
1815
1816    /// Set the pending TOD (Thread Operational Dataset) to the provided data
1817    ///
1818    /// The TOD data should be in Thread TLV format.
1819    pub fn set_pending_tod(&self, tod: &[u8]) -> Result<(), EspError> {
1820        self.driver().set_pending_tod(tod)
1821    }
1822
1823    /// Set the pending TOD (Thread Operational Dataset) to the provided data
1824    ///
1825    /// The TOD data should be in Thread TLV format.
1826    pub fn set_pending_tod_hexstr(&self, tod: &str) -> Result<(), EspError> {
1827        self.driver().set_pending_tod_hexstr(tod)
1828    }
1829
1830    /// Set the active TOD (Thread Operational Dataset) according to the
1831    /// `CONFIG_OPENTHREAD_` TOD-related parameters compiled into the app
1832    /// during build (via `sdkconfig*`)
1833    #[cfg(not(esp_idf_version_major = "4"))]
1834    pub fn set_tod_from_cfg(&self) -> Result<(), EspError> {
1835        self.driver().set_tod_from_cfg()
1836    }
1837
1838    /// Perform an active scan for Thread networks
1839    /// The callback will be called for each found network
1840    ///
1841    /// At the end of the scan, the callback will be called with `None`
1842    pub fn scan<F: FnMut(Option<ActiveScanResult>) + Send + 'static>(
1843        &self,
1844        callback: F,
1845    ) -> Result<(), EspError> {
1846        self.driver().scan(callback)
1847    }
1848
1849    /// Check if an active scan is in progress
1850    pub fn is_scan_in_progress(&self) -> Result<bool, EspError> {
1851        self.driver().is_scan_in_progress()
1852    }
1853
1854    /// Perform an energy scan for Thread networks
1855    /// The callback will be called for each found network
1856    ///
1857    /// At the end of the scan, the callback will be called with `None`
1858    pub fn energy_scan<F: FnMut(Option<EnergyScanResult>) + Send + 'static>(
1859        &self,
1860        callback: F,
1861    ) -> Result<(), EspError> {
1862        self.driver().energy_scan(callback)
1863    }
1864
1865    /// Check if an energy scan is in progress
1866    pub fn is_energy_scan_in_progress(&self) -> Result<bool, EspError> {
1867        self.driver().is_energy_scan_in_progress()
1868    }
1869
1870    /// Start the Thread driver
1871    ///
1872    /// If the driver is already started, an error is returned.
1873    pub fn start(&mut self) -> Result<(), EspError> {
1874        self.driver_mut().start()
1875    }
1876
1877    /// Stop the Thread driver
1878    ///
1879    /// If the driver is not started, an error is returned.
1880    pub fn stop(&mut self) -> Result<(), EspError> {
1881        self.driver_mut().start()
1882    }
1883
1884    /// Check if the Thread driver is started
1885    pub fn is_started(&self) -> Result<bool, EspError> {
1886        self.driver().is_started()
1887    }
1888
1889    // NOTE: Methods starting with `internal_` have to be called only when the OpenThread lock is held
1890
1891    fn internal_init(
1892        driver: ThreadDriver<'d, Host>,
1893        netif: EspNetif,
1894        mut mode: T,
1895    ) -> Result<Self, EspError> {
1896        let inner = driver.inner();
1897
1898        let glue = unsafe { esp_openthread_netif_glue_init(&inner.cfg) };
1899        assert!(!glue.is_null());
1900
1901        esp!(unsafe { esp_netif_attach(netif.handle() as *mut _, glue) })?;
1902
1903        mode.init()?;
1904
1905        info!("EspThread initialized");
1906
1907        Ok(Self {
1908            netif,
1909            mode,
1910            driver,
1911        })
1912    }
1913
1914    fn internal_deinit(&mut self) -> Result<(), EspError> {
1915        let _lock = self.driver.inner();
1916
1917        self.mode.deinit()?;
1918
1919        unsafe {
1920            esp_openthread_netif_glue_deinit();
1921        }
1922
1923        Ok(())
1924    }
1925}
1926
1927#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
1928impl<T> Drop for EspThread<'_, T>
1929where
1930    T: NetifMode,
1931{
1932    fn drop(&mut self) {
1933        self.internal_deinit().unwrap();
1934        info!("EspThread deinitialized");
1935    }
1936}
1937
1938#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
1939unsafe impl<T> Send for EspThread<'_, T> where T: NetifMode {}
1940#[cfg(all(esp_idf_comp_esp_netif_enabled, not(esp_idf_openthread_radio)))]
1941unsafe impl<T> Sync for EspThread<'_, T> where T: NetifMode {}
1942
1943/// Events reported by the Thread stack on the system event loop
1944#[derive(Copy, Clone, Debug)]
1945pub enum ThreadEvent {
1946    /// Thread stack started
1947    Started,
1948    /// Thread stack stopped
1949    Stopped,
1950    /// Thread stack detached
1951    #[cfg(not(esp_idf_version_major = "4"))]
1952    Detached,
1953    /// Thread stack attached
1954    #[cfg(not(esp_idf_version_major = "4"))]
1955    Attached,
1956    /// Thread role changed
1957    #[cfg(not(esp_idf_version_major = "4"))]
1958    RoleChanged {
1959        current_role: Role,
1960        previous_role: Role,
1961    },
1962    /// Thread network interface up
1963    IfUp,
1964    /// Thread network interface down
1965    IfDown,
1966    /// Thread got IPv6 address
1967    GotIpv6,
1968    /// Thread lost IPv6 address
1969    LostIpv6,
1970    /// Thread multicast group joined
1971    MulticastJoined,
1972    /// Thread multicast group left
1973    MulticastLeft,
1974    /// Thread TREL IPv6 address added
1975    #[cfg(not(esp_idf_version_major = "4"))]
1976    TrelIpv6Added,
1977    /// Thread TREL IPv6 address removed
1978    #[cfg(not(esp_idf_version_major = "4"))]
1979    TrelIpv6Removed,
1980    /// Thread TREL multicast group joined
1981    #[cfg(not(esp_idf_version_major = "4"))]
1982    TrelMulticastJoined,
1983    /// Thread DNS server set
1984    // Since 5.1
1985    #[cfg(all(
1986        not(esp_idf_version_major = "4"),
1987        not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))
1988    ))]
1989    DnsServerSet,
1990    /// Thread Meshcop E Publish started
1991    // Since 5.2.2
1992    #[cfg(any(
1993        not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")),
1994        all(
1995            esp_idf_version_major = "5",
1996            not(esp_idf_version_minor = "0"),
1997            not(esp_idf_version_minor = "1"),
1998            not(all(
1999                esp_idf_version_minor = "2",
2000                any(esp_idf_version_patch = "0", esp_idf_version_patch = "1")
2001            )),
2002        ),
2003    ))]
2004    MeshcopEPublishStarted,
2005    /// Thread Meshcop E Remove started
2006    // Since 5.2.2
2007    #[cfg(any(
2008        not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")),
2009        all(
2010            esp_idf_version_major = "5",
2011            not(esp_idf_version_minor = "0"),
2012            not(esp_idf_version_minor = "1"),
2013            not(all(
2014                esp_idf_version_minor = "2",
2015                any(esp_idf_version_patch = "0", esp_idf_version_patch = "1")
2016            )),
2017        ),
2018    ))]
2019    MeshcopERemoveStarted,
2020    #[cfg(any(
2021        esp_idf_version_patch_at_least_5_1_6,
2022        esp_idf_version_patch_at_least_5_2_4,
2023        esp_idf_version_patch_at_least_5_3_2,
2024        esp_idf_version_at_least_5_4_0
2025    ))]
2026    DatasetChanged,
2027
2028    /// An event ID not recognised by this version of the library was received.
2029    ///
2030    /// This variant is produced instead of panicking when an unknown event ID
2031    /// arrives, allowing applications to remain forward-compatible with
2032    /// ESP-IDF versions that introduce new Thread events.
2033    Other(i32),
2034}
2035
2036unsafe impl EspEventSource for ThreadEvent {
2037    fn source() -> Option<&'static ffi::CStr> {
2038        Some(unsafe { ffi::CStr::from_ptr(OPENTHREAD_EVENT) })
2039    }
2040}
2041
2042impl EspEventDeserializer for ThreadEvent {
2043    type Data<'d> = ThreadEvent;
2044
2045    #[allow(non_upper_case_globals, non_snake_case)]
2046    fn deserialize(data: &crate::eventloop::EspEvent) -> ThreadEvent {
2047        let event_id = data.event_id as u32;
2048
2049        match event_id {
2050            esp_openthread_event_t_OPENTHREAD_EVENT_START => ThreadEvent::Started,
2051            esp_openthread_event_t_OPENTHREAD_EVENT_STOP => ThreadEvent::Stopped,
2052            #[cfg(not(esp_idf_version_major = "4"))]
2053            esp_openthread_event_t_OPENTHREAD_EVENT_DETACHED => ThreadEvent::Detached,
2054            #[cfg(not(esp_idf_version_major = "4"))]
2055            esp_openthread_event_t_OPENTHREAD_EVENT_ATTACHED => ThreadEvent::Attached,
2056            #[cfg(not(esp_idf_version_major = "4"))]
2057            esp_openthread_event_t_OPENTHREAD_EVENT_ROLE_CHANGED => {
2058                let payload = unsafe {
2059                    (data.payload.unwrap() as *const _
2060                        as *const esp_openthread_role_changed_event_t)
2061                        .as_ref()
2062                }
2063                .unwrap();
2064
2065                ThreadEvent::RoleChanged {
2066                    current_role: payload.current_role.into(),
2067                    previous_role: payload.previous_role.into(),
2068                }
2069            }
2070            esp_openthread_event_t_OPENTHREAD_EVENT_IF_UP => ThreadEvent::IfUp,
2071            esp_openthread_event_t_OPENTHREAD_EVENT_IF_DOWN => ThreadEvent::IfDown,
2072            esp_openthread_event_t_OPENTHREAD_EVENT_GOT_IP6 => ThreadEvent::GotIpv6,
2073            esp_openthread_event_t_OPENTHREAD_EVENT_LOST_IP6 => ThreadEvent::LostIpv6,
2074            esp_openthread_event_t_OPENTHREAD_EVENT_MULTICAST_GROUP_JOIN => {
2075                ThreadEvent::MulticastJoined
2076            }
2077            esp_openthread_event_t_OPENTHREAD_EVENT_MULTICAST_GROUP_LEAVE => {
2078                ThreadEvent::MulticastLeft
2079            }
2080            #[cfg(not(esp_idf_version_major = "4"))]
2081            esp_openthread_event_t_OPENTHREAD_EVENT_TREL_ADD_IP6 => ThreadEvent::TrelIpv6Added,
2082            #[cfg(not(esp_idf_version_major = "4"))]
2083            esp_openthread_event_t_OPENTHREAD_EVENT_TREL_REMOVE_IP6 => ThreadEvent::TrelIpv6Removed,
2084            #[cfg(not(esp_idf_version_major = "4"))]
2085            esp_openthread_event_t_OPENTHREAD_EVENT_TREL_MULTICAST_GROUP_JOIN => {
2086                ThreadEvent::TrelMulticastJoined
2087            }
2088            #[cfg(all(
2089                not(esp_idf_version_major = "4"),
2090                not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))
2091            ))]
2092            esp_openthread_event_t_OPENTHREAD_EVENT_SET_DNS_SERVER => ThreadEvent::DnsServerSet,
2093            // Since 5.2.2
2094            #[cfg(any(
2095                not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")),
2096                all(
2097                    esp_idf_version_major = "5",
2098                    not(esp_idf_version_minor = "0"),
2099                    not(esp_idf_version_minor = "1"),
2100                    not(all(
2101                        esp_idf_version_minor = "2",
2102                        any(esp_idf_version_patch = "0", esp_idf_version_patch = "1")
2103                    )),
2104                ),
2105            ))]
2106            esp_openthread_event_t_OPENTHREAD_EVENT_PUBLISH_MESHCOP_E => {
2107                ThreadEvent::MeshcopEPublishStarted
2108            }
2109            // Since 5.2.2
2110            #[cfg(any(
2111                not(any(esp_idf_version_major = "4", esp_idf_version_major = "5")),
2112                all(
2113                    esp_idf_version_major = "5",
2114                    not(esp_idf_version_minor = "0"),
2115                    not(esp_idf_version_minor = "1"),
2116                    not(all(
2117                        esp_idf_version_minor = "2",
2118                        any(esp_idf_version_patch = "0", esp_idf_version_patch = "1")
2119                    )),
2120                ),
2121            ))]
2122            esp_openthread_event_t_OPENTHREAD_EVENT_REMOVE_MESHCOP_E => {
2123                ThreadEvent::MeshcopERemoveStarted
2124            }
2125            #[cfg(any(
2126                esp_idf_version_patch_at_least_5_1_6,
2127                esp_idf_version_patch_at_least_5_2_4,
2128                esp_idf_version_patch_at_least_5_3_2,
2129                esp_idf_version_at_least_5_4_0
2130            ))]
2131            esp_openthread_event_t_OPENTHREAD_EVENT_DATASET_CHANGED => ThreadEvent::DatasetChanged,
2132            _ => {
2133                warn!("ThreadEvent: unknown event ID {event_id}, ignoring");
2134                ThreadEvent::Other(event_id as i32)
2135            }
2136        }
2137    }
2138}