From f15e84de6c0c66867c73748649b02d081aa94120 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Mon, 23 Jan 2023 09:42:39 +0530 Subject: [PATCH] update espnow module --- locale/circuitpython.pot | 28 +- .../bindings/espnow/{ESPNow.c => Now.c} | 897 ++++++++---------- .../bindings/espnow/{ESPNow.h => Now.h} | 4 +- ports/espressif/bindings/espnow/__init__.c | 85 ++ ports/espressif/bindings/espnow/__init__.h | 29 + ports/espressif/mpconfigport.h | 2 +- ports/espressif/supervisor/port.c | 2 +- 7 files changed, 535 insertions(+), 512 deletions(-) rename ports/espressif/bindings/espnow/{ESPNow.c => Now.c} (54%) rename ports/espressif/bindings/espnow/{ESPNow.h => Now.h} (95%) create mode 100644 ports/espressif/bindings/espnow/__init__.c create mode 100644 ports/espressif/bindings/espnow/__init__.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 33effc9612..d0fb9e5da4 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -918,18 +918,6 @@ msgstr "" msgid "ESP-IDF memory allocation failed" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "ESPNow.recv(): buffer error" -msgstr "" - -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "ESPNow.recvinto(): Invalid argument" -msgstr "" - -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "ESPNow: bytes or bytearray wrong length" -msgstr "" - #: ports/atmel-samd/common-hal/frequencyio/FrequencyIn.c #: ports/atmel-samd/common-hal/ps2io/Ps2.c #: ports/atmel-samd/common-hal/pulseio/PulseIn.c @@ -2442,7 +2430,7 @@ msgstr "" msgid "addresses is empty" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c +#: ports/espressif/bindings/espnow/Now.c msgid "an error occured" msgstr "" @@ -2970,10 +2958,6 @@ msgid "" "documentation for instructions." msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "espnow not inited" -msgstr "" - #: py/runtime.c msgid "exceptions must derive from BaseException" msgstr "" @@ -3335,10 +3319,6 @@ msgstr "" msgid "invalid format specifier" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "invalid handler" -msgstr "" - #: shared-bindings/wifi/Radio.c msgid "invalid hostname" msgstr "" @@ -3883,10 +3863,6 @@ msgstr "" msgid "queue overflow" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "rate option not supported" -msgstr "" - #: py/parse.c msgid "raw f-strings are not supported" msgstr "" @@ -4182,7 +4158,7 @@ msgstr "" msgid "unindent doesn't match any outer indent level" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c +#: ports/espressif/bindings/espnow/Now.c msgid "unknown config param" msgstr "" diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/Now.c similarity index 54% rename from ports/espressif/bindings/espnow/ESPNow.c rename to ports/espressif/bindings/espnow/Now.c index 33cf2342b8..935a740164 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/Now.c @@ -27,56 +27,52 @@ * THE SOFTWARE. */ -#include -#include -#include - -#include "esp_log.h" #include "esp_now.h" -#include "esp_wifi.h" -#include "esp_wifi_types.h" #include "py/runtime.h" -#include "py/mphal.h" -#include "py/mperrno.h" -#include "py/obj.h" -#include "py/objstr.h" #include "py/objarray.h" +#include "py/objproperty.h" #include "py/stream.h" -#include "py/binary.h" #include "py/ringbuf.h" -#include "mpconfigport.h" #include "mphalport.h" -#include "bindings/espnow/ESPNow.h" +#include "bindings/espnow/__init__.h" +#include "bindings/espnow/Now.h" +#include "shared-bindings/util.h" #include "shared-bindings/wifi/__init__.h" -#ifndef MICROPY_ESPNOW_RSSI -// Include code to track rssi of peers -#define MICROPY_ESPNOW_RSSI 1 -#endif -#ifndef MICROPY_ESPNOW_EXTRA_PEER_METHODS -// Include mod_peer(),get_peer(),peer_count() -#define MICROPY_ESPNOW_EXTRA_PEER_METHODS 1 -#endif - // Relies on gcc Variadic Macros and Statement Expressions -#define NEW_TUPLE(...) \ - ({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z); }) +#define NEW_TUPLE(...) ({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z);}) -static const uint8_t ESPNOW_MAGIC = 0x99; +#define ESPNOW_MAGIC 0x99 + +// The maximum length of an espnow packet (bytes) +#define MAX_PACKET_LEN (sizeof(espnow_pkt_t) + ESP_NOW_MAX_DATA_LEN) + +// Enough for 2 full-size packets: 2 * (6 + 7 + 250) = 526 bytes +// Will allocate an additional 7 bytes for buffer overhead +#define DEFAULT_RECV_BUFFER_SIZE (2 * MAX_PACKET_LEN) + +// Default timeout (millisec) to wait for incoming ESPNow messages (5 minutes). +#define DEFAULT_RECV_TIMEOUT_MS (5 * 60 * 1000) + +// Time to wait (millisec) for responses from sent packets: (2 seconds). +#define DEFAULT_SEND_TIMEOUT_MS (2 * 1000) + +// Number of milliseconds to wait for pending responses to sent packets. +// This is a fallback which should never be reached. +#define PENDING_RESPONSES_TIMEOUT_MS 100 +#define PENDING_RESPONSES_BUSY_POLL_MS 10 // ESPNow packet format for the receive buffer. // Use this for peeking at the header of the next packet in the buffer. typedef struct { uint8_t magic; // = ESPNOW_MAGIC uint8_t msg_len; // Length of the message - #if MICROPY_ESPNOW_RSSI uint32_t time_ms; // Timestamp (ms) when packet is received int8_t rssi; // RSSI value (dBm) (-127 to 0) - #endif // MICROPY_ESPNOW_RSSI } __attribute__((packed)) espnow_hdr_t; typedef struct { @@ -85,48 +81,20 @@ typedef struct { uint8_t msg[0]; // Message is up to 250 bytes } __attribute__((packed)) espnow_pkt_t; -// The maximum length of an espnow packet (bytes) -static const size_t MAX_PACKET_LEN = ( - (sizeof(espnow_pkt_t) + ESP_NOW_MAX_DATA_LEN)); - -// Enough for 2 full-size packets: 2 * (6 + 7 + 250) = 526 bytes -// Will allocate an additional 7 bytes for buffer overhead -static const size_t DEFAULT_RECV_BUFFER_SIZE = (2 * MAX_PACKET_LEN); - -// Default timeout (millisec) to wait for incoming ESPNow messages (5 minutes). -static const size_t DEFAULT_RECV_TIMEOUT_MS = (5 * 60 * 1000); - -// Time to wait (millisec) for responses from sent packets: (2 seconds). -static const size_t DEFAULT_SEND_TIMEOUT_MS = (2 * 1000); - -// Number of milliseconds to wait for pending responses to sent packets. -// This is a fallback which should never be reached. -static const mp_uint_t PENDING_RESPONSES_TIMEOUT_MS = 100; -static const mp_uint_t PENDING_RESPONSES_BUSY_POLL_MS = 10; - // The data structure for the espnow_singleton. -typedef struct _esp_espnow_obj_t { +typedef struct _espnow_obj_t { mp_obj_base_t base; - ringbuf_t *recv_buffer; // A buffer for received packets size_t recv_buffer_size; // The size of the recv_buffer size_t recv_timeout_ms; // Timeout for recv() volatile size_t rx_packets; // # of received packets - size_t dropped_rx_pkts; // # of dropped packets (buffer full) + volatile size_t rx_failures; // # of dropped packets (buffer full) size_t tx_packets; // # of sent packets volatile size_t tx_responses; // # of sent packet responses received volatile size_t tx_failures; // # of sent packet responses failed - size_t peer_count; // Cache the # of peers for send(sync=True) - #if MICROPY_ENABLE_SCHEDULER - mp_obj_t recv_cb; // Callback when a packet is received - mp_obj_t recv_cb_arg; // Argument passed to callback - #endif - #if MICROPY_ESPNOW_RSSI + size_t num_peers; // Cache the # of peers for send(sync=True) mp_obj_t peers_table; // A dictionary of discovered peers - #endif // MICROPY_ESPNOW_RSSI -} esp_espnow_obj_t; - -static const mp_obj_type_t esp_espnow_type; +} espnow_obj_t; static void check_esp_err(esp_err_t status) { if (status != ESP_OK) { @@ -134,54 +102,52 @@ static void check_esp_err(esp_err_t status) { } } -// ### Initialisation and Config functions -// +// --- Initialisation and Config functions --- // Return a pointer to the ESPNow module singleton // If state == INITIALISED check the device has been initialised. // Raises OSError if not initialised and state == INITIALISED. -static esp_espnow_obj_t *_get_singleton(void) { +static espnow_obj_t *_get_singleton(void) { return MP_STATE_PORT(espnow_singleton); } -static esp_espnow_obj_t *_get_singleton_initialised(void) { - esp_espnow_obj_t *self = _get_singleton(); - // assert(self); - if (self->recv_buffer == NULL) { - // Throw an espnow not initialised error - // check_esp_err(ESP_ERR_ESPNOW_NOT_INIT); - mp_raise_RuntimeError(translate("espnow not inited")); - } - return self; +static bool espnow_deinited(espnow_obj_t *self) { + return self->recv_buffer == NULL; } -// Allocate and initialise the ESPNow module as a singleton. -// Returns the initialised espnow_singleton. -STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, - size_t n_kw, const mp_obj_t *all_args) { +// Return a pointer to the ESPNow module singleton +// If state == INITIALISED check the device has been initialised. +// Raises OSError if not initialised and state == INITIALISED. +static void check_for_deinit(espnow_obj_t *self) { + if (espnow_deinited(self)) { + raise_deinited_error(); + } +} + +//| class Now: +//| def __init__(self) -> Now: +//| """ +//| Allocate and initialise the ESPNow module as a singleton. +//| Returns the initialised espnow_singleton. +//| """ +//| ... +//| +STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + espnow_obj_t *self = _get_singleton(); - // The espnow_singleton must be defined in MICROPY_PORT_ROOT_POINTERS - // (see mpconfigport.h) to prevent memory allocated here from being - // garbage collected. - // NOTE: on soft reset the espnow_singleton MUST be set to NULL and the - // ESP-NOW functions de-initialised (see main.c). - esp_espnow_obj_t *self = MP_STATE_PORT(espnow_singleton); if (self != NULL) { return self; } - self = m_new_obj(esp_espnow_obj_t); - self->base.type = &esp_espnow_type; + + self = m_new_obj(espnow_obj_t); + self->base.type = &espnow_type; self->recv_buffer_size = DEFAULT_RECV_BUFFER_SIZE; self->recv_timeout_ms = DEFAULT_RECV_TIMEOUT_MS; - self->recv_buffer = NULL; // Buffer is allocated in espnow_init() - #if MICROPY_ENABLE_SCHEDULER - self->recv_cb = mp_const_none; - #endif - #if MICROPY_ESPNOW_RSSI + self->recv_buffer = NULL; self->peers_table = mp_obj_new_dict(0); + // Prevent user code modifying the dict mp_obj_dict_get_map(self->peers_table)->is_fixed = 1; - #endif // MICROPY_ESPNOW_RSSI // Set the global singleton pointer for the espnow protocol. MP_STATE_PORT(espnow_singleton) = self; @@ -189,10 +155,47 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, return self; } -// Forward declare the send and recv ESPNow callbacks -STATIC void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status); +// --- The ESP-NOW send and recv callback routines --- -STATIC void recv_cb(const uint8_t *mac_addr, const uint8_t *data, int len); +// Callback triggered when a sent packet is acknowledged by the peer (or not). +// Just count the number of responses and number of failures. +// These are used in the send() logic. +static void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) { + espnow_obj_t *self = _get_singleton(); + self->tx_responses++; + if (status != ESP_NOW_SEND_SUCCESS) { + self->tx_failures++; + } +} + +static inline int8_t _get_rssi_from_wifi_pkt(const uint8_t *msg); + +// Callback triggered when an ESP-Now packet is received. +// Write the peer MAC address and the message into the recv_buffer as an ESPNow packet. +// If the buffer is full, drop the message and increment the dropped count. +// Schedules the user callback if one has been registered (ESPNow.config()). +static void recv_cb(const uint8_t *mac_addr, const uint8_t *msg, int msg_len) { + espnow_obj_t *self = _get_singleton(); + ringbuf_t *buf = self->recv_buffer; + + // TODO: Test this works with ">". + if (sizeof(espnow_pkt_t) + msg_len >= ringbuf_free(buf)) { + self->rx_failures++; + return; + } + + espnow_hdr_t header; + header.magic = ESPNOW_MAGIC; + header.msg_len = msg_len; + header.rssi = _get_rssi_from_wifi_pkt(msg); + header.time_ms = mp_hal_ticks_ms(); + + ringbuf_write(buf, &header, sizeof(header)); + ringbuf_write(buf, mac_addr, ESP_NOW_ETH_ALEN); + ringbuf_write(buf, msg, msg_len); + + self->rx_packets++; +} static void _wifi_init(void) { if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { @@ -201,13 +204,16 @@ static void _wifi_init(void) { } } -// ESPNow.init(): Initialise the data buffers and ESP-NOW functions. -// Initialise the Espressif ESPNOW software stack, register callbacks and -// allocate the recv data buffers. -// Returns None. -static void espnow_init(void) { - esp_espnow_obj_t *self = _get_singleton(); - if (self->recv_buffer == NULL) { // Already initialised +//| def init() -> None: +//| """ +//| Initialise the data buffers and ESP-NOW functions. +//| Initialise the Espressif ESP-NOW software stack, register callbacks +//| and allocate the recv data buffers. +//| """ +//| ... +//| +static void espnow_init(espnow_obj_t *self) { + if (espnow_deinited(self)) { // Already initialised self->recv_buffer = m_new_obj(ringbuf_t); if (!ringbuf_alloc(self->recv_buffer, self->recv_buffer_size, true)) { m_malloc_fail(self->recv_buffer_size); @@ -216,54 +222,65 @@ static void espnow_init(void) { _wifi_init(); // Call the wifi init code check_esp_err(esp_now_init()); - check_esp_err(esp_now_register_recv_cb(recv_cb)); check_esp_err(esp_now_register_send_cb(send_cb)); + check_esp_err(esp_now_register_recv_cb(recv_cb)); } } -// ESPNow.deinit(): De-initialise the ESPNOW software stack, disable callbacks -// and deallocate the recv data buffers. -// Note: this function is called from main.c:mp_task() to cleanup before soft -// reset, so cannot be declared STATIC and must guard against self == NULL;. -static void espnow_deinit(void) { - esp_espnow_obj_t *self = _get_singleton(); - if (self != NULL && self->recv_buffer != NULL) { - check_esp_err(esp_now_unregister_recv_cb()); +//| def deinit() -> None: +//| """ +//| De-initialise the ESP-NOW software stack, disable callbacks +//| and deallocate the recv data buffers. +//| """ +//| ... +//| +static void espnow_deinit(espnow_obj_t *self) { + if (self != NULL && !espnow_deinited(self)) { check_esp_err(esp_now_unregister_send_cb()); + check_esp_err(esp_now_unregister_recv_cb()); check_esp_err(esp_now_deinit()); self->recv_buffer->buf = NULL; self->recv_buffer = NULL; - self->peer_count = 0; // esp_now_deinit() removes all peers. + self->num_peers = 0; // esp_now_deinit() removes all peers. self->tx_packets = self->tx_responses; } } void espnow_reset(void) { - espnow_deinit(); + espnow_deinit(_get_singleton()); MP_STATE_PORT(espnow_singleton) = NULL; } +//| def active(state: bool) -> bool: +//| """ +//| Initialise or de-initialise the ESPNow communication protocol +//| depending on the value of the flag optional argument. +//| """ +//| ... +//| STATIC mp_obj_t espnow_active(size_t n_args, const mp_obj_t *args) { - esp_espnow_obj_t *self = _get_singleton(); + espnow_obj_t *self = args[0]; if (n_args > 1) { if (mp_obj_is_true(args[1])) { - espnow_init(); + espnow_init(self); } else { - espnow_deinit(); + espnow_deinit(self); } } - return self->recv_buffer != NULL ? mp_const_true : mp_const_false; + return mp_obj_new_bool(!espnow_deinited(self)); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_active_obj, 1, 2, espnow_active); -// ESPNow.config(['param'|param=value, ..]) -// Get or set configuration values. Supported config params: -// buffer: size of buffer for rx packets (default=514 bytes) -// timeout: Default read timeout (default=300,000 milliseconds) -STATIC mp_obj_t espnow_config( - size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - - esp_espnow_obj_t *self = _get_singleton(); +//| def config(['param'|param=value, ..]) -> int: +//| """ +//| Get or set configuration values. Supported config params: +//| buffer: size of buffer for rx packets (default=514 bytes) +//| timeout: Default read timeout (default=300,000 milliseconds) +//| """ +//| ... +//| +STATIC mp_obj_t espnow_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + espnow_obj_t *self = pos_args[0]; enum { ARG_get, ARG_buffer, ARG_timeout, ARG_rate }; static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, @@ -271,30 +288,28 @@ STATIC mp_obj_t espnow_config( { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, - MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); if (args[ARG_buffer].u_int >= 0) { self->recv_buffer_size = args[ARG_buffer].u_int; } + if (args[ARG_timeout].u_int >= 0) { self->recv_timeout_ms = args[ARG_timeout].u_int; } + if (args[ARG_rate].u_int >= 0) { - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) _wifi_init(); // Call the wifi init code - check_esp_err(esp_wifi_config_espnow_rate( - ESP_IF_WIFI_STA, args[ARG_rate].u_int)); - check_esp_err(esp_wifi_config_espnow_rate( - ESP_IF_WIFI_AP, args[ARG_rate].u_int)); - #else - mp_raise_ValueError(MP_ERROR_TEXT("rate option not supported")); - #endif + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, args[ARG_rate].u_int)); + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, args[ARG_rate].u_int)); } + if (args[ARG_get].u_obj == MP_OBJ_NULL) { return mp_const_none; } + #define QS(x) (uintptr_t)MP_OBJ_NEW_QSTR(x) // Return the value of the requested parameter uintptr_t name = (uintptr_t)args[ARG_get].u_obj; @@ -311,40 +326,26 @@ STATIC mp_obj_t espnow_config( } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_config_obj, 1, espnow_config); -// ESPNow.on_recv(recv_cb) -// Set callback function to be invoked when a message is received. -STATIC mp_obj_t espnow_on_recv(size_t n_args, const mp_obj_t *args) { - #if MICROPY_ENABLE_SCHEDULER - esp_espnow_obj_t *self = _get_singleton(); - mp_obj_t recv_cb = args[1]; - if (recv_cb != mp_const_none && !mp_obj_is_callable(recv_cb)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid handler")); - } - self->recv_cb = recv_cb; - self->recv_cb_arg = (n_args > 2) ? args[2] : mp_const_none; - #else - mp_raise_NotImplementedError(NULL); - #endif - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_on_recv_obj, 2, 3, espnow_on_recv); - -// ESPnow.stats(): Provide some useful stats. -// Returns a tuple of: -// (tx_pkts, tx_responses, tx_failures, rx_pkts, dropped_rx_pkts) -STATIC mp_obj_t espnow_stats(mp_obj_t _) { - const esp_espnow_obj_t *self = _get_singleton(); +//| stats: Tuple[int, int, int, int, int] +//| """Provide some useful stats. +//| Returns a tuple of (tx_packets, tx_responses, tx_failures, rx_packets, rx_failures). +//| """ +//| +STATIC mp_obj_t espnow_get_stats(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); return NEW_TUPLE( mp_obj_new_int(self->tx_packets), mp_obj_new_int(self->tx_responses), mp_obj_new_int(self->tx_failures), mp_obj_new_int(self->rx_packets), - mp_obj_new_int(self->dropped_rx_pkts)); + mp_obj_new_int(self->rx_failures)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_obj, espnow_stats); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_stats_obj, espnow_get_stats); -#if MICROPY_ESPNOW_RSSI -// ### Maintaining the peer table and reading RSSI values +MP_PROPERTY_GETTER(espnow_stats_obj, + (mp_obj_t)&espnow_get_stats_obj); + +// --- Maintaining the peer table and reading RSSI values --- // // We maintain a peers table for several reasons, to: // - support monitoring the RSSI values for all peers; and @@ -364,19 +365,12 @@ static inline int8_t _get_rssi_from_wifi_pkt(const uint8_t *msg) { wifi_promiscuous_pkt_t *wifi_pkt = (wifi_promiscuous_pkt_t *)( msg - sizeof_espnow_frame_format - sizeof(wifi_promiscuous_pkt_t)); #pragma GCC diagnostic pop - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 2, 0) - return wifi_pkt->rx_ctrl.rssi - 100; // Offset rssi for IDF 4.0.2 - #else return wifi_pkt->rx_ctrl.rssi; - #endif } -// Lookup a peer in the peers table and return a reference to the item in the -// peers_table. Add peer to the table if it is not found (may alloc memory). -// Will not return NULL. -static mp_map_elem_t *_lookup_add_peer( - esp_espnow_obj_t *self, const uint8_t *peer) { - +// Lookup a peer in the peers table and return a reference to the item in the peers_table. +// Add peer to the table if it is not found (may alloc memory). Will not return NULL. +static mp_map_elem_t *_lookup_add_peer(espnow_obj_t *self, const uint8_t *peer) { // We do not want to allocate any new memory in the case that the peer // already exists in the peers_table (which is almost all the time). // So, we use a byte string on the stack and look that up in the dict. @@ -396,10 +390,7 @@ static mp_map_elem_t *_lookup_add_peer( // Update the peers table with the new rssi value from a received pkt and // return a reference to the item in the peers_table. -static mp_map_elem_t *_update_rssi( - const uint8_t *peer, int8_t rssi, uint32_t time_ms) { - - esp_espnow_obj_t *self = _get_singleton_initialised(); +static mp_map_elem_t *_update_rssi(espnow_obj_t *self, const uint8_t *peer, int8_t rssi, uint32_t time_ms) { // Lookup the peer in the device table mp_map_elem_t *item = _lookup_add_peer(self, peer); mp_obj_list_t *list = MP_OBJ_TO_PTR(item->value); @@ -407,23 +398,17 @@ static mp_map_elem_t *_update_rssi( list->items[1] = mp_obj_new_int(time_ms); return item; } -#endif // MICROPY_ESPNOW_RSSI -// ### Handling espnow packets in the recv buffer -// +// --- Handling espnow packets in the recv buffer --- -// ### Send and Receive ESP_Now data -// +// --- Send and Receive ESP_Now data --- // Return C pointer to byte memory string/bytes/bytearray in obj. // Raise ValueError if the length does not match expected len. static uint8_t *_get_bytes_len_rw(mp_obj_t obj, size_t len, mp_uint_t rw) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(obj, &bufinfo, rw); - if (bufinfo.len != len) { - mp_raise_ValueError( - MP_ERROR_TEXT("ESPNow: bytes or bytearray wrong length")); - } + mp_arg_validate_length(bufinfo.len, len, MP_QSTR_bytes); return (uint8_t *)bufinfo.buf; } @@ -438,8 +423,7 @@ static uint8_t *_get_bytes_len_w(mp_obj_t obj, size_t len) { // Return C pointer to the MAC address. // Raise ValueError if mac_addr is wrong type or is not 6 bytes long. static const uint8_t *_get_peer(mp_obj_t mac_addr) { - return mp_obj_is_true(mac_addr) - ? _get_bytes_len(mac_addr, ESP_NOW_ETH_ALEN) : NULL; + return mp_obj_is_true(mac_addr) ? _get_bytes_len(mac_addr, ESP_NOW_ETH_ALEN) : NULL; } // Copy data from the ring buffer - wait if buffer is empty up to timeout_ms @@ -454,88 +438,11 @@ static int ringbuf_read_wait(ringbuf_t *r, void *data, size_t len, int timeout_m return status; } -// ESPNow.recvinto(buffers[, timeout_ms]): -// Waits for an espnow message and copies the peer_addr and message into -// the buffers list. -// Arguments: -// buffers: (Optional) list of bytearrays to store return values. -// timeout_ms: (Optional) timeout in milliseconds (or None). -// Buffers should be a list: [bytearray(6), bytearray(250)] -// If buffers is 4 elements long, the rssi and timestamp values will be -// loaded into the 3rd and 4th elements. -// Default timeout is set with ESPNow.config(timeout=milliseconds). -// Return (None, None) on timeout. -STATIC mp_obj_t espnow_recvinto(size_t n_args, const mp_obj_t *args) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - - size_t timeout_ms = ((n_args > 2 && args[2] != mp_const_none) - ? (size_t)mp_obj_get_int(args[2]) : self->recv_timeout_ms); - - mp_obj_list_t *list = MP_OBJ_TO_PTR(args[1]); - if (!mp_obj_is_type(list, &mp_type_list) || list->len < 2) { - mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recvinto(): Invalid argument")); - } - mp_obj_array_t *msg = MP_OBJ_TO_PTR(list->items[1]); - if (mp_obj_is_type(msg, &mp_type_bytearray)) { - msg->len += msg->free; // Make all the space in msg array available - msg->free = 0; - } - #if MICROPY_ESPNOW_RSSI - uint8_t peer_buf[ESP_NOW_ETH_ALEN]; - #else - uint8_t *peer_buf = _get_bytes_len_w(list->items[0], ESP_NOW_ETH_ALEN); - #endif // MICROPY_ESPNOW_RSSI - uint8_t *msg_buf = _get_bytes_len_w(msg, ESP_NOW_MAX_DATA_LEN); - - // Read the packet header from the incoming buffer - espnow_hdr_t hdr; - if (ringbuf_read_wait(self->recv_buffer, &hdr, sizeof(hdr), timeout_ms) < 1) { - return MP_OBJ_NEW_SMALL_INT(0); // Timeout waiting for packet - } - int msg_len = hdr.msg_len; - - // Check the message packet header format and read the message data - if (hdr.magic != ESPNOW_MAGIC || - msg_len > ESP_NOW_MAX_DATA_LEN || - ringbuf_read(self->recv_buffer, peer_buf, ESP_NOW_ETH_ALEN) < 1 || - ringbuf_read(self->recv_buffer, msg_buf, msg_len) < 1) { - mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recv(): buffer error")); - } - if (mp_obj_is_type(msg, &mp_type_bytearray)) { - // Set the length of the message bytearray. - size_t size = msg->len + msg->free; - msg->len = msg_len; - msg->free = size - msg_len; - } - - #if MICROPY_ESPNOW_RSSI - // Update rssi value in the peer device table - mp_map_elem_t *entry = _update_rssi(peer_buf, hdr.rssi, hdr.time_ms); - list->items[0] = entry->key; // Set first element of list to peer - if (list->len >= 4) { - list->items[2] = MP_OBJ_NEW_SMALL_INT(hdr.rssi); - list->items[3] = mp_obj_new_int(hdr.time_ms); - } - #endif // MICROPY_ESPNOW_RSSI - - return MP_OBJ_NEW_SMALL_INT(msg_len); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_recvinto_obj, 2, 3, espnow_recvinto); - -// Test if data is available to read from the buffers -STATIC mp_obj_t espnow_any(const mp_obj_t _) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - - return ringbuf_avail(self->recv_buffer) ? mp_const_true : mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_any_obj, espnow_any); - // Used by espnow_send() for sends() with sync==True. // Wait till all pending sent packet responses have been received. // ie. self->tx_responses == self->tx_packets. -static void _wait_for_pending_responses(esp_espnow_obj_t *self) { - mp_uint_t start = mp_hal_ticks_ms(); - mp_uint_t t; +static void _wait_for_pending_responses(espnow_obj_t *self) { + mp_uint_t t, start = mp_hal_ticks_ms(); while (self->tx_responses < self->tx_packets) { if ((t = mp_hal_ticks_ms() - start) > PENDING_RESPONSES_TIMEOUT_MS) { mp_raise_OSError(MP_ETIMEDOUT); @@ -547,19 +454,26 @@ static void _wait_for_pending_responses(esp_espnow_obj_t *self) { } } -// ESPNow.send(peer_addr, message, [sync (=true), size]) -// ESPNow.send(message) -// Send a message to the peer's mac address. Optionally wait for a response. -// If peer_addr == None or any non-true value, send to all registered peers. -// If sync == True, wait for response after sending. -// If size is provided it should be the number of bytes in message to send(). -// Returns: -// True if sync==False and message sent successfully. -// True if sync==True and message is received successfully by all recipients -// False if sync==True and message is not received by at least one recipient -// Raises: EAGAIN if the internal espnow buffers are full. +//| def send(peer_addr: bytes = None, message: Union[bytes, str], sync: bool = True) -> bool: +//| """ +//| Send a message to the peer's mac address. Optionally wait for a response. +//| If peer_addr == None or any non-true value, send to all registered peers. +//| If sync == True, wait for response after sending. +//| If size is provided it should be the number of bytes in message to send(). +//| +//| Returns: +//| True if sync == False and message sent successfully. +//| True if sync == True and message is received successfully by all recipients +//| False if sync == True and message is not received by at least one recipient +//| +//| Raises: EAGAIN if the internal espnow buffers are full. +//| """ +//| ... +//| STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) { - esp_espnow_obj_t *self = _get_singleton_initialised(); + espnow_obj_t *self = args[0]; + check_for_deinit(self); + // Check the various combinations of input arguments const uint8_t *peer = (n_args > 2) ? _get_peer(args[1]) : NULL; mp_obj_t msg = (n_args > 2) ? args[2] : (n_args == 2) ? args[1] : MP_OBJ_NULL; @@ -577,139 +491,166 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) { // call has sync=True. _wait_for_pending_responses(self); } + size_t saved_failures = self->tx_failures; // Send the packet - try, try again if internal esp-now buffers are full. esp_err_t err; int64_t start = mp_hal_ticks_ms(); - while ((ESP_ERR_ESPNOW_NO_MEM == - (err = esp_now_send(peer, message.buf, message.len))) && + while ((ESP_ERR_ESPNOW_NO_MEM == (err = esp_now_send(peer, message.buf, message.len))) && (mp_hal_ticks_ms() - start) <= DEFAULT_SEND_TIMEOUT_MS) { RUN_BACKGROUND_TASKS; } check_esp_err(err); // Will raise OSError if e != ESP_OK + // Increment the sent packet count. If peer_addr==NULL msg will be // sent to all peers EXCEPT any broadcast or multicast addresses. - self->tx_packets += ((peer == NULL) ? self->peer_count : 1); + self->tx_packets += ((peer == NULL) ? self->num_peers : 1); if (sync) { // Wait for and tally all the expected responses from peers _wait_for_pending_responses(self); } + // Return False if sync and any peers did not respond. return mp_obj_new_bool(!(sync && self->tx_failures != saved_failures)); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_send_obj, 2, 4, espnow_send); -// ### The ESP_Now send and recv callback routines -// +//| def recv(buffers: List[bytearray(6), bytearray(250), ...], timeout: int) -> Optional[int]: +//| """ +//| Waits for an espnow message and copies the peer_addr and message into the buffers list. +//| +//| If buffers is 2 elements long, the peer_mac and message will be +//| loaded into the 1st and 2nd elements. +//| If buffers is 4 elements long, the rssi and timestamp values will be +//| loaded into the 3rd and 4th elements. +//| +//| Default timeout is set with ESPNow.config(timeout=milliseconds). +//| Returns None on timeout otherwise length of the message. +//| """ +//| ... +//| +STATIC mp_obj_t espnow_recv(size_t n_args, const mp_obj_t *args) { + espnow_obj_t *self = args[0]; + check_for_deinit(self); -// Callback triggered when a sent packet is acknowledged by the peer (or not). -// Just count the number of responses and number of failures. -// These are used in the send() logic. -STATIC void send_cb( - const uint8_t *mac_addr, esp_now_send_status_t status) { + size_t timeout_ms = ((n_args > 2 && args[2] != mp_const_none) + ? (size_t)mp_obj_get_int(args[2]) : self->recv_timeout_ms); - esp_espnow_obj_t *self = _get_singleton(); - self->tx_responses++; - if (status != ESP_NOW_SEND_SUCCESS) { - self->tx_failures++; + mp_obj_list_t *list = MP_OBJ_TO_PTR(args[1]); + if (!mp_obj_is_type(list, &mp_type_list) || list->len < 2) { + mp_arg_error_invalid(MP_QSTR_buffers); } + + mp_obj_array_t *msg = MP_OBJ_TO_PTR(list->items[1]); + if (mp_obj_is_type(msg, &mp_type_bytearray)) { + msg->len += msg->free; // Make all the space in msg array available + msg->free = 0; + } + + uint8_t peer_buf[ESP_NOW_ETH_ALEN]; + uint8_t *msg_buf = _get_bytes_len_w(msg, ESP_NOW_MAX_DATA_LEN); + + // Read the packet header from the incoming buffer + espnow_hdr_t hdr; + if (ringbuf_read_wait(self->recv_buffer, &hdr, sizeof(hdr), timeout_ms) < 1) { + return MP_OBJ_NEW_SMALL_INT(0); // Timeout waiting for packet + } + int msg_len = hdr.msg_len; + + // Check the message packet header format and read the message data + if (hdr.magic != ESPNOW_MAGIC || + msg_len > ESP_NOW_MAX_DATA_LEN || + ringbuf_read(self->recv_buffer, peer_buf, ESP_NOW_ETH_ALEN) < 1 || + ringbuf_read(self->recv_buffer, msg_buf, msg_len) < 1) { + mp_arg_error_invalid(MP_QSTR_buffer); + } + if (mp_obj_is_type(msg, &mp_type_bytearray)) { + // Set the length of the message bytearray. + size_t size = msg->len + msg->free; + msg->len = msg_len; + msg->free = size - msg_len; + } + + // Update rssi value in the peer device table + mp_map_elem_t *entry = _update_rssi(self, peer_buf, hdr.rssi, hdr.time_ms); + list->items[0] = entry->key; // Set first element of list to peer + if (list->len >= 4) { + list->items[2] = MP_OBJ_NEW_SMALL_INT(hdr.rssi); + list->items[3] = mp_obj_new_int(hdr.time_ms); + } + + return MP_OBJ_NEW_SMALL_INT(msg_len); } +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_recv_obj, 2, 3, espnow_recv); -// Callback triggered when an ESP-Now packet is received. -// Write the peer MAC address and the message into the recv_buffer as an -// ESPNow packet. -// If the buffer is full, drop the message and increment the dropped count. -// Schedules the user callback if one has been registered (ESPNow.config()). -STATIC void recv_cb( - const uint8_t *mac_addr, const uint8_t *msg, int msg_len) { - - esp_espnow_obj_t *self = _get_singleton(); - ringbuf_t *buf = self->recv_buffer; - // TODO: Test this works with ">". - if (sizeof(espnow_pkt_t) + msg_len >= ringbuf_free(buf)) { - self->dropped_rx_pkts++; - return; - } - espnow_hdr_t header; - header.magic = ESPNOW_MAGIC; - header.msg_len = msg_len; - #if MICROPY_ESPNOW_RSSI - header.rssi = _get_rssi_from_wifi_pkt(msg); - header.time_ms = mp_hal_ticks_ms(); - #endif // MICROPY_ESPNOW_RSSI - - ringbuf_write(buf, &header, sizeof(header)); - ringbuf_write(buf, mac_addr, ESP_NOW_ETH_ALEN); - ringbuf_write(buf, msg, msg_len); - self->rx_packets++; - #if MICROPY_ENABLE_SCHEDULER - if (self->recv_cb != mp_const_none) { - mp_sched_schedule(self->recv_cb, self->recv_cb_arg); - } - #endif +//| def any(self) -> bool: +//| """Test if data is available to read from the buffers. +//| """ +//| ... +//| +STATIC mp_obj_t espnow_any(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return ringbuf_avail(self->recv_buffer) ? mp_const_true : mp_const_false; } +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_any_obj, espnow_any); -// ### Peer Management Functions -// +// --- Peer Management Functions --- -// Set the ESP-NOW Primary Master Key (pmk) (for encrypted communications). -// Raise OSError if ESP-NOW functions are not initialised. -// Raise ValueError if key is not a bytes-like object exactly 16 bytes long. -STATIC mp_obj_t espnow_set_pmk(mp_obj_t _, mp_obj_t key) { +//| def set_pmk(pmk: bytes(16)) -> None: +//| """Set the ESP-NOW Primary Master Key (pmk) (for encrypted communications). +//| """ +//| ... +//| +STATIC mp_obj_t espnow_set_pmk(mp_obj_t self_in, mp_obj_t key) { check_esp_err(esp_now_set_pmk(_get_bytes_len(key, ESP_NOW_KEY_LEN))); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_pmk_obj, espnow_set_pmk); -// Common code for add_peer() and mod_peer() to process the args and kw_args: -// Raise ValueError if the LMK is not a bytes-like object of exactly 16 bytes. -// Raise TypeError if invalid keyword args or too many positional args. -// Return true if all args parsed correctly. -STATIC bool _update_peer_info( - esp_now_peer_info_t *peer, size_t n_args, - const mp_obj_t *pos_args, mp_map_t *kw_args) { - +// Common code for add_peer() and mod_peer() to process the args and kw_args. +static void _update_peer_info(esp_now_peer_info_t *peer, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_lmk, ARG_channel, ARG_ifidx, ARG_encrypt }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_lmk, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_channel, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_ifidx, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_encrypt, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_lmk, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_channel, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_ifidx, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_encrypt, MP_ARG_OBJ, {.u_obj = mp_const_none} }, }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, - MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if (args[ARG_lmk].u_obj != mp_const_none) { mp_obj_t obj = args[ARG_lmk].u_obj; peer->encrypt = mp_obj_is_true(obj); if (peer->encrypt) { // Key must be 16 bytes in length. - memcpy(peer->lmk, - _get_bytes_len(obj, ESP_NOW_KEY_LEN), - ESP_NOW_KEY_LEN); + memcpy(peer->lmk, _get_bytes_len(obj, ESP_NOW_KEY_LEN), ESP_NOW_KEY_LEN); } } + if (args[ARG_channel].u_obj != mp_const_none) { peer->channel = mp_obj_get_int(args[ARG_channel].u_obj); } + if (args[ARG_ifidx].u_obj != mp_const_none) { peer->ifidx = mp_obj_get_int(args[ARG_ifidx].u_obj); } + if (args[ARG_encrypt].u_obj != mp_const_none) { peer->encrypt = mp_obj_is_true(args[ARG_encrypt].u_obj); } - return true; } -// Update the cached peer count in self->peer_count; -// The peer_count ignores broadcast and multicast addresses and is used for the +// Update the cached peer count in self->num_peers; +// The num_peers ignores broadcast and multicast addresses and is used for the // send() logic and is updated from add_peer(), mod_peer() and del_peer(). -STATIC void _update_peer_count(void) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - +static void _update_peer_count(espnow_obj_t *self) { esp_now_peer_info_t peer = {0}; bool from_head = true; int count = 0; + // esp_now_fetch_peer() skips over any broadcast or multicast addresses while (esp_now_fetch_peer(from_head, &peer) == ESP_OK) { from_head = false; @@ -717,41 +658,75 @@ STATIC void _update_peer_count(void) { break; // Should not happen } } - self->peer_count = count; + + self->num_peers = count; } -// ESPNow.add_peer(peer_mac, [lmk, [channel, [ifidx, [encrypt]]]]) or -// ESPNow.add_peer(peer_mac, [lmk=b'0123456789abcdef'|b''|None|False], -// [channel=1..11|0], [ifidx=0|1], [encrypt=True|False]) -// Positional args set to None will be left at defaults. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if mac or LMK are not bytes-like objects or wrong length. -// Raise TypeError if invalid keyword args or too many positional args. -// Return None. -STATIC mp_obj_t espnow_add_peer( - size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { +// ESPNow.add_peer(peer_mac, [lmk=b'0123456789abcdef'|b''|None|False], [channel=1..11|0], [ifidx=0|1], [encrypt=True|False]) + +//| def add_peer(self, peer_mac: bytes(6), lmk: bytes, channel: int, ifidx: int, encrypt: bool) -> None: +//| """ +//| Positional args set to None will be left at defaults. +//| Raise ValueError if mac or LMK are not bytes-like objects or wrong length. +//| Raise TypeError if invalid keyword args or too many positional args. +//| """ +//| ... +//| +STATIC mp_obj_t espnow_add_peer(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + espnow_obj_t *self = args[0]; + check_for_deinit(self); esp_now_peer_info_t peer = {0}; memcpy(peer.peer_addr, _get_peer(args[1]), ESP_NOW_ETH_ALEN); - _update_peer_info(&peer, n_args - 2, args + 2, kw_args); + _update_peer_info(&peer, n_args - 2, args + 2, kw_args); check_esp_err(esp_now_add_peer(&peer)); - _update_peer_count(); + _update_peer_count(self); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_add_peer_obj, 2, espnow_add_peer); -// ESPNow.del_peer(peer_mac): Unregister peer_mac. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if peer is not a bytes-like objects or wrong length. -// Return None. -STATIC mp_obj_t espnow_del_peer(mp_obj_t _, mp_obj_t peer) { +// ESPNow.mod_peer(self, peer_mac, [lmk=b'0123456789abcdef'|b''|None|False], [channel=1..11|0], [ifidx=0|1], [encrypt=True|False]) + +//| def mod_peer(self, peer_mac: bytes(6), lmk: bytes, channel: int, ifidx: int, encrypt: bool) -> None: +//| """ +//| Positional args set to None will be left at current values. +//| Raise ValueError if mac or LMK are not bytes-like objects or wrong length. +//| Raise TypeError if invalid keyword args or too many positional args. +//| """ +//| ... +//| +STATIC mp_obj_t espnow_mod_peer(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + espnow_obj_t *self = args[0]; + check_for_deinit(self); + + esp_now_peer_info_t peer = {0}; + memcpy(peer.peer_addr, _get_peer(args[1]), ESP_NOW_ETH_ALEN); + check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); + + _update_peer_info(&peer, n_args - 2, args + 2, kw_args); + check_esp_err(esp_now_mod_peer(&peer)); + _update_peer_count(self); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_mod_peer_obj, 2, espnow_mod_peer); + +//| def del_peer(peer_mac: bytes(6)) -> None: +//| """Un-register peer_mac. +//| """ +//| ... +//| +STATIC mp_obj_t espnow_del_peer(mp_obj_t self_in, mp_obj_t peer) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + uint8_t peer_addr[ESP_NOW_ETH_ALEN]; memcpy(peer_addr, _get_peer(peer), ESP_NOW_ETH_ALEN); check_esp_err(esp_now_del_peer(peer_addr)); - _update_peer_count(); + _update_peer_count(self); return mp_const_none; } @@ -768,127 +743,95 @@ static mp_obj_t _peer_info_to_tuple(const esp_now_peer_info_t *peer) { (peer->encrypt) ? mp_const_true : mp_const_false); } -// ESPNow.get_peers(): Fetch peer_info records for all registered ESPNow peers. -// Raise OSError if ESPNow.init() has not been called. -// Return a tuple of tuples: -// ((peer_addr, lmk, channel, ifidx, encrypt), -// (peer_addr, lmk, channel, ifidx, encrypt), ...) -STATIC mp_obj_t espnow_get_peers(mp_obj_t _) { - esp_espnow_obj_t *self = _get_singleton_initialised(); +//| def get_peer(self, peer_mac: bytes(6)) -> Tuple[bytes, int, int, bool]: +//| """ +//| Get the peer info for peer_mac as a tuple. +//| Raise ValueError if mac or LMK are not bytes-like objects or wrong length. +//| Returns a tuple of (peer_addr, lmk, channel, ifidx, encrypt). +//| """ +//| ... +//| +STATIC mp_obj_t espnow_get_peer(mp_obj_t self_in, mp_obj_t peer_mac) { + esp_now_peer_info_t peer = {0}; + memcpy(peer.peer_addr, _get_peer(peer_mac), ESP_NOW_ETH_ALEN); + check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); + return _peer_info_to_tuple(&peer); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_get_peer_obj, espnow_get_peer); + +//| def get_peers(self) -> Tuple[Tuple[bytes, bytes, int, int, bool], ...]: +//| """ +//| Fetch peer_info records for all registered ESPNow peers. +//| Returns a tuple of tuples: ((peer_addr, lmk, channel, ifidx, encrypt), ...) +//| """ +//| ... +//| +STATIC mp_obj_t espnow_get_peers(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); // Build and initialise the peer info tuple. - mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peer_count, NULL); + mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->num_peers, NULL); esp_now_peer_info_t peer = {0}; + for (size_t i = 0; i < peerinfo_tuple->len; i++) { esp_err_t status = esp_now_fetch_peer((i == 0), &peer); - peerinfo_tuple->items[i] = - (status == ESP_OK ? _peer_info_to_tuple(&peer) : mp_const_none); + peerinfo_tuple->items[i] = (status == ESP_OK ? _peer_info_to_tuple(&peer) : mp_const_none); } return peerinfo_tuple; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers); -#if MICROPY_ESPNOW_EXTRA_PEER_METHODS -// ESPNow.get_peer(peer_mac): Get the peer info for peer_mac as a tuple. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if mac or LMK are not bytes-like objects or wrong length. -// Return a tuple of (peer_addr, lmk, channel, ifidx, encrypt). -STATIC mp_obj_t espnow_get_peer(mp_obj_t _, mp_obj_t arg1) { - esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer(arg1), ESP_NOW_ETH_ALEN); - - check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); - - return _peer_info_to_tuple(&peer); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_get_peer_obj, espnow_get_peer); - -// ESPNow.mod_peer(peer_mac, [lmk, [channel, [ifidx, [encrypt]]]]) or -// ESPNow.mod_peer(peer_mac, [lmk=b'0123456789abcdef'|b''|None|False], -// [channel=1..11|0], [ifidx=0|1], [encrypt=True|False]) -// Positional args set to None will be left at current values. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if mac or LMK are not bytes-like objects or wrong length. -// Raise TypeError if invalid keyword args or too many positional args. -// Return None. -STATIC mp_obj_t espnow_mod_peer( - size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - - esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer(args[1]), ESP_NOW_ETH_ALEN); - check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); - - _update_peer_info(&peer, n_args - 2, args + 2, kw_args); - - check_esp_err(esp_now_mod_peer(&peer)); - _update_peer_count(); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_mod_peer_obj, 2, espnow_mod_peer); - -// ESPNow.espnow_peer_count(): Get the number of registered peers. -// Raise OSError if ESPNow.init() has not been called. -// Return a tuple of (num_total_peers, num_encrypted_peers). -STATIC mp_obj_t espnow_peer_count(mp_obj_t _) { +//| num_peers: Tuple[int, int] +//| """The number of registered peers in a tuple of (num_total_peers, num_encrypted_peers). (read-only) +//| """ +//| +STATIC mp_obj_t espnow_get_num_peers(mp_obj_t self_in) { esp_now_peer_num_t peer_num = {0}; check_esp_err(esp_now_get_peer_num(&peer_num)); - return NEW_TUPLE( mp_obj_new_int(peer_num.total_num), mp_obj_new_int(peer_num.encrypt_num)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_count_obj, espnow_peer_count); -#endif +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_num_peers_obj, espnow_get_num_peers); -STATIC const mp_rom_map_elem_t esp_espnow_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&espnow_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&espnow_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_on_recv), MP_ROM_PTR(&espnow_on_recv_obj) }, - { MP_ROM_QSTR(MP_QSTR_stats), MP_ROM_PTR(&espnow_stats_obj) }, +MP_PROPERTY_GETTER(espnow_num_peers_obj, + (mp_obj_t)&espnow_get_num_peers_obj); + +STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&espnow_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&espnow_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_stats), MP_ROM_PTR(&espnow_stats_obj) }, // Send and receive messages - { MP_ROM_QSTR(MP_QSTR_recvinto), MP_ROM_PTR(&espnow_recvinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, - { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&espnow_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&espnow_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&espnow_any_obj) }, // Peer management functions - { MP_ROM_QSTR(MP_QSTR_set_pmk), MP_ROM_PTR(&espnow_set_pmk_obj) }, - { MP_ROM_QSTR(MP_QSTR_add_peer), MP_ROM_PTR(&espnow_add_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_del_peer), MP_ROM_PTR(&espnow_del_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_peers), MP_ROM_PTR(&espnow_get_peers_obj) }, - #if MICROPY_ESPNOW_EXTRA_PEER_METHODS - { MP_ROM_QSTR(MP_QSTR_mod_peer), MP_ROM_PTR(&espnow_mod_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_peer), MP_ROM_PTR(&espnow_get_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_peer_count), MP_ROM_PTR(&espnow_peer_count_obj) }, - #endif // MICROPY_ESPNOW_EXTRA_PEER_METHODS + { MP_ROM_QSTR(MP_QSTR_set_pmk), MP_ROM_PTR(&espnow_set_pmk_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_peer), MP_ROM_PTR(&espnow_add_peer_obj) }, + { MP_ROM_QSTR(MP_QSTR_mod_peer), MP_ROM_PTR(&espnow_mod_peer_obj) }, + { MP_ROM_QSTR(MP_QSTR_del_peer), MP_ROM_PTR(&espnow_del_peer_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_peer), MP_ROM_PTR(&espnow_get_peer_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_peers), MP_ROM_PTR(&espnow_get_peers_obj) }, + { MP_ROM_QSTR(MP_QSTR_num_peers), MP_ROM_PTR(&espnow_num_peers_obj) }, }; -STATIC MP_DEFINE_CONST_DICT(esp_espnow_locals_dict, esp_espnow_locals_dict_table); +STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_dict_table); -STATIC const mp_rom_map_elem_t espnow_globals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__espnow) }, - { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&esp_espnow_type) }, - { MP_ROM_QSTR(MP_QSTR_MAX_DATA_LEN), MP_ROM_INT(ESP_NOW_MAX_DATA_LEN)}, - { MP_ROM_QSTR(MP_QSTR_ETH_ALEN), MP_ROM_INT(ESP_NOW_ETH_ALEN)}, - { MP_ROM_QSTR(MP_QSTR_KEY_LEN), MP_ROM_INT(ESP_NOW_KEY_LEN)}, - { MP_ROM_QSTR(MP_QSTR_MAX_TOTAL_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_TOTAL_PEER_NUM)}, - { MP_ROM_QSTR(MP_QSTR_MAX_ENCRYPT_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_ENCRYPT_PEER_NUM)}, -}; -STATIC MP_DEFINE_CONST_DICT(espnow_globals_dict, espnow_globals_dict_table); - -// ### Dummy Buffer Protocol support +// --- Dummy Buffer Protocol support --- // ...so asyncio can poll.ipoll() on this device // Support ioctl(MP_STREAM_POLL, ) for asyncio -STATIC mp_uint_t espnow_stream_ioctl(mp_obj_t self_in, mp_uint_t request, - uintptr_t arg, int *errcode) { +STATIC mp_uint_t espnow_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { if (request != MP_STREAM_POLL) { *errcode = MP_EINVAL; return MP_STREAM_ERROR; } - esp_espnow_obj_t *self = _get_singleton(); - return (self->recv_buffer == NULL) ? 0 : // If not initialised + + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return (espnow_deinited(self)) ? 0 : // If not initialised arg ^ ( // If no data in the buffer, unset the Read ready flag ((ringbuf_avail(self->recv_buffer) == 0) ? MP_STREAM_POLL_RD : 0) | @@ -900,16 +843,14 @@ STATIC const mp_stream_p_t espnow_stream_p = { .ioctl = espnow_stream_ioctl, }; -#if MICROPY_ESPNOW_RSSI -// Return reference to the dictionary of peers we have seen: -// {peer1: (rssi, time_sec), peer2: (rssi, time_msec), ...} -// where: -// peerX is a byte string containing the 6-byte mac address of the peer, -// rssi is the wifi signal strength from the last msg received -// (in dBm from -127 to 0) -// time_sec is the time in milliseconds since device last booted. +// Return reference to the dictionary of peers we have seen: +// {peer1: (rssi, time), peer2: (rssi, time), ...} +// where: +// peerX is a byte string containing the 6-byte mac address of the peer, +// rssi is the wifi signal strength from the last msg received (in dBm from -127 to 0) +// time is the time in milliseconds since device last booted. STATIC void espnow_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - esp_espnow_obj_t *self = _get_singleton(); + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] != MP_OBJ_NULL) { // Only allow "Load" operation return; } @@ -917,27 +858,17 @@ STATIC void espnow_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = self->peers_table; return; } - dest[1] = MP_OBJ_SENTINEL; // Attribute not found + dest[1] = MP_OBJ_SENTINEL; // Attribute not found } -#endif // MICROPY_ESPNOW_RSSI -STATIC const mp_obj_type_t esp_espnow_type = { +const mp_obj_type_t espnow_type = { { &mp_type_type }, - .name = MP_QSTR_ESPNow, + .name = MP_QSTR_NOW, .make_new = espnow_make_new, - .locals_dict = (mp_obj_t)&esp_espnow_locals_dict, - #if MICROPY_ESPNOW_RSSI + .locals_dict = (mp_obj_t)&espnow_locals_dict, .attr = espnow_attr, - #endif // MICROPY_ESPNOW_RSSI .flags = MP_TYPE_FLAG_EXTENDED, MP_TYPE_EXTENDED_FIELDS( .protocol = &espnow_stream_p, ), }; - -const mp_obj_module_t mp_module_espnow = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&espnow_globals_dict, -}; - -MP_REGISTER_MODULE(MP_QSTR__espnow, mp_module_espnow, CIRCUITPY_ESPNOW); diff --git a/ports/espressif/bindings/espnow/ESPNow.h b/ports/espressif/bindings/espnow/Now.h similarity index 95% rename from ports/espressif/bindings/espnow/ESPNow.h rename to ports/espressif/bindings/espnow/Now.h index 9855cd4ff8..6aa011c501 100644 --- a/ports/espressif/bindings/espnow/ESPNow.h +++ b/ports/espressif/bindings/espnow/Now.h @@ -26,4 +26,6 @@ */ #pragma once -void espnow_reset(void); + +#include "py/obj.h" +extern const mp_obj_type_t espnow_type; diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c new file mode 100644 index 0000000000..52325dad58 --- /dev/null +++ b/ports/espressif/bindings/espnow/__init__.c @@ -0,0 +1,85 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 MicroDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shared-bindings/util.h" + +#include "bindings/espnow/__init__.h" +#include "bindings/espnow/Now.h" + +//| """ESP-NOW Module +//| +//| The `espnow` module provides an interface to the +//| `ESP-NOW `_ +//| protocol provided by Espressif on its SoCs +//| (`API docs `_). +//| +//| **Sender:** :: +//| +//| import espnow +//| +//| e = espnow.ESPNow() +//| e.active(True) +//| peer = b'\xbb\xbb\xbb\xbb\xbb\xbb' # MAC address of peer's wifi interface +//| e.add_peer(peer) +//| +//| e.send("Starting...") # Send to all peers +//| for i in range(100): +//| e.send(peer, str(i)*20, True) +//| e.send(b'end') +//| +//| **Receiver:** :: +//| +//| import espnow +//| +//| e = espnow.ESPNow() +//| e.active(True) +//| peer = b'\xaa\xaa\xaa\xaa\xaa\xaa' # MAC address of peer's wifi interface +//| e.add_peer(peer) +//| +//| while True: +//| host, msg = e.recv() +//| if msg: # msg == None if timeout in recv() +//| print(host, msg) +//| if msg == b'end': +//| break +//| """ +//| ... +//| + +STATIC const mp_rom_map_elem_t espnow_module_globals_table[] = { + // module name + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_espnow) }, + // module classes + { MP_ROM_QSTR(MP_QSTR_Now), MP_ROM_PTR(&espnow_type) }, +}; +STATIC MP_DEFINE_CONST_DICT(espnow_module_globals, espnow_module_globals_table); + +const mp_obj_module_t espnow_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&espnow_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_espnow, espnow_module, CIRCUITPY_ESPNOW); diff --git a/ports/espressif/bindings/espnow/__init__.h b/ports/espressif/bindings/espnow/__init__.h new file mode 100644 index 0000000000..fb814a434f --- /dev/null +++ b/ports/espressif/bindings/espnow/__init__.h @@ -0,0 +1,29 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 MicroDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +void espnow_reset(void); diff --git a/ports/espressif/mpconfigport.h b/ports/espressif/mpconfigport.h index 8c206cf02a..e4733356bb 100644 --- a/ports/espressif/mpconfigport.h +++ b/ports/espressif/mpconfigport.h @@ -44,7 +44,7 @@ #endif #if CIRCUITPY_ESPNOW -#define ESPNOW_ROOT_POINTERS struct _esp_espnow_obj_t *espnow_singleton; +#define ESPNOW_ROOT_POINTERS struct _espnow_obj_t *espnow_singleton; #else #define ESPNOW_ROOT_POINTERS #endif diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 8a6dfabb5f..be33b87cca 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -37,7 +37,7 @@ #include "freertos/task.h" #include "bindings/espidf/__init__.h" -#include "bindings/espnow/ESPNow.h" +#include "bindings/espnow/__init__.h" #include "bindings/espulp/__init__.h" #include "common-hal/microcontroller/Pin.h" #include "common-hal/analogio/AnalogOut.h"