Skip to main content

esp_idf_svc/
espnow.rs

1//! ESP-NOW
2//!
3//! ESP-NOW is a kind of connectionless Wi-Fi communication protocol that is
4//! defined by Espressif. In ESP-NOW, application data is encapsulated in a
5//! vendor-specific action frame and then transmitted from one Wi-Fi device to
6//! another without connection. CTR with CBC-MAC Protocol(CCMP) is used to
7//! protect the action frame for security. ESP-NOW is widely used in smart
8//! light, remote controlling, sensor, etc.
9use core::marker::PhantomData;
10
11use ::log::info;
12
13use alloc::boxed::Box;
14
15use crate::sys::*;
16
17use crate::private::mutex::Mutex;
18
19type Singleton<T> = Mutex<Option<Box<T>>>;
20
21pub const BROADCAST: [u8; 6] = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
22
23#[derive(Debug, Clone)]
24pub struct ReceiveInfo<'a> {
25    pub src_addr: &'a [u8; 6],
26    pub dst_addr: &'a [u8; 6],
27}
28
29#[allow(clippy::type_complexity)]
30static RECV_CALLBACK: Singleton<dyn FnMut(&ReceiveInfo, &[u8]) + Send + 'static> = Mutex::new(None);
31#[allow(clippy::type_complexity)]
32static SEND_CALLBACK: Singleton<dyn FnMut(&[u8], SendStatus) + Send + 'static> = Mutex::new(None);
33
34static TAKEN: Mutex<bool> = Mutex::new(false);
35
36#[derive(Debug)]
37pub enum SendStatus {
38    SUCCESS = 0,
39    FAIL,
40}
41
42impl From<u32> for SendStatus {
43    fn from(val: u32) -> Self {
44        match val {
45            0 => SendStatus::SUCCESS,
46            1 => SendStatus::FAIL,
47            _ => panic!("Wrong status code"),
48        }
49    }
50}
51
52pub type PeerInfo = esp_now_peer_info_t;
53
54pub struct EspNow<'a>(PhantomData<&'a ()>);
55
56impl EspNow<'static> {
57    pub fn take() -> Result<Self, EspError> {
58        Self::internal_take()
59    }
60}
61
62impl<'a> EspNow<'a> {
63    /// # Safety
64    ///
65    /// This method - in contrast to method `take` - allows the user to set
66    /// non-static callbacks/closures into the returned `EspNow` service. This enables users to borrow
67    /// - in the closure - variables that live on the stack - or more generally - in the same
68    ///   scope where the service is created.
69    ///
70    /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the service,
71    /// as that would immediately lead to an UB (crash).
72    /// Also note that forgetting the service might happen with `Rc` and `Arc`
73    /// when circular references are introduced: <https://github.com/rust-lang/rust/issues/24456>
74    ///
75    /// The reason is that the closure is actually sent to a hidden ESP IDF thread.
76    /// This means that if the service is forgotten, Rust is free to e.g. unwind the stack
77    /// and the closure now owned by this other thread will end up with references to variables that no longer exist.
78    ///
79    /// The destructor of the service takes care - prior to the service being dropped and e.g.
80    /// the stack being unwind - to remove the closure from the hidden thread and destroy it.
81    /// Unfortunately, when the service is forgotten, the un-subscription does not happen
82    /// and invalid references are left dangling.
83    ///
84    /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types
85    /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor).
86    pub unsafe fn take_nonstatic() -> Result<Self, EspError> {
87        Self::internal_take()
88    }
89
90    fn internal_take() -> Result<Self, EspError> {
91        let mut taken = TAKEN.lock();
92
93        if *taken {
94            return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
95        }
96
97        // disable modem sleep, otherwise messages queue up and we're not able
98        // to send any esp-now data after a few messages
99        // esp-idf bug report: https://github.com/espressif/esp-idf/issues/7496
100        esp!(unsafe { esp_wifi_set_ps(0) })?;
101
102        info!("Initializing ESP NOW");
103        esp!(unsafe { esp_now_init() })?;
104
105        *taken = true;
106
107        Ok(Self(PhantomData))
108    }
109
110    pub fn send(&self, peer_addr: [u8; 6], data: &[u8]) -> Result<(), EspError> {
111        esp!(unsafe { crate::sys::esp_now_send(peer_addr.as_ptr(), data.as_ptr(), data.len(),) })?;
112
113        Ok(())
114    }
115
116    pub fn add_peer(&self, peer_info: PeerInfo) -> Result<(), EspError> {
117        esp!(unsafe { esp_now_add_peer(&peer_info) })?;
118
119        Ok(())
120    }
121
122    pub fn del_peer(&self, peer_addr: [u8; 6]) -> Result<(), EspError> {
123        esp!(unsafe { esp_now_del_peer(&peer_addr as *const u8) })?;
124
125        Ok(())
126    }
127
128    pub fn mod_peer(&self, peer_info: PeerInfo) -> Result<(), EspError> {
129        esp!(unsafe { esp_now_mod_peer(&peer_info) })?;
130
131        Ok(())
132    }
133
134    pub fn get_peer(&self, peer_addr: [u8; 6]) -> Result<PeerInfo, EspError> {
135        let mut peer_info = PeerInfo::default();
136        esp!(unsafe {
137            esp_now_get_peer(
138                &peer_addr as *const u8,
139                &mut peer_info as *mut esp_now_peer_info_t,
140            )
141        })?;
142
143        Ok(peer_info)
144    }
145
146    pub fn peer_exists(&self, peer_addr: [u8; 6]) -> Result<bool, EspError> {
147        Ok(unsafe { esp_now_is_peer_exist(&peer_addr as *const u8) })
148    }
149
150    pub fn get_peers_number(&self) -> Result<(usize, usize), EspError> {
151        let mut num = esp_now_peer_num_t::default();
152        esp!(unsafe { esp_now_get_peer_num(&mut num as *mut esp_now_peer_num_t) })?;
153        Ok((num.total_num as usize, num.encrypt_num as usize))
154    }
155
156    pub fn fetch_peer(&self, from_head: bool) -> Result<PeerInfo, EspError> {
157        let mut peer_info = PeerInfo::default();
158        esp!(unsafe { esp_now_fetch_peer(from_head, &mut peer_info as *mut esp_now_peer_info_t) })?;
159
160        Ok(peer_info)
161    }
162
163    pub fn set_pmk(&self, pmk: &[u8]) -> Result<(), EspError> {
164        esp!(unsafe { esp_now_set_pmk(pmk.as_ptr()) })?;
165
166        Ok(())
167    }
168
169    pub fn get_version(&self) -> Result<u32, EspError> {
170        let mut version: u32 = 0;
171        esp!(unsafe { esp_now_get_version(&mut version as *mut u32) })?;
172        Ok(version)
173    }
174
175    pub fn register_recv_cb<F>(&self, callback: F) -> Result<(), EspError>
176    where
177        F: FnMut(&ReceiveInfo, &[u8]) + Send + 'a,
178    {
179        #[allow(clippy::type_complexity)]
180        let callback: Box<dyn FnMut(&ReceiveInfo, &[u8]) + Send + 'a> = Box::new(callback);
181        #[allow(clippy::type_complexity)]
182        let callback: Box<dyn FnMut(&ReceiveInfo, &[u8]) + Send + 'static> =
183            unsafe { core::mem::transmute(callback) };
184
185        *RECV_CALLBACK.lock() = Some(Box::new(callback));
186        esp!(unsafe { esp_now_register_recv_cb(Some(Self::recv_callback)) })?;
187
188        Ok(())
189    }
190
191    pub fn unregister_recv_cb(&self) -> Result<(), EspError> {
192        esp!(unsafe { esp_now_unregister_recv_cb() })?;
193        *RECV_CALLBACK.lock() = None;
194
195        Ok(())
196    }
197
198    pub fn register_send_cb<F>(&self, callback: F) -> Result<(), EspError>
199    where
200        F: FnMut(&[u8], SendStatus) + Send + 'a,
201    {
202        #[allow(clippy::type_complexity)]
203        let callback: Box<dyn FnMut(&[u8], SendStatus) + Send + 'a> = Box::new(callback);
204        #[allow(clippy::type_complexity)]
205        let callback: Box<dyn FnMut(&[u8], SendStatus) + Send + 'static> =
206            unsafe { core::mem::transmute(callback) };
207
208        *SEND_CALLBACK.lock() = Some(Box::new(callback));
209        esp!(unsafe { esp_now_register_send_cb(Some(Self::send_callback)) })?;
210
211        Ok(())
212    }
213
214    pub fn unregister_send_cb(&self) -> Result<(), EspError> {
215        esp!(unsafe { esp_now_unregister_send_cb() })?;
216        *SEND_CALLBACK.lock() = None;
217
218        Ok(())
219    }
220
221    extern "C" fn send_callback(
222        #[cfg(esp_idf_version_at_least_5_5_0)] tx_info: *const esp_now_send_info_t,
223        #[cfg(not(esp_idf_version_at_least_5_5_0))] dst_addr: *const u8,
224        status: esp_now_send_status_t,
225    ) {
226        #[cfg(esp_idf_version_at_least_5_5_0)]
227        let c_mac = unsafe { core::slice::from_raw_parts((*tx_info).des_addr, 6usize) };
228
229        #[cfg(not(esp_idf_version_at_least_5_5_0))]
230        let c_mac = unsafe { core::slice::from_raw_parts(dst_addr, 6usize) };
231
232        if let Some(ref mut callback) = *SEND_CALLBACK.lock() {
233            callback(c_mac, status.into())
234        } else {
235            panic!("EspNow callback not available");
236        }
237    }
238
239    extern "C" fn recv_callback(
240        #[cfg(esp_idf_version_major = "4")] src_addr: *const u8,
241        #[cfg(not(esp_idf_version_major = "4"))] esp_now_info: *const esp_now_recv_info_t,
242        data: *const u8,
243        data_len: core::ffi::c_int,
244    ) {
245        #[cfg(not(any(esp_idf_version_major = "4")))]
246        let src_addr = unsafe { *esp_now_info }.src_addr.cast_const();
247        #[cfg(not(any(esp_idf_version_major = "4")))]
248        let dst_addr = unsafe { *esp_now_info }.des_addr.cast_const();
249        let c_src_addr = unsafe { &*(src_addr as *const [u8; 6]) };
250        #[cfg(not(any(esp_idf_version_major = "4")))]
251        let c_dst_addr = unsafe { &*(dst_addr as *const [u8; 6]) };
252        let c_data = unsafe { core::slice::from_raw_parts(data, data_len as usize) };
253
254        if let Some(ref mut callback) = *RECV_CALLBACK.lock() {
255            callback(
256                &ReceiveInfo {
257                    src_addr: c_src_addr,
258                    #[cfg(esp_idf_version_major = "4")]
259                    dst_addr: &[0u8; 6],
260                    #[cfg(not(any(esp_idf_version_major = "4")))]
261                    dst_addr: c_dst_addr,
262                },
263                c_data,
264            )
265        } else {
266            panic!("EspNow callback not available");
267        }
268    }
269}
270
271impl Drop for EspNow<'_> {
272    fn drop(&mut self) {
273        let mut taken = TAKEN.lock();
274
275        esp!(unsafe { esp_now_deinit() }).unwrap();
276
277        let send_cb = &mut *SEND_CALLBACK.lock();
278        *send_cb = None;
279
280        let recv_cb = &mut *RECV_CALLBACK.lock();
281        *recv_cb = None;
282
283        *taken = false;
284    }
285}