blob: 7873ff897787664ed9275304bd09264f77ffb6d0 [file] [log] [blame]
Glenn Moloney7fa322a2020-09-24 15:37:04 +10001/*
2 * This file is part of the MicroPython project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2017-2020 Nick Moore
7 * Copyright (c) 2018 shawwwn <shawwwn1@gmail.com>
8 * Copyright (c) 2020-2021 Glenn Moloney @glenn20
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of this software and associated documentation files (the "Software"), to deal
12 * in the Software without restriction, including without limitation the rights
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the Software is
15 * furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 */
28
29
30#include <stdio.h>
31#include <stdint.h>
32#include <string.h>
33
34#include "esp_log.h"
35#include "esp_now.h"
36#include "esp_wifi.h"
37#include "esp_wifi_types.h"
38
39#include "py/runtime.h"
40#include "py/mphal.h"
41#include "py/mperrno.h"
42#include "py/obj.h"
43#include "py/objstr.h"
44#include "py/objarray.h"
45#include "py/stream.h"
46#include "py/binary.h"
47#include "py/ringbuf.h"
48
49#include "mpconfigport.h"
Glenn Moloney9f835df2023-10-10 13:06:59 +110050
51#if MICROPY_PY_ESPNOW
52
Glenn Moloney7fa322a2020-09-24 15:37:04 +100053#include "modnetwork.h"
54#include "modespnow.h"
55
Glenn Moloney9f835df2023-10-10 13:06:59 +110056#ifndef MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +100057// Include code to track rssi of peers
Glenn Moloney9f835df2023-10-10 13:06:59 +110058#define MICROPY_PY_ESPNOW_RSSI 1
Glenn Moloney7fa322a2020-09-24 15:37:04 +100059#endif
Glenn Moloney9f835df2023-10-10 13:06:59 +110060#ifndef MICROPY_PY_ESPNOW_EXTRA_PEER_METHODS
Glenn Moloney7fa322a2020-09-24 15:37:04 +100061// Include mod_peer(),get_peer(),peer_count()
Glenn Moloney9f835df2023-10-10 13:06:59 +110062#define MICROPY_PY_ESPNOW_EXTRA_PEER_METHODS 1
Glenn Moloney7fa322a2020-09-24 15:37:04 +100063#endif
64
65// Relies on gcc Variadic Macros and Statement Expressions
66#define NEW_TUPLE(...) \
67 ({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z); })
68
69static const uint8_t ESPNOW_MAGIC = 0x99;
70
71// ESPNow packet format for the receive buffer.
72// Use this for peeking at the header of the next packet in the buffer.
73typedef struct {
74 uint8_t magic; // = ESPNOW_MAGIC
75 uint8_t msg_len; // Length of the message
Glenn Moloney9f835df2023-10-10 13:06:59 +110076 #if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +100077 uint32_t time_ms; // Timestamp (ms) when packet is received
78 int8_t rssi; // RSSI value (dBm) (-127 to 0)
Glenn Moloney9f835df2023-10-10 13:06:59 +110079 #endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +100080} __attribute__((packed)) espnow_hdr_t;
81
82typedef struct {
83 espnow_hdr_t hdr; // The header
84 uint8_t peer[6]; // Peer address
85 uint8_t msg[0]; // Message is up to 250 bytes
86} __attribute__((packed)) espnow_pkt_t;
87
88// The maximum length of an espnow packet (bytes)
89static const size_t MAX_PACKET_LEN = (
90 (sizeof(espnow_pkt_t) + ESP_NOW_MAX_DATA_LEN));
91
92// Enough for 2 full-size packets: 2 * (6 + 7 + 250) = 526 bytes
93// Will allocate an additional 7 bytes for buffer overhead
94static const size_t DEFAULT_RECV_BUFFER_SIZE = (2 * MAX_PACKET_LEN);
95
96// Default timeout (millisec) to wait for incoming ESPNow messages (5 minutes).
97static const size_t DEFAULT_RECV_TIMEOUT_MS = (5 * 60 * 1000);
98
99// Time to wait (millisec) for responses from sent packets: (2 seconds).
100static const size_t DEFAULT_SEND_TIMEOUT_MS = (2 * 1000);
101
102// Number of milliseconds to wait for pending responses to sent packets.
103// This is a fallback which should never be reached.
104static const mp_uint_t PENDING_RESPONSES_TIMEOUT_MS = 100;
105static const mp_uint_t PENDING_RESPONSES_BUSY_POLL_MS = 10;
106
107// The data structure for the espnow_singleton.
108typedef struct _esp_espnow_obj_t {
109 mp_obj_base_t base;
110
111 ringbuf_t *recv_buffer; // A buffer for received packets
112 size_t recv_buffer_size; // The size of the recv_buffer
113 mp_int_t recv_timeout_ms; // Timeout for recv()
114 volatile size_t rx_packets; // # of received packets
115 size_t dropped_rx_pkts; // # of dropped packets (buffer full)
116 size_t tx_packets; // # of sent packets
117 volatile size_t tx_responses; // # of sent packet responses received
118 volatile size_t tx_failures; // # of sent packet responses failed
119 size_t peer_count; // Cache the # of peers for send(sync=True)
120 mp_obj_t recv_cb; // Callback when a packet is received
121 mp_obj_t recv_cb_arg; // Argument passed to callback
Glenn Moloney9f835df2023-10-10 13:06:59 +1100122 #if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000123 mp_obj_t peers_table; // A dictionary of discovered peers
Glenn Moloney9f835df2023-10-10 13:06:59 +1100124 #endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000125} esp_espnow_obj_t;
126
127const mp_obj_type_t esp_espnow_type;
128
129// ### Initialisation and Config functions
130//
131
132// Return a pointer to the ESPNow module singleton
133// If state == INITIALISED check the device has been initialised.
134// Raises OSError if not initialised and state == INITIALISED.
135static esp_espnow_obj_t *_get_singleton() {
136 return MP_STATE_PORT(espnow_singleton);
137}
138
139static esp_espnow_obj_t *_get_singleton_initialised() {
140 esp_espnow_obj_t *self = _get_singleton();
141 // assert(self);
142 if (self->recv_buffer == NULL) {
143 // Throw an espnow not initialised error
144 check_esp_err(ESP_ERR_ESPNOW_NOT_INIT);
145 }
146 return self;
147}
148
149// Allocate and initialise the ESPNow module as a singleton.
150// Returns the initialised espnow_singleton.
Angus Grattondecf8e62024-02-27 15:32:29 +1100151static mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args,
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000152 size_t n_kw, const mp_obj_t *all_args) {
153
154 // The espnow_singleton must be defined in MICROPY_PORT_ROOT_POINTERS
155 // (see mpconfigport.h) to prevent memory allocated here from being
156 // garbage collected.
157 // NOTE: on soft reset the espnow_singleton MUST be set to NULL and the
158 // ESP-NOW functions de-initialised (see main.c).
159 esp_espnow_obj_t *self = MP_STATE_PORT(espnow_singleton);
160 if (self != NULL) {
161 return self;
162 }
163 self = m_new_obj(esp_espnow_obj_t);
164 self->base.type = &esp_espnow_type;
165 self->recv_buffer_size = DEFAULT_RECV_BUFFER_SIZE;
166 self->recv_timeout_ms = DEFAULT_RECV_TIMEOUT_MS;
167 self->recv_buffer = NULL; // Buffer is allocated in espnow_init()
168 self->recv_cb = mp_const_none;
Glenn Moloney9f835df2023-10-10 13:06:59 +1100169 #if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000170 self->peers_table = mp_obj_new_dict(0);
171 // Prevent user code modifying the dict
172 mp_obj_dict_get_map(self->peers_table)->is_fixed = 1;
Glenn Moloney9f835df2023-10-10 13:06:59 +1100173 #endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000174
175 // Set the global singleton pointer for the espnow protocol.
176 MP_STATE_PORT(espnow_singleton) = self;
177
178 return self;
179}
180
181// Forward declare the send and recv ESPNow callbacks
Angus Grattondecf8e62024-02-27 15:32:29 +1100182static void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000183
Angus Grattondecf8e62024-02-27 15:32:29 +1100184static void recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *msg, int msg_len);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000185
186// ESPNow.init(): Initialise the data buffers and ESP-NOW functions.
187// Initialise the Espressif ESPNOW software stack, register callbacks and
188// allocate the recv data buffers.
189// Returns None.
190static mp_obj_t espnow_init(mp_obj_t _) {
191 esp_espnow_obj_t *self = _get_singleton();
192 if (self->recv_buffer == NULL) { // Already initialised
193 self->recv_buffer = m_new_obj(ringbuf_t);
194 ringbuf_alloc(self->recv_buffer, self->recv_buffer_size);
195
196 esp_initialise_wifi(); // Call the wifi init code in network_wlan.c
197 check_esp_err(esp_now_init());
198 check_esp_err(esp_now_register_recv_cb(recv_cb));
199 check_esp_err(esp_now_register_send_cb(send_cb));
200 }
201 return mp_const_none;
202}
203
204// ESPNow.deinit(): De-initialise the ESPNOW software stack, disable callbacks
205// and deallocate the recv data buffers.
206// Note: this function is called from main.c:mp_task() to cleanup before soft
Angus Grattondecf8e62024-02-27 15:32:29 +1100207// reset, so cannot be declared static and must guard against self == NULL;.
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000208mp_obj_t espnow_deinit(mp_obj_t _) {
209 esp_espnow_obj_t *self = _get_singleton();
210 if (self != NULL && self->recv_buffer != NULL) {
211 check_esp_err(esp_now_unregister_recv_cb());
212 check_esp_err(esp_now_unregister_send_cb());
213 check_esp_err(esp_now_deinit());
214 self->recv_buffer->buf = NULL;
215 self->recv_buffer = NULL;
216 self->peer_count = 0; // esp_now_deinit() removes all peers.
217 self->tx_packets = self->tx_responses;
218 }
219 return mp_const_none;
220}
221
Angus Grattondecf8e62024-02-27 15:32:29 +1100222static mp_obj_t espnow_active(size_t n_args, const mp_obj_t *args) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000223 esp_espnow_obj_t *self = _get_singleton();
224 if (n_args > 1) {
225 if (mp_obj_is_true(args[1])) {
226 espnow_init(self);
227 } else {
228 espnow_deinit(self);
229 }
230 }
231 return self->recv_buffer != NULL ? mp_const_true : mp_const_false;
232}
Angus Grattondecf8e62024-02-27 15:32:29 +1100233static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_active_obj, 1, 2, espnow_active);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000234
235// ESPNow.config(['param'|param=value, ..])
236// Get or set configuration values. Supported config params:
237// buffer: size of buffer for rx packets (default=514 bytes)
238// timeout: Default read timeout (default=300,000 milliseconds)
Angus Grattondecf8e62024-02-27 15:32:29 +1100239static mp_obj_t espnow_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000240 esp_espnow_obj_t *self = _get_singleton();
Glenn Moloneyfd277702023-06-09 13:09:46 +1000241 enum { ARG_get, ARG_rxbuf, ARG_timeout_ms, ARG_rate };
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000242 static const mp_arg_t allowed_args[] = {
243 { MP_QSTR_, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
Glenn Moloneyfd277702023-06-09 13:09:46 +1000244 { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000245 { MP_QSTR_timeout_ms, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MIN} },
246 { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
247 };
248 mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
249 mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args,
250 MP_ARRAY_SIZE(allowed_args), allowed_args, args);
251
Glenn Moloneyfd277702023-06-09 13:09:46 +1000252 if (args[ARG_rxbuf].u_int >= 0) {
253 self->recv_buffer_size = args[ARG_rxbuf].u_int;
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000254 }
255 if (args[ARG_timeout_ms].u_int != INT_MIN) {
256 self->recv_timeout_ms = args[ARG_timeout_ms].u_int;
257 }
258 if (args[ARG_rate].u_int >= 0) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000259 esp_initialise_wifi(); // Call the wifi init code in network_wlan.c
260 check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, args[ARG_rate].u_int));
261 check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, args[ARG_rate].u_int));
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000262 }
263 if (args[ARG_get].u_obj == MP_OBJ_NULL) {
264 return mp_const_none;
265 }
266#define QS(x) (uintptr_t)MP_OBJ_NEW_QSTR(x)
267 // Return the value of the requested parameter
268 uintptr_t name = (uintptr_t)args[ARG_get].u_obj;
Glenn Moloneyfd277702023-06-09 13:09:46 +1000269 if (name == QS(MP_QSTR_rxbuf)) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000270 return mp_obj_new_int(self->recv_buffer_size);
271 } else if (name == QS(MP_QSTR_timeout_ms)) {
272 return mp_obj_new_int(self->recv_timeout_ms);
273 } else {
274 mp_raise_ValueError(MP_ERROR_TEXT("unknown config param"));
275 }
276#undef QS
277
278 return mp_const_none;
279}
Angus Grattondecf8e62024-02-27 15:32:29 +1100280static MP_DEFINE_CONST_FUN_OBJ_KW(espnow_config_obj, 1, espnow_config);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000281
282// ESPNow.irq(recv_cb)
283// Set callback function to be invoked when a message is received.
Angus Grattondecf8e62024-02-27 15:32:29 +1100284static mp_obj_t espnow_irq(size_t n_args, const mp_obj_t *args) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000285 esp_espnow_obj_t *self = _get_singleton();
286 mp_obj_t recv_cb = args[1];
287 if (recv_cb != mp_const_none && !mp_obj_is_callable(recv_cb)) {
288 mp_raise_ValueError(MP_ERROR_TEXT("invalid handler"));
289 }
290 self->recv_cb = recv_cb;
291 self->recv_cb_arg = (n_args > 2) ? args[2] : mp_const_none;
292 return mp_const_none;
293}
Angus Grattondecf8e62024-02-27 15:32:29 +1100294static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_irq_obj, 2, 3, espnow_irq);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000295
296// ESPnow.stats(): Provide some useful stats.
297// Returns a tuple of:
298// (tx_pkts, tx_responses, tx_failures, rx_pkts, dropped_rx_pkts)
Angus Grattondecf8e62024-02-27 15:32:29 +1100299static mp_obj_t espnow_stats(mp_obj_t _) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000300 const esp_espnow_obj_t *self = _get_singleton();
301 return NEW_TUPLE(
302 mp_obj_new_int(self->tx_packets),
303 mp_obj_new_int(self->tx_responses),
304 mp_obj_new_int(self->tx_failures),
305 mp_obj_new_int(self->rx_packets),
306 mp_obj_new_int(self->dropped_rx_pkts));
307}
Angus Grattondecf8e62024-02-27 15:32:29 +1100308static MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_obj, espnow_stats);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000309
Glenn Moloney9f835df2023-10-10 13:06:59 +1100310#if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000311// ### Maintaining the peer table and reading RSSI values
312//
313// We maintain a peers table for several reasons, to:
314// - support monitoring the RSSI values for all peers; and
315// - to return unique bytestrings for each peer which supports more efficient
316// application memory usage and peer handling.
317
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000318// Lookup a peer in the peers table and return a reference to the item in the
319// peers_table. Add peer to the table if it is not found (may alloc memory).
320// Will not return NULL.
321static mp_map_elem_t *_lookup_add_peer(esp_espnow_obj_t *self, const uint8_t *peer) {
322 // We do not want to allocate any new memory in the case that the peer
323 // already exists in the peers_table (which is almost all the time).
324 // So, we use a byte string on the stack and look that up in the dict.
325 mp_map_t *map = mp_obj_dict_get_map(self->peers_table);
326 mp_obj_str_t peer_obj = {{&mp_type_bytes}, 0, ESP_NOW_ETH_ALEN, peer};
327 mp_map_elem_t *item = mp_map_lookup(map, &peer_obj, MP_MAP_LOOKUP);
328 if (item == NULL) {
329 // If not found, add the peer using a new bytestring
330 map->is_fixed = 0; // Allow to modify the dict
331 mp_obj_t new_peer = mp_obj_new_bytes(peer, ESP_NOW_ETH_ALEN);
332 item = mp_map_lookup(map, new_peer, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
333 item->value = mp_obj_new_list(2, NULL);
334 map->is_fixed = 1; // Relock the dict
335 }
336 return item;
337}
338
339// Update the peers table with the new rssi value from a received pkt and
340// return a reference to the item in the peers_table.
341static mp_map_elem_t *_update_rssi(const uint8_t *peer, int8_t rssi, uint32_t time_ms) {
342 esp_espnow_obj_t *self = _get_singleton_initialised();
343 // Lookup the peer in the device table
344 mp_map_elem_t *item = _lookup_add_peer(self, peer);
345 mp_obj_list_t *list = MP_OBJ_TO_PTR(item->value);
346 list->items[0] = MP_OBJ_NEW_SMALL_INT(rssi);
347 list->items[1] = mp_obj_new_int(time_ms);
348 return item;
349}
Glenn Moloney9f835df2023-10-10 13:06:59 +1100350#endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000351
352// Return C pointer to byte memory string/bytes/bytearray in obj.
353// Raise ValueError if the length does not match expected len.
354static uint8_t *_get_bytes_len_rw(mp_obj_t obj, size_t len, mp_uint_t rw) {
355 mp_buffer_info_t bufinfo;
356 mp_get_buffer_raise(obj, &bufinfo, rw);
357 if (bufinfo.len != len) {
358 mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length"));
359 }
360 return (uint8_t *)bufinfo.buf;
361}
362
363static uint8_t *_get_bytes_len(mp_obj_t obj, size_t len) {
364 return _get_bytes_len_rw(obj, len, MP_BUFFER_READ);
365}
366
367static uint8_t *_get_bytes_len_w(mp_obj_t obj, size_t len) {
368 return _get_bytes_len_rw(obj, len, MP_BUFFER_WRITE);
369}
370
371// Return C pointer to the MAC address.
372// Raise ValueError if mac_addr is wrong type or is not 6 bytes long.
373static const uint8_t *_get_peer(mp_obj_t mac_addr) {
374 return mp_obj_is_true(mac_addr)
375 ? _get_bytes_len(mac_addr, ESP_NOW_ETH_ALEN) : NULL;
376}
377
378// Copy data from the ring buffer - wait if buffer is empty up to timeout_ms
379// 0: Success
380// -1: Not enough data available to complete read (try again later)
381// -2: Requested read is larger than buffer - will never succeed
382static int ringbuf_get_bytes_wait(ringbuf_t *r, uint8_t *data, size_t len, mp_int_t timeout_ms) {
383 mp_uint_t start = mp_hal_ticks_ms();
384 int status = 0;
385 while (((status = ringbuf_get_bytes(r, data, len)) == -1)
386 && (timeout_ms < 0 || (mp_uint_t)(mp_hal_ticks_ms() - start) < (mp_uint_t)timeout_ms)) {
387 MICROPY_EVENT_POLL_HOOK;
388 }
389 return status;
390}
391
392// ESPNow.recvinto(buffers[, timeout_ms]):
393// Waits for an espnow message and copies the peer_addr and message into
394// the buffers list.
395// Arguments:
396// buffers: (Optional) list of bytearrays to store return values.
397// timeout_ms: (Optional) timeout in milliseconds (or None).
398// Buffers should be a list: [bytearray(6), bytearray(250)]
399// If buffers is 4 elements long, the rssi and timestamp values will be
400// loaded into the 3rd and 4th elements.
401// Default timeout is set with ESPNow.config(timeout=milliseconds).
402// Return (None, None) on timeout.
Angus Grattondecf8e62024-02-27 15:32:29 +1100403static mp_obj_t espnow_recvinto(size_t n_args, const mp_obj_t *args) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000404 esp_espnow_obj_t *self = _get_singleton_initialised();
405
406 mp_int_t timeout_ms = ((n_args > 2 && args[2] != mp_const_none)
407 ? mp_obj_get_int(args[2]) : self->recv_timeout_ms);
408
409 mp_obj_list_t *list = MP_OBJ_TO_PTR(args[1]);
410 if (!mp_obj_is_type(list, &mp_type_list) || list->len < 2) {
411 mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recvinto(): Invalid argument"));
412 }
413 mp_obj_array_t *msg = MP_OBJ_TO_PTR(list->items[1]);
414 if (mp_obj_is_type(msg, &mp_type_bytearray)) {
415 msg->len += msg->free; // Make all the space in msg array available
416 msg->free = 0;
417 }
Glenn Moloney9f835df2023-10-10 13:06:59 +1100418 #if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000419 uint8_t peer_buf[ESP_NOW_ETH_ALEN];
420 #else
421 uint8_t *peer_buf = _get_bytes_len_w(list->items[0], ESP_NOW_ETH_ALEN);
Glenn Moloney9f835df2023-10-10 13:06:59 +1100422 #endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000423 uint8_t *msg_buf = _get_bytes_len_w(msg, ESP_NOW_MAX_DATA_LEN);
424
425 // Read the packet header from the incoming buffer
426 espnow_hdr_t hdr;
427 if (ringbuf_get_bytes_wait(self->recv_buffer, (uint8_t *)&hdr, sizeof(hdr), timeout_ms) < 0) {
428 return MP_OBJ_NEW_SMALL_INT(0); // Timeout waiting for packet
429 }
430 int msg_len = hdr.msg_len;
431
432 // Check the message packet header format and read the message data
433 if (hdr.magic != ESPNOW_MAGIC
434 || msg_len > ESP_NOW_MAX_DATA_LEN
435 || ringbuf_get_bytes(self->recv_buffer, peer_buf, ESP_NOW_ETH_ALEN) < 0
436 || ringbuf_get_bytes(self->recv_buffer, msg_buf, msg_len) < 0) {
437 mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recv(): buffer error"));
438 }
439 if (mp_obj_is_type(msg, &mp_type_bytearray)) {
440 // Set the length of the message bytearray.
441 size_t size = msg->len + msg->free;
442 msg->len = msg_len;
443 msg->free = size - msg_len;
444 }
445
Glenn Moloney9f835df2023-10-10 13:06:59 +1100446 #if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000447 // Update rssi value in the peer device table
448 mp_map_elem_t *entry = _update_rssi(peer_buf, hdr.rssi, hdr.time_ms);
449 list->items[0] = entry->key; // Set first element of list to peer
450 if (list->len >= 4) {
451 list->items[2] = MP_OBJ_NEW_SMALL_INT(hdr.rssi);
452 list->items[3] = mp_obj_new_int(hdr.time_ms);
453 }
Glenn Moloney9f835df2023-10-10 13:06:59 +1100454 #endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000455
456 return MP_OBJ_NEW_SMALL_INT(msg_len);
457}
Angus Grattondecf8e62024-02-27 15:32:29 +1100458static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_recvinto_obj, 2, 3, espnow_recvinto);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000459
460// Test if data is available to read from the buffers
Angus Grattondecf8e62024-02-27 15:32:29 +1100461static mp_obj_t espnow_any(const mp_obj_t _) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000462 esp_espnow_obj_t *self = _get_singleton_initialised();
463
464 return ringbuf_avail(self->recv_buffer) ? mp_const_true : mp_const_false;
465}
Angus Grattondecf8e62024-02-27 15:32:29 +1100466static MP_DEFINE_CONST_FUN_OBJ_1(espnow_any_obj, espnow_any);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000467
468// Used by espnow_send() for sends() with sync==True.
469// Wait till all pending sent packet responses have been received.
470// ie. self->tx_responses == self->tx_packets.
471static void _wait_for_pending_responses(esp_espnow_obj_t *self) {
472 mp_uint_t start = mp_hal_ticks_ms();
473 mp_uint_t t;
474 while (self->tx_responses < self->tx_packets) {
475 if ((t = mp_hal_ticks_ms() - start) > PENDING_RESPONSES_TIMEOUT_MS) {
476 mp_raise_OSError(MP_ETIMEDOUT);
477 }
478 if (t > PENDING_RESPONSES_BUSY_POLL_MS) {
479 // After 10ms of busy waiting give other tasks a look in.
480 MICROPY_EVENT_POLL_HOOK;
481 }
482 }
483}
484
485// ESPNow.send(peer_addr, message, [sync (=true), size])
486// ESPNow.send(message)
487// Send a message to the peer's mac address. Optionally wait for a response.
488// If peer_addr == None or any non-true value, send to all registered peers.
489// If sync == True, wait for response after sending.
490// If size is provided it should be the number of bytes in message to send().
491// Returns:
492// True if sync==False and message sent successfully.
493// True if sync==True and message is received successfully by all recipients
494// False if sync==True and message is not received by at least one recipient
495// Raises: EAGAIN if the internal espnow buffers are full.
Angus Grattondecf8e62024-02-27 15:32:29 +1100496static mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000497 esp_espnow_obj_t *self = _get_singleton_initialised();
498 // Check the various combinations of input arguments
499 const uint8_t *peer = (n_args > 2) ? _get_peer(args[1]) : NULL;
500 mp_obj_t msg = (n_args > 2) ? args[2] : (n_args == 2) ? args[1] : MP_OBJ_NULL;
501 bool sync = n_args <= 3 || args[3] == mp_const_none || mp_obj_is_true(args[3]);
502
503 // Get a pointer to the data buffer of the message
504 mp_buffer_info_t message;
505 mp_get_buffer_raise(msg, &message, MP_BUFFER_READ);
506
507 if (sync) {
508 // Flush out any pending responses.
509 // If the last call was sync==False there may be outstanding responses
510 // still to be received (possible many if we just had a burst of
511 // unsync send()s). We need to wait for all pending responses if this
512 // call has sync=True.
513 _wait_for_pending_responses(self);
514 }
515 int saved_failures = self->tx_failures;
516 // Send the packet - try, try again if internal esp-now buffers are full.
517 esp_err_t err;
518 mp_uint_t start = mp_hal_ticks_ms();
519 while ((ESP_ERR_ESPNOW_NO_MEM == (err = esp_now_send(peer, message.buf, message.len)))
520 && (mp_uint_t)(mp_hal_ticks_ms() - start) < (mp_uint_t)DEFAULT_SEND_TIMEOUT_MS) {
521 MICROPY_EVENT_POLL_HOOK;
522 }
523 check_esp_err(err); // Will raise OSError if e != ESP_OK
524 // Increment the sent packet count. If peer_addr==NULL msg will be
525 // sent to all peers EXCEPT any broadcast or multicast addresses.
526 self->tx_packets += ((peer == NULL) ? self->peer_count : 1);
527 if (sync) {
528 // Wait for and tally all the expected responses from peers
529 _wait_for_pending_responses(self);
530 }
531 // Return False if sync and any peers did not respond.
532 return mp_obj_new_bool(!(sync && self->tx_failures != saved_failures));
533}
Angus Grattondecf8e62024-02-27 15:32:29 +1100534static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_send_obj, 2, 4, espnow_send);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000535
536// ### The ESP_Now send and recv callback routines
537//
538
539// Callback triggered when a sent packet is acknowledged by the peer (or not).
540// Just count the number of responses and number of failures.
541// These are used in the send() logic.
Angus Grattondecf8e62024-02-27 15:32:29 +1100542static void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000543 esp_espnow_obj_t *self = _get_singleton();
544 self->tx_responses++;
545 if (status != ESP_NOW_SEND_SUCCESS) {
546 self->tx_failures++;
547 }
548}
549
550// Callback triggered when an ESP-Now packet is received.
551// Write the peer MAC address and the message into the recv_buffer as an
552// ESPNow packet.
553// If the buffer is full, drop the message and increment the dropped count.
554// Schedules the user callback if one has been registered (ESPNow.config()).
Angus Grattondecf8e62024-02-27 15:32:29 +1100555static void recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *msg, int msg_len) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000556 esp_espnow_obj_t *self = _get_singleton();
557 ringbuf_t *buf = self->recv_buffer;
558 // TODO: Test this works with ">".
559 if (sizeof(espnow_pkt_t) + msg_len >= ringbuf_free(buf)) {
560 self->dropped_rx_pkts++;
561 return;
562 }
563 espnow_hdr_t header;
564 header.magic = ESPNOW_MAGIC;
565 header.msg_len = msg_len;
Glenn Moloney9f835df2023-10-10 13:06:59 +1100566 #if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney2cc37112023-05-25 10:40:50 +1000567 header.rssi = recv_info->rx_ctrl->rssi;
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000568 header.time_ms = mp_hal_ticks_ms();
Glenn Moloney9f835df2023-10-10 13:06:59 +1100569 #endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000570
571 ringbuf_put_bytes(buf, (uint8_t *)&header, sizeof(header));
Damien Georgee4650122023-05-09 09:52:54 +1000572 ringbuf_put_bytes(buf, recv_info->src_addr, ESP_NOW_ETH_ALEN);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000573 ringbuf_put_bytes(buf, msg, msg_len);
574 self->rx_packets++;
575 if (self->recv_cb != mp_const_none) {
576 mp_sched_schedule(self->recv_cb, self->recv_cb_arg);
577 }
578}
579
580// ### Peer Management Functions
581//
582
583// Set the ESP-NOW Primary Master Key (pmk) (for encrypted communications).
584// Raise OSError if ESP-NOW functions are not initialised.
585// Raise ValueError if key is not a bytes-like object exactly 16 bytes long.
Angus Grattondecf8e62024-02-27 15:32:29 +1100586static mp_obj_t espnow_set_pmk(mp_obj_t _, mp_obj_t key) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000587 check_esp_err(esp_now_set_pmk(_get_bytes_len(key, ESP_NOW_KEY_LEN)));
588 return mp_const_none;
589}
Angus Grattondecf8e62024-02-27 15:32:29 +1100590static MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_pmk_obj, espnow_set_pmk);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000591
592// Common code for add_peer() and mod_peer() to process the args and kw_args:
593// Raise ValueError if the LMK is not a bytes-like object of exactly 16 bytes.
594// Raise TypeError if invalid keyword args or too many positional args.
595// Return true if all args parsed correctly.
Angus Grattondecf8e62024-02-27 15:32:29 +1100596static bool _update_peer_info(
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000597 esp_now_peer_info_t *peer, size_t n_args,
598 const mp_obj_t *pos_args, mp_map_t *kw_args) {
599
600 enum { ARG_lmk, ARG_channel, ARG_ifidx, ARG_encrypt };
601 static const mp_arg_t allowed_args[] = {
602 { MP_QSTR_lmk, MP_ARG_OBJ, {.u_obj = mp_const_none} },
603 { MP_QSTR_channel, MP_ARG_OBJ, {.u_obj = mp_const_none} },
604 { MP_QSTR_ifidx, MP_ARG_OBJ, {.u_obj = mp_const_none} },
605 { MP_QSTR_encrypt, MP_ARG_OBJ, {.u_obj = mp_const_none} },
606 };
607 mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
608 mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
609 if (args[ARG_lmk].u_obj != mp_const_none) {
610 mp_obj_t obj = args[ARG_lmk].u_obj;
611 peer->encrypt = mp_obj_is_true(obj);
612 if (peer->encrypt) {
613 // Key must be 16 bytes in length.
614 memcpy(peer->lmk, _get_bytes_len(obj, ESP_NOW_KEY_LEN), ESP_NOW_KEY_LEN);
615 }
616 }
617 if (args[ARG_channel].u_obj != mp_const_none) {
618 peer->channel = mp_obj_get_int(args[ARG_channel].u_obj);
619 }
620 if (args[ARG_ifidx].u_obj != mp_const_none) {
621 peer->ifidx = mp_obj_get_int(args[ARG_ifidx].u_obj);
622 }
623 if (args[ARG_encrypt].u_obj != mp_const_none) {
624 peer->encrypt = mp_obj_is_true(args[ARG_encrypt].u_obj);
625 }
626 return true;
627}
628
629// Update the cached peer count in self->peer_count;
630// The peer_count ignores broadcast and multicast addresses and is used for the
631// send() logic and is updated from add_peer(), mod_peer() and del_peer().
Angus Grattondecf8e62024-02-27 15:32:29 +1100632static void _update_peer_count() {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000633 esp_espnow_obj_t *self = _get_singleton_initialised();
634
635 esp_now_peer_info_t peer = {0};
636 bool from_head = true;
637 int count = 0;
638 // esp_now_fetch_peer() skips over any broadcast or multicast addresses
639 while (esp_now_fetch_peer(from_head, &peer) == ESP_OK) {
640 from_head = false;
641 if (++count >= ESP_NOW_MAX_TOTAL_PEER_NUM) {
642 break; // Should not happen
643 }
644 }
645 self->peer_count = count;
646}
647
648// ESPNow.add_peer(peer_mac, [lmk, [channel, [ifidx, [encrypt]]]]) or
649// ESPNow.add_peer(peer_mac, [lmk=b'0123456789abcdef'|b''|None|False],
650// [channel=1..11|0], [ifidx=0|1], [encrypt=True|False])
651// Positional args set to None will be left at defaults.
652// Raise OSError if ESPNow.init() has not been called.
653// Raise ValueError if mac or LMK are not bytes-like objects or wrong length.
654// Raise TypeError if invalid keyword args or too many positional args.
655// Return None.
Angus Grattondecf8e62024-02-27 15:32:29 +1100656static mp_obj_t espnow_add_peer(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000657 esp_now_peer_info_t peer = {0};
658 memcpy(peer.peer_addr, _get_peer(args[1]), ESP_NOW_ETH_ALEN);
659 _update_peer_info(&peer, n_args - 2, args + 2, kw_args);
660
661 check_esp_err(esp_now_add_peer(&peer));
662 _update_peer_count();
663
664 return mp_const_none;
665}
Angus Grattondecf8e62024-02-27 15:32:29 +1100666static MP_DEFINE_CONST_FUN_OBJ_KW(espnow_add_peer_obj, 2, espnow_add_peer);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000667
668// ESPNow.del_peer(peer_mac): Unregister peer_mac.
669// Raise OSError if ESPNow.init() has not been called.
670// Raise ValueError if peer is not a bytes-like objects or wrong length.
671// Return None.
Angus Grattondecf8e62024-02-27 15:32:29 +1100672static mp_obj_t espnow_del_peer(mp_obj_t _, mp_obj_t peer) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000673 uint8_t peer_addr[ESP_NOW_ETH_ALEN];
674 memcpy(peer_addr, _get_peer(peer), ESP_NOW_ETH_ALEN);
675
676 check_esp_err(esp_now_del_peer(peer_addr));
677 _update_peer_count();
678
679 return mp_const_none;
680}
Angus Grattondecf8e62024-02-27 15:32:29 +1100681static MP_DEFINE_CONST_FUN_OBJ_2(espnow_del_peer_obj, espnow_del_peer);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000682
683// Convert a peer_info struct to python tuple
684// Used by espnow_get_peer() and espnow_get_peers()
685static mp_obj_t _peer_info_to_tuple(const esp_now_peer_info_t *peer) {
686 return NEW_TUPLE(
687 mp_obj_new_bytes(peer->peer_addr, MP_ARRAY_SIZE(peer->peer_addr)),
688 mp_obj_new_bytes(peer->lmk, MP_ARRAY_SIZE(peer->lmk)),
689 mp_obj_new_int(peer->channel),
690 mp_obj_new_int(peer->ifidx),
691 (peer->encrypt) ? mp_const_true : mp_const_false);
692}
693
694// ESPNow.get_peers(): Fetch peer_info records for all registered ESPNow peers.
695// Raise OSError if ESPNow.init() has not been called.
696// Return a tuple of tuples:
697// ((peer_addr, lmk, channel, ifidx, encrypt),
698// (peer_addr, lmk, channel, ifidx, encrypt), ...)
Angus Grattondecf8e62024-02-27 15:32:29 +1100699static mp_obj_t espnow_get_peers(mp_obj_t _) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000700 esp_espnow_obj_t *self = _get_singleton_initialised();
701
702 // Build and initialise the peer info tuple.
703 mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peer_count, NULL);
704 esp_now_peer_info_t peer = {0};
705 for (int i = 0; i < peerinfo_tuple->len; i++) {
706 int status = esp_now_fetch_peer((i == 0), &peer);
707 peerinfo_tuple->items[i] =
708 (status == ESP_OK ? _peer_info_to_tuple(&peer) : mp_const_none);
709 }
710
711 return peerinfo_tuple;
712}
Angus Grattondecf8e62024-02-27 15:32:29 +1100713static MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000714
Glenn Moloney9f835df2023-10-10 13:06:59 +1100715#if MICROPY_PY_ESPNOW_EXTRA_PEER_METHODS
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000716// ESPNow.get_peer(peer_mac): Get the peer info for peer_mac as a tuple.
717// Raise OSError if ESPNow.init() has not been called.
718// Raise ValueError if mac or LMK are not bytes-like objects or wrong length.
719// Return a tuple of (peer_addr, lmk, channel, ifidx, encrypt).
Angus Grattondecf8e62024-02-27 15:32:29 +1100720static mp_obj_t espnow_get_peer(mp_obj_t _, mp_obj_t arg1) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000721 esp_now_peer_info_t peer = {0};
722 memcpy(peer.peer_addr, _get_peer(arg1), ESP_NOW_ETH_ALEN);
723
724 check_esp_err(esp_now_get_peer(peer.peer_addr, &peer));
725
726 return _peer_info_to_tuple(&peer);
727}
Angus Grattondecf8e62024-02-27 15:32:29 +1100728static MP_DEFINE_CONST_FUN_OBJ_2(espnow_get_peer_obj, espnow_get_peer);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000729
730// ESPNow.mod_peer(peer_mac, [lmk, [channel, [ifidx, [encrypt]]]]) or
731// ESPNow.mod_peer(peer_mac, [lmk=b'0123456789abcdef'|b''|None|False],
732// [channel=1..11|0], [ifidx=0|1], [encrypt=True|False])
733// Positional args set to None will be left at current values.
734// Raise OSError if ESPNow.init() has not been called.
735// Raise ValueError if mac or LMK are not bytes-like objects or wrong length.
736// Raise TypeError if invalid keyword args or too many positional args.
737// Return None.
Angus Grattondecf8e62024-02-27 15:32:29 +1100738static mp_obj_t espnow_mod_peer(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000739 esp_now_peer_info_t peer = {0};
740 memcpy(peer.peer_addr, _get_peer(args[1]), ESP_NOW_ETH_ALEN);
741 check_esp_err(esp_now_get_peer(peer.peer_addr, &peer));
742
743 _update_peer_info(&peer, n_args - 2, args + 2, kw_args);
744
745 check_esp_err(esp_now_mod_peer(&peer));
746 _update_peer_count();
747
748 return mp_const_none;
749}
Angus Grattondecf8e62024-02-27 15:32:29 +1100750static MP_DEFINE_CONST_FUN_OBJ_KW(espnow_mod_peer_obj, 2, espnow_mod_peer);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000751
752// ESPNow.espnow_peer_count(): Get the number of registered peers.
753// Raise OSError if ESPNow.init() has not been called.
754// Return a tuple of (num_total_peers, num_encrypted_peers).
Angus Grattondecf8e62024-02-27 15:32:29 +1100755static mp_obj_t espnow_peer_count(mp_obj_t _) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000756 esp_now_peer_num_t peer_num = {0};
757 check_esp_err(esp_now_get_peer_num(&peer_num));
758
759 return NEW_TUPLE(
760 mp_obj_new_int(peer_num.total_num),
761 mp_obj_new_int(peer_num.encrypt_num));
762}
Angus Grattondecf8e62024-02-27 15:32:29 +1100763static MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_count_obj, espnow_peer_count);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000764#endif
765
Angus Grattondecf8e62024-02-27 15:32:29 +1100766static const mp_rom_map_elem_t esp_espnow_locals_dict_table[] = {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000767 { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&espnow_active_obj) },
768 { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&espnow_config_obj) },
769 { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&espnow_irq_obj) },
770 { MP_ROM_QSTR(MP_QSTR_stats), MP_ROM_PTR(&espnow_stats_obj) },
771
772 // Send and receive messages
773 { MP_ROM_QSTR(MP_QSTR_recvinto), MP_ROM_PTR(&espnow_recvinto_obj) },
774 { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) },
775 { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&espnow_any_obj) },
776
777 // Peer management functions
778 { MP_ROM_QSTR(MP_QSTR_set_pmk), MP_ROM_PTR(&espnow_set_pmk_obj) },
779 { MP_ROM_QSTR(MP_QSTR_add_peer), MP_ROM_PTR(&espnow_add_peer_obj) },
780 { MP_ROM_QSTR(MP_QSTR_del_peer), MP_ROM_PTR(&espnow_del_peer_obj) },
781 { MP_ROM_QSTR(MP_QSTR_get_peers), MP_ROM_PTR(&espnow_get_peers_obj) },
Glenn Moloney9f835df2023-10-10 13:06:59 +1100782 #if MICROPY_PY_ESPNOW_EXTRA_PEER_METHODS
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000783 { MP_ROM_QSTR(MP_QSTR_mod_peer), MP_ROM_PTR(&espnow_mod_peer_obj) },
784 { MP_ROM_QSTR(MP_QSTR_get_peer), MP_ROM_PTR(&espnow_get_peer_obj) },
785 { MP_ROM_QSTR(MP_QSTR_peer_count), MP_ROM_PTR(&espnow_peer_count_obj) },
Glenn Moloney9f835df2023-10-10 13:06:59 +1100786 #endif // MICROPY_PY_ESPNOW_EXTRA_PEER_METHODS
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000787};
Angus Grattondecf8e62024-02-27 15:32:29 +1100788static MP_DEFINE_CONST_DICT(esp_espnow_locals_dict, esp_espnow_locals_dict_table);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000789
Angus Grattondecf8e62024-02-27 15:32:29 +1100790static const mp_rom_map_elem_t espnow_globals_dict_table[] = {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000791 { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__espnow) },
792 { MP_ROM_QSTR(MP_QSTR_ESPNowBase), MP_ROM_PTR(&esp_espnow_type) },
793 { MP_ROM_QSTR(MP_QSTR_MAX_DATA_LEN), MP_ROM_INT(ESP_NOW_MAX_DATA_LEN)},
794 { MP_ROM_QSTR(MP_QSTR_ADDR_LEN), MP_ROM_INT(ESP_NOW_ETH_ALEN)},
795 { MP_ROM_QSTR(MP_QSTR_KEY_LEN), MP_ROM_INT(ESP_NOW_KEY_LEN)},
796 { MP_ROM_QSTR(MP_QSTR_MAX_TOTAL_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_TOTAL_PEER_NUM)},
797 { MP_ROM_QSTR(MP_QSTR_MAX_ENCRYPT_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_ENCRYPT_PEER_NUM)},
798};
Angus Grattondecf8e62024-02-27 15:32:29 +1100799static MP_DEFINE_CONST_DICT(espnow_globals_dict, espnow_globals_dict_table);
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000800
801// ### Dummy Buffer Protocol support
802// ...so asyncio can poll.ipoll() on this device
803
804// Support ioctl(MP_STREAM_POLL, ) for asyncio
Angus Grattondecf8e62024-02-27 15:32:29 +1100805static mp_uint_t espnow_stream_ioctl(
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000806 mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
807 if (request != MP_STREAM_POLL) {
808 *errcode = MP_EINVAL;
809 return MP_STREAM_ERROR;
810 }
811 esp_espnow_obj_t *self = _get_singleton();
812 return (self->recv_buffer == NULL) ? 0 : // If not initialised
813 arg ^ (
814 // If no data in the buffer, unset the Read ready flag
815 ((ringbuf_avail(self->recv_buffer) == 0) ? MP_STREAM_POLL_RD : 0) |
816 // If still waiting for responses, unset the Write ready flag
817 ((self->tx_responses < self->tx_packets) ? MP_STREAM_POLL_WR : 0));
818}
819
Angus Grattondecf8e62024-02-27 15:32:29 +1100820static const mp_stream_p_t espnow_stream_p = {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000821 .ioctl = espnow_stream_ioctl,
822};
823
Glenn Moloney9f835df2023-10-10 13:06:59 +1100824#if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000825// Return reference to the dictionary of peers we have seen:
826// {peer1: (rssi, time_sec), peer2: (rssi, time_msec), ...}
827// where:
828// peerX is a byte string containing the 6-byte mac address of the peer,
829// rssi is the wifi signal strength from the last msg received
830// (in dBm from -127 to 0)
831// time_sec is the time in milliseconds since device last booted.
Angus Grattondecf8e62024-02-27 15:32:29 +1100832static void espnow_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000833 esp_espnow_obj_t *self = _get_singleton();
834 if (dest[0] != MP_OBJ_NULL) { // Only allow "Load" operation
835 return;
836 }
837 if (attr == MP_QSTR_peers_table) {
838 dest[0] = self->peers_table;
839 return;
840 }
841 dest[1] = MP_OBJ_SENTINEL; // Attribute not found
842}
Glenn Moloney9f835df2023-10-10 13:06:59 +1100843#endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000844
845MP_DEFINE_CONST_OBJ_TYPE(
846 esp_espnow_type,
847 MP_QSTR_ESPNowBase,
848 MP_TYPE_FLAG_NONE,
849 make_new, espnow_make_new,
Glenn Moloney9f835df2023-10-10 13:06:59 +1100850 #if MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000851 attr, espnow_attr,
Glenn Moloney9f835df2023-10-10 13:06:59 +1100852 #endif // MICROPY_PY_ESPNOW_RSSI
Glenn Moloney7fa322a2020-09-24 15:37:04 +1000853 protocol, &espnow_stream_p,
854 locals_dict, &esp_espnow_locals_dict
855 );
856
857const mp_obj_module_t mp_module_espnow = {
858 .base = { &mp_type_module },
859 .globals = (mp_obj_dict_t *)&espnow_globals_dict,
860};
861
862MP_REGISTER_MODULE(MP_QSTR__espnow, mp_module_espnow);
863MP_REGISTER_ROOT_POINTER(struct _esp_espnow_obj_t *espnow_singleton);
Glenn Moloney9f835df2023-10-10 13:06:59 +1100864
865#endif // MICROPY_PY_ESPNOW