From 59c3618cbdb0e8627ef701727e5c2178eb321067 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Fri, 20 Jan 2023 13:19:59 +0530 Subject: [PATCH 01/17] clone espnow implementation Co-authored-by: Nick Moore Co-authored-by: shawwwn Co-authored-by: glenn20 --- ports/espressif/bindings/espnow/ESPNow.c | 908 +++++++++++++++++++++++ ports/espressif/bindings/espnow/ESPNow.h | 30 + 2 files changed, 938 insertions(+) create mode 100644 ports/espressif/bindings/espnow/ESPNow.c create mode 100644 ports/espressif/bindings/espnow/ESPNow.h diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c new file mode 100644 index 000000000000..0e9da4fc1b59 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -0,0 +1,908 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017-2020 Nick Moore + * Copyright (c) 2018 shawwwn + * Copyright (c) 2020-2021 Glenn Moloney @glenn20 + * + * 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 +#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/stream.h" +#include "py/binary.h" +#include "py/ringbuf.h" + +#include "mpconfigport.h" +#include "mphalport.h" +#include "modnetwork.h" +#include "modespnow.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); }) + +static const uint8_t ESPNOW_MAGIC = 0x99; + +// 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 { + espnow_hdr_t hdr; // The header + uint8_t peer[6]; // Peer address + 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 { + 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) + 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) + mp_obj_t recv_cb; // Callback when a packet is received + mp_obj_t recv_cb_arg; // Argument passed to callback + #if MICROPY_ESPNOW_RSSI + mp_obj_t peers_table; // A dictionary of discovered peers + #endif // MICROPY_ESPNOW_RSSI +} esp_espnow_obj_t; + +const mp_obj_type_t esp_espnow_type; + +// ### 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() { + return MP_STATE_PORT(espnow_singleton); +} + +static esp_espnow_obj_t *_get_singleton_initialised() { + 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); + } + return self; +} + +// 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) { + + // 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->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() + self->recv_cb = mp_const_none; + #if MICROPY_ESPNOW_RSSI + 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; + + 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); + +STATIC void recv_cb(const uint8_t *mac_addr, const uint8_t *data, int len); + +// 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 mp_obj_t espnow_init(mp_obj_t _) { + esp_espnow_obj_t *self = _get_singleton(); + if (self->recv_buffer == NULL) { // Already initialised + self->recv_buffer = m_new_obj(ringbuf_t); + ringbuf_alloc(self->recv_buffer, self->recv_buffer_size); + + esp_initialise_wifi(); // Call the wifi init code in network_wlan.c + 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)); + } + return mp_const_none; +} + +// 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;. +mp_obj_t espnow_deinit(mp_obj_t _) { + esp_espnow_obj_t *self = _get_singleton(); + if (self != NULL && self->recv_buffer != NULL) { + check_esp_err(esp_now_unregister_recv_cb()); + check_esp_err(esp_now_unregister_send_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->tx_packets = self->tx_responses; + } + return mp_const_none; +} + +STATIC mp_obj_t espnow_active(size_t n_args, const mp_obj_t *args) { + esp_espnow_obj_t *self = _get_singleton(); + if (n_args > 1) { + if (mp_obj_is_true(args[1])) { + espnow_init(self); + } else { + espnow_deinit(self); + } + } + return self->recv_buffer != NULL ? mp_const_true : mp_const_false; +} +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(); + 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} }, + { MP_QSTR_buffer, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { 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); + + 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) + esp_initialise_wifi(); // Call the wifi init code in network_wlan.c + 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 + } + 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; + if (name == QS(MP_QSTR_buffer)) { + return mp_obj_new_int(self->recv_buffer_size); + } else if (name == QS(MP_QSTR_timeout)) { + return mp_obj_new_int(self->recv_timeout_ms); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); + } +#undef QS + + return mp_const_none; +} +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) { + 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; + 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(); + 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)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_obj, espnow_stats); + +#if MICROPY_ESPNOW_RSSI +// ### 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 +// - to return unique bytestrings for each peer which supports more efficient +// application memory usage and peer handling. + +// Get the RSSI value from the wifi packet header +static inline int8_t _get_rssi_from_wifi_pkt(const uint8_t *msg) { + // Warning: Secret magic to get the rssi from the wifi packet header + // See espnow.c:espnow_recv_cb() at https://github.com/espressif/esp-now/ + // In the wifi packet the msg comes after a wifi_promiscuous_pkt_t + // and a espnow_frame_format_t. + // Backtrack to get a pointer to the wifi_promiscuous_pkt_t. + static const size_t sizeof_espnow_frame_format = 39; + wifi_promiscuous_pkt_t *wifi_pkt = (wifi_promiscuous_pkt_t *)( + msg - sizeof_espnow_frame_format - sizeof(wifi_promiscuous_pkt_t)); + + #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) { + + // 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. + mp_map_t *map = mp_obj_dict_get_map(self->peers_table); + mp_obj_str_t peer_obj = {{&mp_type_bytes}, 0, ESP_NOW_ETH_ALEN, peer}; + mp_map_elem_t *item = mp_map_lookup(map, &peer_obj, MP_MAP_LOOKUP); + if (item == NULL) { + // If not found, add the peer using a new bytestring + map->is_fixed = 0; // Allow to modify the dict + mp_obj_t new_peer = mp_obj_new_bytes(peer, ESP_NOW_ETH_ALEN); + item = mp_map_lookup(map, new_peer, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + item->value = mp_obj_new_list(2, NULL); + map->is_fixed = 1; // Relock the dict + } + return item; +} + +// 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(); + // 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); + list->items[0] = MP_OBJ_NEW_SMALL_INT(rssi); + list->items[1] = mp_obj_new_int(time_ms); + return item; +} +#endif // MICROPY_ESPNOW_RSSI + +// ### Handling espnow packets in the recv buffer +// + +// ### 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")); + } + return (uint8_t *)bufinfo.buf; +} + +static uint8_t *_get_bytes_len(mp_obj_t obj, size_t len) { + return _get_bytes_len_rw(obj, len, MP_BUFFER_READ); +} + +static uint8_t *_get_bytes_len_w(mp_obj_t obj, size_t len) { + return _get_bytes_len_rw(obj, len, MP_BUFFER_WRITE); +} + +// 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; +} + +// Copy data from the ring buffer - wait if buffer is empty up to timeout_ms +int ringbuf_read_wait(ringbuf_t *r, void *data, size_t len, int timeout_ms) { + int64_t end = mp_hal_ticks_ms() + timeout_ms; + int status = 0; + while ( + ((status = ringbuf_read(r, data, len)) == 0) && + (end - (int64_t)mp_hal_ticks_ms()) >= 0) { + MICROPY_EVENT_POLL_HOOK; + } + 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) + ? 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; + while (self->tx_responses < self->tx_packets) { + if ((t = mp_hal_ticks_ms() - start) > PENDING_RESPONSES_TIMEOUT_MS) { + mp_raise_OSError(MP_ETIMEDOUT); + } + if (t > PENDING_RESPONSES_BUSY_POLL_MS) { + // After 10ms of busy waiting give other tasks a look in. + MICROPY_EVENT_POLL_HOOK; + } + } +} + +// 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. +STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) { + esp_espnow_obj_t *self = _get_singleton_initialised(); + // 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; + bool sync = n_args <= 3 || args[3] == mp_const_none || mp_obj_is_true(args[3]); + + // Get a pointer to the data buffer of the message + mp_buffer_info_t message; + mp_get_buffer_raise(msg, &message, MP_BUFFER_READ); + + if (sync) { + // Flush out any pending responses. + // If the last call was sync==False there may be outstanding responses + // still to be received (possible many if we just had a burst of + // unsync send()s). We need to wait for all pending responses if this + // call has sync=True. + _wait_for_pending_responses(self); + } + int 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))) && + (mp_hal_ticks_ms() - start) <= DEFAULT_SEND_TIMEOUT_MS) { + MICROPY_EVENT_POLL_HOOK; + } + 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); + 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 +// + +// 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) { + + esp_espnow_obj_t *self = _get_singleton(); + self->tx_responses++; + if (status != ESP_NOW_SEND_SUCCESS) { + self->tx_failures++; + } +} + +// 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 (self->recv_cb != mp_const_none) { + mp_sched_schedule(self->recv_cb, self->recv_cb_arg); + } +} + +// ### 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) { + 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) { + + 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_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); + 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); + } + } + 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 +// send() logic and is updated from add_peer(), mod_peer() and del_peer(). +STATIC void _update_peer_count() { + esp_espnow_obj_t *self = _get_singleton_initialised(); + + 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; + if (++count >= ESP_NOW_MAX_TOTAL_PEER_NUM) { + break; // Should not happen + } + } + self->peer_count = 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) { + + 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); + + check_esp_err(esp_now_add_peer(&peer)); + _update_peer_count(); + + 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) { + 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(); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_del_peer_obj, espnow_del_peer); + +// Convert a peer_info struct to python tuple +// Used by espnow_get_peer() and espnow_get_peers() +static mp_obj_t _peer_info_to_tuple(const esp_now_peer_info_t *peer) { + return NEW_TUPLE( + mp_obj_new_bytes(peer->peer_addr, MP_ARRAY_SIZE(peer->peer_addr)), + mp_obj_new_bytes(peer->lmk, MP_ARRAY_SIZE(peer->lmk)), + mp_obj_new_int(peer->channel), + mp_obj_new_int(peer->ifidx), + (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(); + + // Build and initialise the peer info tuple. + mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peer_count, NULL); + esp_now_peer_info_t peer = {0}; + for (int i = 0; i < peerinfo_tuple->len; i++) { + int status = esp_now_fetch_peer((i == 0), &peer); + 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 _) { + 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 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) }, + + // 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) }, + + // 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 +}; +STATIC MP_DEFINE_CONST_DICT(esp_espnow_locals_dict, esp_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 +// ...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) { + 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 + arg ^ ( + // If no data in the buffer, unset the Read ready flag + ((ringbuf_avail(self->recv_buffer) == 0) ? MP_STREAM_POLL_RD : 0) | + // If still waiting for responses, unset the Write ready flag + ((self->tx_responses < self->tx_packets) ? MP_STREAM_POLL_WR : 0)); +} + +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. +STATIC void espnow_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + esp_espnow_obj_t *self = _get_singleton(); + if (dest[0] != MP_OBJ_NULL) { // Only allow "Load" operation + return; + } + if (attr == MP_QSTR_peers_table) { + dest[0] = self->peers_table; + return; + } + dest[1] = MP_OBJ_SENTINEL; // Attribute not found +} +#endif // MICROPY_ESPNOW_RSSI + +MP_DEFINE_CONST_OBJ_TYPE( + esp_espnow_type, + MP_QSTR_ESPNow, + MP_TYPE_FLAG_NONE, + make_new, espnow_make_new, + #if MICROPY_ESPNOW_RSSI + attr, espnow_attr, + #endif // MICROPY_ESPNOW_RSSI + protocol, &espnow_stream_p, + locals_dict, &esp_espnow_locals_dict + ); + +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); +MP_REGISTER_ROOT_POINTER(struct _esp_espnow_obj_t *espnow_singleton); diff --git a/ports/espressif/bindings/espnow/ESPNow.h b/ports/espressif/bindings/espnow/ESPNow.h new file mode 100644 index 000000000000..3c6280b1ced4 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNow.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Glenn Moloney @glenn20 + * + * 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 "py/obj.h" + +// Called from main.c:mp_task() to reset the espnow software stack +mp_obj_t espnow_deinit(mp_obj_t _); From 7330c638b9abe1e84b4b6d45f2c0c78179985aea Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Fri, 20 Jan 2023 13:27:00 +0530 Subject: [PATCH 02/17] minimal changes to make espnow work --- locale/circuitpython.pot | 33 ++++++ ports/espressif/Makefile | 5 + ports/espressif/bindings/espnow/ESPNow.c | 109 ++++++++++++------ ports/espressif/bindings/espnow/ESPNow.h | 7 +- .../common-hal/_bleio/CharacteristicBuffer.c | 9 +- .../espressif/common-hal/_bleio/Connection.c | 7 +- .../common-hal/_bleio/PacketBuffer.c | 7 +- ports/espressif/mpconfigport.h | 18 +-- ports/espressif/mpconfigport.mk | 1 + ports/espressif/supervisor/port.c | 5 + py/circuitpy_mpconfig.mk | 3 + py/ringbuf.c | 44 +++++++ py/ringbuf.h | 10 ++ 13 files changed, 203 insertions(+), 55 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index b98bddec5013..33effc9612ce 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -663,6 +663,7 @@ msgstr "" #: ports/espressif/boards/m5stack_core_basic/mpconfigboard.h #: ports/espressif/boards/m5stack_core_fire/mpconfigboard.h +#: ports/espressif/boards/m5stack_stick_c/mpconfigboard.h msgid "Button A was pressed at start up.\n" msgstr "" @@ -917,6 +918,18 @@ 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 @@ -2429,6 +2442,10 @@ msgstr "" msgid "addresses is empty" msgstr "" +#: ports/espressif/bindings/espnow/ESPNow.c +msgid "an error occured" +msgstr "" + #: py/compile.c msgid "annotation must be an identifier" msgstr "" @@ -2953,6 +2970,10 @@ 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 "" @@ -3314,6 +3335,10 @@ 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 "" @@ -3858,6 +3883,10 @@ 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 "" @@ -4153,6 +4182,10 @@ msgstr "" msgid "unindent doesn't match any outer indent level" msgstr "" +#: ports/espressif/bindings/espnow/ESPNow.c +msgid "unknown config param" +msgstr "" + #: py/objstr.c #, c-format msgid "unknown conversion specifier %c" diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index ddfcaeeb6e7f..92e91e808a8a 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -259,6 +259,11 @@ CFLAGS += -isystem esp32-camera/driver/include CFLAGS += -isystem esp32-camera/conversions/include endif +ifneq ($(CIRCUITPY_ESPNOW),0) +SRC_ESPNOW := $(wildcard bindings/espnow/*.c) +SRC_C += $(SRC_ESPNOW) +endif + ifneq ($(CIRCUITPY_ESPULP),0) SRC_ULP := \ $(wildcard common-hal/espulp/*.c) \ diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 0e9da4fc1b59..33cf2342b878 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -6,6 +6,7 @@ * Copyright (c) 2017-2020 Nick Moore * Copyright (c) 2018 shawwwn * Copyright (c) 2020-2021 Glenn Moloney @glenn20 + * 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 @@ -26,7 +27,6 @@ * THE SOFTWARE. */ - #include #include #include @@ -48,8 +48,10 @@ #include "mpconfigport.h" #include "mphalport.h" -#include "modnetwork.h" -#include "modespnow.h" + +#include "bindings/espnow/ESPNow.h" + +#include "shared-bindings/wifi/__init__.h" #ifndef MICROPY_ESPNOW_RSSI // Include code to track rssi of peers @@ -115,14 +117,22 @@ typedef struct _esp_espnow_obj_t { 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 mp_obj_t peers_table; // A dictionary of discovered peers #endif // MICROPY_ESPNOW_RSSI } esp_espnow_obj_t; -const mp_obj_type_t esp_espnow_type; +static const mp_obj_type_t esp_espnow_type; + +static void check_esp_err(esp_err_t status) { + if (status != ESP_OK) { + mp_raise_RuntimeError(translate("an error occured")); + } +} // ### Initialisation and Config functions // @@ -130,16 +140,17 @@ const mp_obj_type_t esp_espnow_type; // 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() { +static esp_espnow_obj_t *_get_singleton(void) { return MP_STATE_PORT(espnow_singleton); } -static esp_espnow_obj_t *_get_singleton_initialised() { +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); + // check_esp_err(ESP_ERR_ESPNOW_NOT_INIT); + mp_raise_RuntimeError(translate("espnow not inited")); } return self; } @@ -163,7 +174,9 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, 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->peers_table = mp_obj_new_dict(0); // Prevent user code modifying the dict @@ -181,29 +194,38 @@ STATIC void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status); STATIC void recv_cb(const uint8_t *mac_addr, const uint8_t *data, int len); +static void _wifi_init(void) { + if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { + common_hal_wifi_init(false); + common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true); + } +} + // 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 mp_obj_t espnow_init(mp_obj_t _) { +static void espnow_init(void) { esp_espnow_obj_t *self = _get_singleton(); if (self->recv_buffer == NULL) { // Already initialised self->recv_buffer = m_new_obj(ringbuf_t); - ringbuf_alloc(self->recv_buffer, self->recv_buffer_size); + if (!ringbuf_alloc(self->recv_buffer, self->recv_buffer_size, true)) { + m_malloc_fail(self->recv_buffer_size); + } + + _wifi_init(); // Call the wifi init code - esp_initialise_wifi(); // Call the wifi init code in network_wlan.c 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)); } - return mp_const_none; } // 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;. -mp_obj_t espnow_deinit(mp_obj_t _) { +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()); @@ -214,16 +236,20 @@ mp_obj_t espnow_deinit(mp_obj_t _) { self->peer_count = 0; // esp_now_deinit() removes all peers. self->tx_packets = self->tx_responses; } - return mp_const_none; +} + +void espnow_reset(void) { + espnow_deinit(); + MP_STATE_PORT(espnow_singleton) = NULL; } STATIC mp_obj_t espnow_active(size_t n_args, const mp_obj_t *args) { esp_espnow_obj_t *self = _get_singleton(); if (n_args > 1) { if (mp_obj_is_true(args[1])) { - espnow_init(self); + espnow_init(); } else { - espnow_deinit(self); + espnow_deinit(); } } return self->recv_buffer != NULL ? mp_const_true : mp_const_false; @@ -257,7 +283,7 @@ STATIC mp_obj_t espnow_config( } if (args[ARG_rate].u_int >= 0) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) - esp_initialise_wifi(); // Call the wifi init code in network_wlan.c + _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( @@ -288,6 +314,7 @@ 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)) { @@ -295,6 +322,9 @@ STATIC mp_obj_t espnow_on_recv(size_t n_args, const mp_obj_t *args) { } 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); @@ -329,9 +359,11 @@ static inline int8_t _get_rssi_from_wifi_pkt(const uint8_t *msg) { // and a espnow_frame_format_t. // Backtrack to get a pointer to the wifi_promiscuous_pkt_t. static const size_t sizeof_espnow_frame_format = 39; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" 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 @@ -411,13 +443,13 @@ static const uint8_t *_get_peer(mp_obj_t mac_addr) { } // Copy data from the ring buffer - wait if buffer is empty up to timeout_ms -int ringbuf_read_wait(ringbuf_t *r, void *data, size_t len, int timeout_ms) { +static int ringbuf_read_wait(ringbuf_t *r, void *data, size_t len, int timeout_ms) { int64_t end = mp_hal_ticks_ms() + timeout_ms; int status = 0; while ( ((status = ringbuf_read(r, data, len)) == 0) && (end - (int64_t)mp_hal_ticks_ms()) >= 0) { - MICROPY_EVENT_POLL_HOOK; + RUN_BACKGROUND_TASKS; } return status; } @@ -437,7 +469,7 @@ 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) - ? mp_obj_get_int(args[2]) : self->recv_timeout_ms); + ? (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) { @@ -510,7 +542,7 @@ static void _wait_for_pending_responses(esp_espnow_obj_t *self) { } if (t > PENDING_RESPONSES_BUSY_POLL_MS) { // After 10ms of busy waiting give other tasks a look in. - MICROPY_EVENT_POLL_HOOK; + RUN_BACKGROUND_TASKS; } } } @@ -545,14 +577,14 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) { // call has sync=True. _wait_for_pending_responses(self); } - int saved_failures = self->tx_failures; + 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))) && (mp_hal_ticks_ms() - start) <= DEFAULT_SEND_TIMEOUT_MS) { - MICROPY_EVENT_POLL_HOOK; + 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 @@ -610,9 +642,11 @@ STATIC void recv_cb( 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 } // ### Peer Management Functions @@ -670,7 +704,7 @@ STATIC bool _update_peer_info( // Update the cached peer count in self->peer_count; // The peer_count 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() { +STATIC void _update_peer_count(void) { esp_espnow_obj_t *self = _get_singleton_initialised(); esp_now_peer_info_t peer = {0}; @@ -745,8 +779,8 @@ STATIC mp_obj_t espnow_get_peers(mp_obj_t _) { // Build and initialise the peer info tuple. mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peer_count, NULL); esp_now_peer_info_t peer = {0}; - for (int i = 0; i < peerinfo_tuple->len; i++) { - int status = esp_now_fetch_peer((i == 0), &peer); + 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); } @@ -887,22 +921,23 @@ STATIC void espnow_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } #endif // MICROPY_ESPNOW_RSSI -MP_DEFINE_CONST_OBJ_TYPE( - esp_espnow_type, - MP_QSTR_ESPNow, - MP_TYPE_FLAG_NONE, - make_new, espnow_make_new, +STATIC const mp_obj_type_t esp_espnow_type = { + { &mp_type_type }, + .name = MP_QSTR_ESPNow, + .make_new = espnow_make_new, + .locals_dict = (mp_obj_t)&esp_espnow_locals_dict, #if MICROPY_ESPNOW_RSSI - attr, espnow_attr, + .attr = espnow_attr, #endif // MICROPY_ESPNOW_RSSI - protocol, &espnow_stream_p, - locals_dict, &esp_espnow_locals_dict - ); + .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); -MP_REGISTER_ROOT_POINTER(struct _esp_espnow_obj_t *espnow_singleton); +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/ESPNow.h index 3c6280b1ced4..9855cd4ff83c 100644 --- a/ports/espressif/bindings/espnow/ESPNow.h +++ b/ports/espressif/bindings/espnow/ESPNow.h @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2021 Glenn Moloney @glenn20 + * 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 @@ -24,7 +25,5 @@ * THE SOFTWARE. */ -#include "py/obj.h" - -// Called from main.c:mp_task() to reset the espnow software stack -mp_obj_t espnow_deinit(mp_obj_t _); +#pragma once +void espnow_reset(void); diff --git a/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c b/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c index 48be3abd67c9..8edb40ca85cc 100644 --- a/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c +++ b/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c @@ -27,17 +27,20 @@ #include #include -#include "shared/runtime/interrupt_char.h" #include "py/ringbuf.h" #include "py/runtime.h" #include "py/stream.h" +#include "shared/runtime/interrupt_char.h" + #include "shared-bindings/_bleio/__init__.h" #include "shared-bindings/_bleio/Connection.h" -#include "supervisor/shared/tick.h" -#include "common-hal/_bleio/CharacteristicBuffer.h" #include "shared-bindings/_bleio/CharacteristicBuffer.h" +#include "supervisor/shared/tick.h" + +#include "common-hal/_bleio/ble_events.h" + STATIC int characteristic_buffer_on_ble_evt(struct ble_gap_event *event, void *param) { bleio_characteristic_buffer_obj_t *self = (bleio_characteristic_buffer_obj_t *)param; switch (event->type) { diff --git a/ports/espressif/common-hal/_bleio/Connection.c b/ports/espressif/common-hal/_bleio/Connection.c index 63c85099e691..75def8ad76c9 100644 --- a/ports/espressif/common-hal/_bleio/Connection.c +++ b/ports/espressif/common-hal/_bleio/Connection.c @@ -30,21 +30,24 @@ #include #include -#include "shared/runtime/interrupt_char.h" #include "py/gc.h" #include "py/objlist.h" #include "py/objstr.h" #include "py/qstr.h" #include "py/runtime.h" + +#include "shared/runtime/interrupt_char.h" + #include "shared-bindings/_bleio/__init__.h" #include "shared-bindings/_bleio/Adapter.h" #include "shared-bindings/_bleio/Attribute.h" #include "shared-bindings/_bleio/Characteristic.h" #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/UUID.h" + #include "supervisor/shared/tick.h" -// #include "common-hal/_bleio/bonding.h" +#include "common-hal/_bleio/ble_events.h" #include "host/ble_att.h" diff --git a/ports/espressif/common-hal/_bleio/PacketBuffer.c b/ports/espressif/common-hal/_bleio/PacketBuffer.c index 3b3e51df6173..d1d8c24123df 100644 --- a/ports/espressif/common-hal/_bleio/PacketBuffer.c +++ b/ports/espressif/common-hal/_bleio/PacketBuffer.c @@ -27,17 +27,20 @@ #include #include -#include "shared/runtime/interrupt_char.h" #include "py/runtime.h" #include "py/stream.h" +#include "shared/runtime/interrupt_char.h" + #include "shared-bindings/_bleio/__init__.h" #include "shared-bindings/_bleio/Connection.h" #include "shared-bindings/_bleio/PacketBuffer.h" -#include "supervisor/shared/tick.h" +#include "supervisor/shared/tick.h" #include "supervisor/shared/bluetooth/serial.h" +#include "common-hal/_bleio/ble_events.h" + #include "host/ble_att.h" STATIC void write_to_ringbuf(bleio_packet_buffer_obj_t *self, const struct os_mbuf *mbuf) { diff --git a/ports/espressif/mpconfigport.h b/ports/espressif/mpconfigport.h index c296be202495..8c206cf02a1f 100644 --- a/ports/espressif/mpconfigport.h +++ b/ports/espressif/mpconfigport.h @@ -38,18 +38,22 @@ #include "py/circuitpy_mpconfig.h" #if CIRCUITPY_BLEIO -#include "common-hal/_bleio/ble_events.h" +#define BLEIO_ROOT_POINTERS struct ble_event_handler_entry *ble_event_handler_entries; +#else +#define BLEIO_ROOT_POINTERS #endif -#if CIRCUITPY_BLEIO -#define MICROPY_PORT_ROOT_POINTERS \ - CIRCUITPY_COMMON_ROOT_POINTERS \ - ble_event_handler_entry_t *ble_event_handler_entries; +#if CIRCUITPY_ESPNOW +#define ESPNOW_ROOT_POINTERS struct _esp_espnow_obj_t *espnow_singleton; #else -#define MICROPY_PORT_ROOT_POINTERS \ - CIRCUITPY_COMMON_ROOT_POINTERS +#define ESPNOW_ROOT_POINTERS #endif +#define MICROPY_PORT_ROOT_POINTERS \ + CIRCUITPY_COMMON_ROOT_POINTERS \ + BLEIO_ROOT_POINTERS \ + ESPNOW_ROOT_POINTERS + #define MICROPY_NLR_SETJMP (1) #define CIRCUITPY_DEFAULT_STACK_SIZE 0x6000 diff --git a/ports/espressif/mpconfigport.mk b/ports/espressif/mpconfigport.mk index c5c73c77bd99..035fa75278b2 100644 --- a/ports/espressif/mpconfigport.mk +++ b/ports/espressif/mpconfigport.mk @@ -81,6 +81,7 @@ CIRCUITPY_DUALBANK = 0 endif # Modules dependent on other modules +CIRCUITPY_ESPNOW ?= $(CIRCUITPY_WIFI) CIRCUITPY_GIFIO ?= $(CIRCUITPY_ESP32_CAMERA) CIRCUITPY_QRIO ?= $(CIRCUITPY_ESP32_CAMERA) diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 6dd09ed238f9..8a6dfabb5f12 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -37,6 +37,7 @@ #include "freertos/task.h" #include "bindings/espidf/__init__.h" +#include "bindings/espnow/ESPNow.h" #include "bindings/espulp/__init__.h" #include "common-hal/microcontroller/Pin.h" #include "common-hal/analogio/AnalogOut.h" @@ -369,6 +370,10 @@ void reset_port(void) { dualbank_reset(); #endif + #if CIRCUITPY_ESPNOW + espnow_reset(); + #endif + #if CIRCUITPY_ESPULP espulp_reset(); #endif diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 3c1c173a5155..03ff0f9d9560 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -227,6 +227,9 @@ CFLAGS += -DCIRCUITPY_ERRNO=$(CIRCUITPY_ERRNO) CIRCUITPY_ESPIDF ?= 0 CFLAGS += -DCIRCUITPY_ESPIDF=$(CIRCUITPY_ESPIDF) +CIRCUITPY_ESPNOW ?= 0 +CFLAGS += -DCIRCUITPY_ESPNOW=$(CIRCUITPY_ESPNOW) + CIRCUITPY_ESPULP ?= 0 CFLAGS += -DCIRCUITPY_ESPULP=$(CIRCUITPY_ESPULP) diff --git a/py/ringbuf.c b/py/ringbuf.c index 8a4cb33cbc89..f90805d6f324 100644 --- a/py/ringbuf.c +++ b/py/ringbuf.c @@ -142,3 +142,47 @@ int ringbuf_put16(ringbuf_t *r, uint16_t v) { ringbuf_put(r, v & 0xff); return 0; } + +// Returns: +// 1: Success +// 0: Not enough data available to complete read (try again later) +// -1: Requested read is larger than buffer - will never succeed +int ringbuf_read(ringbuf_t *r, void *data, size_t data_len) { + if (ringbuf_avail(r) < data_len) { + return (r->size <= data_len) ? -1 : 0; + } + uint32_t iget = r->next_read; + uint32_t iget_a = (iget + data_len) % r->size; + uint8_t *datap = data; + if (iget_a < iget) { + // Copy part of the data from the space left at the end of the buffer + memcpy(datap, r->buf + iget, r->size - iget); + datap += (r->size - iget); + iget = 0; + } + memcpy(datap, r->buf + iget, iget_a - iget); + r->next_read = iget_a; + return 1; +} + +// Returns: +// 1: Success +// 0: Not enough free space available to complete write (try again later) +// -1: Requested write is larger than buffer - will never succeed +int ringbuf_write(ringbuf_t *r, const void *data, size_t data_len) { + if (ringbuf_free(r) < data_len) { + return (r->size <= data_len) ? -1 : 0; + } + uint32_t iput = r->next_write; + uint32_t iput_a = (iput + data_len) % r->size; + const uint8_t *datap = data; + if (iput_a < iput) { + // Copy part of the data to the end of the buffer + memcpy(r->buf + iput, datap, r->size - iput); + datap += (r->size - iput); + iput = 0; + } + memcpy(r->buf + iput, datap, iput_a - iput); + r->next_write = iput_a; + return 1; +} diff --git a/py/ringbuf.h b/py/ringbuf.h index 2725bedccaff..5d54abc6b5f0 100644 --- a/py/ringbuf.h +++ b/py/ringbuf.h @@ -61,5 +61,15 @@ size_t ringbuf_get_n(ringbuf_t *r, uint8_t *buf, size_t bufsize); // Note: big-endian. Return -1 if can't read or write two bytes. int ringbuf_get16(ringbuf_t *r); int ringbuf_put16(ringbuf_t *r, uint16_t v); +int ringbuf_read(ringbuf_t *r, void *data, size_t data_len); +int ringbuf_write(ringbuf_t *r, const void *data, size_t data_len); + +static inline size_t ringbuf_free(ringbuf_t *r) { + return (r->size + r->next_read - r->next_write - 1) % r->size; +} + +static inline size_t ringbuf_avail(ringbuf_t *r) { + return (r->size + r->next_write - r->next_read) % r->size; +} #endif // MICROPY_INCLUDED_PY_RINGBUF_H 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 03/17] update espnow module --- locale/circuitpython.pot | 28 +- .../bindings/espnow/{ESPNow.c => Now.c} | 887 ++++++++---------- .../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, 530 insertions(+), 507 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 33effc9612ce..d0fb9e5da4ec 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 33cf2342b878..935a740164fd 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);}) + +#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) -static const uint8_t ESPNOW_MAGIC = 0x99; +// 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")); +static bool espnow_deinited(espnow_obj_t *self) { + return self->recv_buffer == NULL; +} + +// 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(); } - return self; } -// 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) { +//| 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 --- + +// 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); -STATIC void recv_cb(const uint8_t *mac_addr, const uint8_t *data, int 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); } -} -// 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) { + 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; + } - 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; + 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 } - 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 + int msg_len = hdr.msg_len; - 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); + // 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); } - #endif + + return MP_OBJ_NEW_SMALL_INT(msg_len); } +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_recv_obj, 2, 3, espnow_recv); -// ### Peer Management Functions -// +//| 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 --- -// 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(); - - // Build and initialise the peer info tuple. - mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peer_count, NULL); +//| 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}; - 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); - } - - 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); - + 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); -// 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) { +//| 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->num_peers, NULL); 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(); + 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); + } - return mp_const_none; + return peerinfo_tuple; } -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_mod_peer_obj, 2, espnow_mod_peer); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers); -// 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 -}; -STATIC MP_DEFINE_CONST_DICT(esp_espnow_locals_dict, esp_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)}, + { 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(espnow_globals_dict, espnow_globals_dict_table); +STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_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 9855cd4ff83c..6aa011c5017d 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 000000000000..52325dad5811 --- /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 000000000000..fb814a434f07 --- /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 8c206cf02a1f..e4733356bb30 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 8a6dfabb5f12..be33b87cca77 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" From 3c10dd8b5f033e8822f845359c4ef1812c331d6b Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Mon, 23 Jan 2023 23:10:00 +0530 Subject: [PATCH 04/17] update espnow api --- locale/circuitpython.pot | 12 +- .../bindings/espnow/{Now.c => ESPNow.c} | 758 +++++++++--------- .../bindings/espnow/{Now.h => ESPNow.h} | 0 ports/espressif/bindings/espnow/__init__.c | 12 +- py/objtuple.h | 3 + py/ringbuf.c | 64 +- py/ringbuf.h | 10 - 7 files changed, 406 insertions(+), 453 deletions(-) rename ports/espressif/bindings/espnow/{Now.c => ESPNow.c} (50%) rename ports/espressif/bindings/espnow/{Now.h => ESPNow.h} (100%) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index d0fb9e5da4ec..cca9e1b0670f 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -124,7 +124,7 @@ msgstr "" msgid "%q init failed" msgstr "" -#: shared-bindings/dualbank/__init__.c +#: ports/espressif/bindings/espnow/ESPNow.c shared-bindings/dualbank/__init__.c msgid "%q is %q" msgstr "" @@ -2430,7 +2430,7 @@ msgstr "" msgid "addresses is empty" msgstr "" -#: ports/espressif/bindings/espnow/Now.c +#: ports/espressif/bindings/espnow/ESPNow.c msgid "an error occured" msgstr "" @@ -3814,6 +3814,10 @@ msgstr "" msgid "parameters must be registers in sequence r0 to r3" msgstr "" +#: ports/espressif/bindings/espnow/ESPNow.c +msgid "peer already exists" +msgstr "" + #: shared-bindings/bitmaptools/__init__.c shared-bindings/displayio/Bitmap.c msgid "pixel coordinates out of bounds" msgstr "" @@ -4158,10 +4162,6 @@ msgstr "" msgid "unindent doesn't match any outer indent level" msgstr "" -#: ports/espressif/bindings/espnow/Now.c -msgid "unknown config param" -msgstr "" - #: py/objstr.c #, c-format msgid "unknown conversion specifier %c" diff --git a/ports/espressif/bindings/espnow/Now.c b/ports/espressif/bindings/espnow/ESPNow.c similarity index 50% rename from ports/espressif/bindings/espnow/Now.c rename to ports/espressif/bindings/espnow/ESPNow.c index 935a740164fd..0bcd7d39ed04 100644 --- a/ports/espressif/bindings/espnow/Now.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -38,18 +38,15 @@ #include "mphalport.h" #include "bindings/espnow/__init__.h" -#include "bindings/espnow/Now.h" +#include "bindings/espnow/ESPNow.h" #include "shared-bindings/util.h" #include "shared-bindings/wifi/__init__.h" -// 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 ESPNOW_MAGIC 0x99 // The maximum length of an espnow packet (bytes) -#define MAX_PACKET_LEN (sizeof(espnow_pkt_t) + ESP_NOW_MAX_DATA_LEN) +#define MAX_PACKET_LEN (sizeof(espnow_packet_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 @@ -73,26 +70,26 @@ typedef struct { uint8_t msg_len; // Length of the message uint32_t time_ms; // Timestamp (ms) when packet is received int8_t rssi; // RSSI value (dBm) (-127 to 0) -} __attribute__((packed)) espnow_hdr_t; +} __attribute__((packed)) espnow_header_t; typedef struct { - espnow_hdr_t hdr; // The header + espnow_header_t header; // The header uint8_t peer[6]; // Peer address uint8_t msg[0]; // Message is up to 250 bytes -} __attribute__((packed)) espnow_pkt_t; +} __attribute__((packed)) espnow_packet_t; // The data structure for the espnow_singleton. 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() + wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. volatile size_t rx_packets; // # of received packets 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 num_peers; // Cache the # of peers for send(sync=True) + size_t peers_count; // Cache the # of peers for send(sync=True) mp_obj_t peers_table; // A dictionary of discovered peers } espnow_obj_t; @@ -105,8 +102,6 @@ static void check_esp_err(esp_err_t status) { // --- 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 espnow_obj_t *_get_singleton(void) { return MP_STATE_PORT(espnow_singleton); } @@ -115,42 +110,56 @@ static bool espnow_deinited(espnow_obj_t *self) { return self->recv_buffer == NULL; } -// 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 void _set_buffer_size(espnow_obj_t *self, mp_int_t value) { + self->recv_buffer_size = mp_arg_validate_int_min(value, MAX_PACKET_LEN, MP_QSTR_buffer_size); +}; + +static void _set_phy_rate(espnow_obj_t *self, mp_int_t value) { + self->phy_rate = mp_arg_validate_int_range(value, 0, WIFI_PHY_RATE_MAX - 1, MP_QSTR_phy_rate); +}; + +//| class ESPNow: +//| """Provides access to the ESP-NOW protocol.""" +//| +//| def __init__(self, buffer_size: Optional[int], phy_rate: Optional[int]) -> None: +//| """Allocate and initialize `ESPNow` instance as a singleton. //| +//| :param int buffer_size: The size of the internal ring buffer (length > 263 bytes). Default: 526 bytes. +//| :param int phy_rate: The ESP-NOW physical layer rate. Default 1 Mbps.""" +//| ... 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) { + enum { ARG_buffer_size, ARG_phy_rate }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buffer_size, MP_ARG_INT, { .u_int = DEFAULT_RECV_BUFFER_SIZE } }, + { MP_QSTR_phy_rate, MP_ARG_INT, { .u_int = WIFI_PHY_RATE_1M_L } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + espnow_obj_t *self = _get_singleton(); - if (self != NULL) { - return self; - } + if (self == NULL) { + self = m_new_obj(espnow_obj_t); + self->base.type = &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; - self->peers_table = mp_obj_new_dict(0); + _set_buffer_size(self, args[ARG_buffer_size].u_int); + _set_phy_rate(self, args[ARG_phy_rate].u_int); - // Prevent user code modifying the dict - mp_obj_dict_get_map(self->peers_table)->is_fixed = 1; + self->peers_table = mp_obj_new_dict(0); - // Set the global singleton pointer for the espnow protocol. - MP_STATE_PORT(espnow_singleton) = self; + // Prevent user code modifying the dict + mp_obj_dict_get_map(self->peers_table)->is_fixed = 1; + + // Set the global singleton pointer for the espnow protocol. + MP_STATE_PORT(espnow_singleton) = self; + } return self; } @@ -160,7 +169,7 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t // 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) { +static void send_cb(const uint8_t *mac, esp_now_send_status_t status) { espnow_obj_t *self = _get_singleton(); self->tx_responses++; if (status != ESP_NOW_SEND_SUCCESS) { @@ -168,58 +177,49 @@ static void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) { } } -static inline int8_t _get_rssi_from_wifi_pkt(const uint8_t *msg); +static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg); -// Callback triggered when an ESP-Now packet is received. +// 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) { +static void recv_cb(const uint8_t *mac, 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)) { + if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { self->rx_failures++; return; } - espnow_hdr_t header; + espnow_header_t header; header.magic = ESPNOW_MAGIC; header.msg_len = msg_len; - header.rssi = _get_rssi_from_wifi_pkt(msg); + header.rssi = _get_rssi_from_wifi_packet(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); + ringbuf_put_n(buf, (uint8_t *)&header, sizeof(header)); + ringbuf_put_n(buf, mac, ESP_NOW_ETH_ALEN); + ringbuf_put_n(buf, msg, msg_len); self->rx_packets++; } -static void _wifi_init(void) { - if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { - common_hal_wifi_init(false); - common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true); - } -} - -//| 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. -//| """ -//| ... -//| +// Initialize the 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 + if (espnow_deinited(self)) { 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); } - _wifi_init(); // Call the wifi init code + if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { + common_hal_wifi_init(false); + common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true); + } + + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, self->phy_rate)); + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, self->phy_rate)); check_esp_err(esp_now_init()); check_esp_err(esp_now_register_send_cb(send_cb)); @@ -227,13 +227,8 @@ static void espnow_init(espnow_obj_t *self) { } } -//| def deinit() -> None: -//| """ -//| De-initialise the ESP-NOW software stack, disable callbacks -//| and deallocate the recv data buffers. -//| """ -//| ... -//| +// De-initialize 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()); @@ -241,7 +236,7 @@ static void espnow_deinit(espnow_obj_t *self) { check_esp_err(esp_now_deinit()); self->recv_buffer->buf = NULL; self->recv_buffer = NULL; - self->num_peers = 0; // esp_now_deinit() removes all peers. + self->peers_count = 0; // esp_now_deinit() removes all peers. self->tx_packets = self->tx_responses; } } @@ -251,89 +246,94 @@ void espnow_reset(void) { 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. -//| """ -//| ... +// 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(mp_obj_t obj, size_t len, mp_uint_t rw) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(obj, &bufinfo, rw); + mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); + return (uint8_t *)bufinfo.buf; +} + +//| def set_pmk(self, pmk: bytes) -> None: +//| """Set the ESP-NOW Primary Master Key (pmk) for encrypted communications. //| -STATIC mp_obj_t espnow_active(size_t n_args, const mp_obj_t *args) { - espnow_obj_t *self = args[0]; - if (n_args > 1) { - if (mp_obj_is_true(args[1])) { - espnow_init(self); - } else { - espnow_deinit(self); - } - } - return mp_obj_new_bool(!espnow_deinited(self)); +//| :param bytes pmk: The ESP-NOW Primary Master Key (length = 16 bytes).""" +//| ... +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, MP_BUFFER_READ))); + return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_active_obj, 1, 2, espnow_active); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_pmk_obj, espnow_set_pmk); -//| 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) -//| """ -//| ... +//| active: bool +//| """Initialize or de-initialize the `ESPNow` communication protocol.""" //| -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} }, - { MP_QSTR_buffer, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { 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} }, - }; +STATIC mp_obj_t espnow_get_active(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(!espnow_deinited(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_active_obj, espnow_get_active); - 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); +STATIC mp_obj_t espnow_set_active(const mp_obj_t self_in, const mp_obj_t value) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_is_true(value) ? espnow_init(self) : espnow_deinit(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_active_obj, espnow_set_active); - if (args[ARG_buffer].u_int >= 0) { - self->recv_buffer_size = args[ARG_buffer].u_int; - } +MP_PROPERTY_GETSET(espnow_active_obj, + (mp_obj_t)&espnow_get_active_obj, + (mp_obj_t)&espnow_set_active_obj); - if (args[ARG_timeout].u_int >= 0) { - self->recv_timeout_ms = args[ARG_timeout].u_int; - } - if (args[ARG_rate].u_int >= 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)); - } +//| buffer_size: int +//| """The size of the internal ring buffer.""" +//| +STATIC mp_obj_t espnow_get_buffer_size(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->recv_buffer_size); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_buffer_size_obj, espnow_get_buffer_size); - if (args[ARG_get].u_obj == MP_OBJ_NULL) { - return mp_const_none; - } +STATIC mp_obj_t espnow_set_buffer_size(const mp_obj_t self_in, const mp_obj_t value) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + _set_buffer_size(self, mp_obj_get_int(value)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_buffer_size_obj, espnow_set_buffer_size); -#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; - if (name == QS(MP_QSTR_buffer)) { - return mp_obj_new_int(self->recv_buffer_size); - } else if (name == QS(MP_QSTR_timeout)) { - return mp_obj_new_int(self->recv_timeout_ms); - } else { - mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); - } -#undef QS +MP_PROPERTY_GETSET(espnow_buffer_size_obj, + (mp_obj_t)&espnow_get_buffer_size_obj, + (mp_obj_t)&espnow_set_buffer_size_obj); +//| phy_rate: int +//| """The ESP-NOW physical layer rate.""" +//| +STATIC mp_obj_t espnow_get_phy_rate(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->phy_rate); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_phy_rate_obj, espnow_get_phy_rate); + +STATIC mp_obj_t espnow_set_phy_rate(const mp_obj_t self_in, const mp_obj_t value) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + _set_phy_rate(self, mp_obj_get_int(value)); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_config_obj, 1, espnow_config); +MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_phy_rate_obj, espnow_set_phy_rate); + +MP_PROPERTY_GETSET(espnow_phy_rate_obj, + (mp_obj_t)&espnow_get_phy_rate_obj, + (mp_obj_t)&espnow_set_phy_rate_obj); //| 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). -//| """ +//| """Provide some useful stats in a `tuple` of +//| (tx_packets, tx_responses, tx_failures, rx_packets, rx_failures). (read-only)""" //| 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( + return MP_OBJ_NEW_TUPLE( mp_obj_new_int(self->tx_packets), mp_obj_new_int(self->tx_responses), mp_obj_new_int(self->tx_failures), @@ -346,26 +346,26 @@ 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 // - to return unique bytestrings for each peer which supports more efficient // application memory usage and peer handling. // Get the RSSI value from the wifi packet header -static inline int8_t _get_rssi_from_wifi_pkt(const uint8_t *msg) { +static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { // Warning: Secret magic to get the rssi from the wifi packet header // See espnow.c:espnow_recv_cb() at https://github.com/espressif/esp-now/ // In the wifi packet the msg comes after a wifi_promiscuous_pkt_t // and a espnow_frame_format_t. // Backtrack to get a pointer to the wifi_promiscuous_pkt_t. - static const size_t sizeof_espnow_frame_format = 39; + #define SIZEOF_ESPNOW_FRAME_FORMAT 39 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" - wifi_promiscuous_pkt_t *wifi_pkt = (wifi_promiscuous_pkt_t *)( - msg - sizeof_espnow_frame_format - sizeof(wifi_promiscuous_pkt_t)); + wifi_promiscuous_pkt_t *wifi_packet = (wifi_promiscuous_pkt_t *)( + msg - SIZEOF_ESPNOW_FRAME_FORMAT - sizeof(wifi_promiscuous_pkt_t)); #pragma GCC diagnostic pop - return wifi_pkt->rx_ctrl.rssi; + return wifi_packet->rx_ctrl.rssi; } // Lookup a peer in the peers table and return a reference to the item in the peers_table. @@ -388,54 +388,24 @@ static mp_map_elem_t *_lookup_add_peer(espnow_obj_t *self, const uint8_t *peer) return item; } -// Update the peers table with the new rssi value from a received pkt and +// Update the peers table with the new rssi value from a received packet and // return a reference to the item in the peers_table. -static mp_map_elem_t *_update_rssi(espnow_obj_t *self, const uint8_t *peer, int8_t rssi, uint32_t time_ms) { +static void _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); list->items[0] = MP_OBJ_NEW_SMALL_INT(rssi); list->items[1] = mp_obj_new_int(time_ms); - return item; } // --- Handling espnow packets in the recv buffer --- -// --- 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); - mp_arg_validate_length(bufinfo.len, len, MP_QSTR_bytes); - return (uint8_t *)bufinfo.buf; -} - -static uint8_t *_get_bytes_len(mp_obj_t obj, size_t len) { - return _get_bytes_len_rw(obj, len, MP_BUFFER_READ); -} - -static uint8_t *_get_bytes_len_w(mp_obj_t obj, size_t len) { - return _get_bytes_len_rw(obj, len, MP_BUFFER_WRITE); -} +// --- Send and Receive ESP-NOW data --- // 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; -} - -// Copy data from the ring buffer - wait if buffer is empty up to timeout_ms -static int ringbuf_read_wait(ringbuf_t *r, void *data, size_t len, int timeout_ms) { - int64_t end = mp_hal_ticks_ms() + timeout_ms; - int status = 0; - while ( - ((status = ringbuf_read(r, data, len)) == 0) && - (end - (int64_t)mp_hal_ticks_ms()) >= 0) { - RUN_BACKGROUND_TASKS; - } - return status; +// Raise ValueError if mac is wrong type or is not 6 bytes long. +static const uint8_t *_get_peer_addr(mp_obj_t mac) { + return mp_obj_is_true(mac) ? _get_bytes_len(mac, ESP_NOW_ETH_ALEN, MP_BUFFER_READ) : NULL; } // Used by espnow_send() for sends() with sync==True. @@ -454,57 +424,70 @@ static void _wait_for_pending_responses(espnow_obj_t *self) { } } -//| 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(). +//| def send( +//| self, +//| message: Union[bytearray, bytes, str], +//| mac: Optional[bytes], +//| sync: bool = True, +//| ) -> bool: +//| """Send a message to the peer's mac address. Optionally wait for a response. //| -//| 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 +//| :param Union[bytearray, bytes, str] message: The message to send (length < 250 bytes). +//| :param bytes mac: The peer's address (length = 6 bytes). If `None` or any non-true value, send to all registered peers. +//| :param bool sync: If `True`, wait for response from peer(s) after sending. //| -//| Raises: EAGAIN if the internal espnow buffers are full. -//| """ -//| ... +//| :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 //| -STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) { - espnow_obj_t *self = args[0]; - check_for_deinit(self); +//| :raises EAGAIN: if the internal espnow buffers are full.""" +//| ... +STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_message, ARG_mac, ARG_sync }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_message, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_mac, MP_ARG_OBJ, { .u_obj = mp_const_none } }, + { MP_QSTR_sync, MP_ARG_BOOL, { .u_bool = mp_const_true } }, + }; - // 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; - bool sync = n_args <= 3 || args[3] == mp_const_none || mp_obj_is_true(args[3]); + 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); - // Get a pointer to the data buffer of the message - mp_buffer_info_t message; - mp_get_buffer_raise(msg, &message, MP_BUFFER_READ); + espnow_obj_t *self = pos_args[0]; + check_for_deinit(self); + + const bool sync = mp_obj_is_true(args[ARG_sync].u_obj); if (sync) { // Flush out any pending responses. - // If the last call was sync==False there may be outstanding responses - // still to be received (possible many if we just had a burst of - // unsync send()s). We need to wait for all pending responses if this - // call has sync=True. + // If the last call was sync == False there may be outstanding responses + // still to be received (possible many if we just had a burst of unsync send()s). + // We need to wait for all pending responses if this call has sync = True. _wait_for_pending_responses(self); } - size_t saved_failures = self->tx_failures; + const uint8_t *peer_addr = _get_peer_addr(args[ARG_mac].u_obj); + + // Get a pointer to the data buffer of the message + mp_buffer_info_t message; + mp_get_buffer_raise(args[ARG_message].u_obj, &message, MP_BUFFER_READ); + // 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))) && + size_t saved_failures = self->tx_failures; + mp_uint_t start = mp_hal_ticks_ms(); + + while ((ESP_ERR_ESPNOW_NO_MEM == (err = esp_now_send(peer_addr, 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 + check_esp_err(err); + + // 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_addr == NULL) ? self->peers_count : 1); - // 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->num_peers : 1); if (sync) { // Wait for and tally all the expected responses from peers _wait_for_pending_responses(self); @@ -513,30 +496,25 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) { // 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); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); -//| 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. +//| def recv(self, buffers: List[bytearray]) -> int: +//| """Loads mac, message, rssi and timestamp into the provided buffers. //| -//| If buffers is 2 elements long, the peer_mac and message will be +//| If buffers is 2 elements long, the 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. -//| """ -//| ... +//| :param List[bytearray] buffers: List of buffers to be loaded. //| -STATIC mp_obj_t espnow_recv(size_t n_args, const mp_obj_t *args) { - espnow_obj_t *self = args[0]; +//| :returns: Length of the message.""" +//| ... +STATIC mp_obj_t espnow_recv(mp_obj_t self_in, mp_obj_t buffers) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); - 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]); + mp_obj_list_t *list = MP_OBJ_TO_PTR(buffers); if (!mp_obj_is_type(list, &mp_type_list) || list->len < 2) { mp_arg_error_invalid(MP_QSTR_buffers); } @@ -547,21 +525,22 @@ STATIC mp_obj_t espnow_recv(size_t n_args, const mp_obj_t *args) { 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); + uint8_t *peer_buf = _get_bytes_len(list->items[0], ESP_NOW_ETH_ALEN, MP_BUFFER_WRITE); + uint8_t *msg_buf = _get_bytes_len(msg, ESP_NOW_MAX_DATA_LEN, MP_BUFFER_WRITE); // 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 + espnow_header_t header; + if (!ringbuf_get_n(self->recv_buffer, (uint8_t *)&header, sizeof(header))) { + return MP_OBJ_NEW_SMALL_INT(0); } - int msg_len = hdr.msg_len; + + uint8_t msg_len = header.msg_len; // Check the message packet header format and read the message data - if (hdr.magic != ESPNOW_MAGIC || + if (header.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) { + !ringbuf_get_n(self->recv_buffer, peer_buf, ESP_NOW_ETH_ALEN) || + !ringbuf_get_n(self->recv_buffer, msg_buf, msg_len)) { mp_arg_error_invalid(MP_QSTR_buffer); } if (mp_obj_is_type(msg, &mp_type_bytearray)) { @@ -572,79 +551,87 @@ STATIC mp_obj_t espnow_recv(size_t n_args, const mp_obj_t *args) { } // 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); + _update_rssi(self, peer_buf, header.rssi, header.time_ms); + if (list->len == 4) { + list->items[2] = MP_OBJ_NEW_SMALL_INT(header.rssi); + list->items[3] = mp_obj_new_int(header.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); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_recv_obj, espnow_recv); -//| def any(self) -> bool: -//| """Test if data is available to read from the buffers. -//| """ -//| ... +//| any: bool +//| """`True` if data is available to read from the buffers.""" //| -STATIC mp_obj_t espnow_any(const mp_obj_t self_in) { +STATIC mp_obj_t espnow_get_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; + + return ringbuf_num_filled(self->recv_buffer) ? mp_const_true : mp_const_false; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_any_obj, espnow_any); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_any_obj, espnow_get_any); -// --- Peer Management Functions --- +MP_PROPERTY_GETTER(espnow_any_obj, + (mp_obj_t)&espnow_get_any_obj); -//| 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); +// --- Peer Management Functions --- -// 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 }; +// Common code for add_peer() and mod_peer() to process the args. +static void _update_peer_info(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, bool modify) { + enum { ARG_mac, ARG_lmk, ARG_channel, ARG_interface, 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_mac, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_lmk, MP_ARG_OBJ, { .u_obj = mp_const_none } }, + { MP_QSTR_channel, MP_ARG_INT, { .u_obj = mp_const_none } }, + { MP_QSTR_interface,MP_ARG_INT, { .u_obj = mp_const_none } }, + { MP_QSTR_encrypt, MP_ARG_BOOL,{ .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); - - 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); + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + esp_now_peer_info_t peer = {0}; + memcpy(peer.peer_addr, _get_peer_addr(args[ARG_mac].u_obj), ESP_NOW_ETH_ALEN); + + if (modify) { + check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); + } else { + if (esp_now_is_peer_exist(peer.peer_addr)) { + mp_raise_RuntimeError(translate("peer already exists")); } + peer.channel = 0; + peer.ifidx = WIFI_IF_STA; + peer.encrypt = false; + } + + const mp_obj_t channel = args[ARG_channel].u_obj; + if (channel != mp_const_none) { + peer.channel = mp_arg_validate_int_range(mp_obj_get_int(channel), 1, 14, MP_QSTR_channel); } - if (args[ARG_channel].u_obj != mp_const_none) { - peer->channel = mp_obj_get_int(args[ARG_channel].u_obj); + const mp_obj_t interface = args[ARG_interface].u_obj; + if (interface != mp_const_none) { + peer.ifidx = (wifi_interface_t)mp_arg_validate_int_range(mp_obj_get_int(interface), 0, 1, MP_QSTR_interface); } - if (args[ARG_ifidx].u_obj != mp_const_none) { - peer->ifidx = mp_obj_get_int(args[ARG_ifidx].u_obj); + const mp_obj_t encrypt = args[ARG_encrypt].u_obj; + if (encrypt != mp_const_none) { + peer.encrypt = mp_obj_is_true(encrypt); } - if (args[ARG_encrypt].u_obj != mp_const_none) { - peer->encrypt = mp_obj_is_true(args[ARG_encrypt].u_obj); + const mp_obj_t lmk = args[ARG_lmk].u_obj; + if (lmk != mp_const_none) { + memcpy(peer.lmk, _get_bytes_len(lmk, ESP_NOW_KEY_LEN, MP_BUFFER_READ), ESP_NOW_KEY_LEN); + } else if (peer.encrypt) { + mp_raise_ValueError_varg(translate("%q is %q"), MP_QSTR_lmk, MP_QSTR_None); } + + check_esp_err((modify) ? esp_now_mod_peer(&peer) : esp_now_add_peer(&peer)); } -// Update the cached peer count in self->num_peers; -// The num_peers ignores broadcast and multicast addresses and is used for the +// Update the cached peer count in self->peers_count; +// The peers_count 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(espnow_obj_t *self) { esp_now_peer_info_t peer = {0}; @@ -659,71 +646,73 @@ static void _update_peer_count(espnow_obj_t *self) { } } - self->num_peers = count; + self->peers_count = count; } -// 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. -//| """ -//| ... +//| def add_peer( +//| self, +//| mac: bytes, +//| lmk: Optional[bytes], +//| channel: int = 0, +//| interface: int = 0, +//| encrypt: bool = False, +//| ) -> None: +//| """Add peer. //| -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]; +//| :param bytes mac: The mac address of the peer. +//| :param bytes lmk: The Local Master Key (lmk) of the peer. +//| :param int channel: The peer's channel. Default: 0 ie. use the current channel. +//| :param int interface: The WiFi interface to use. Default: 0 ie. STA. +//| :param bool encrypt: Whether or not to use encryption.""" +//| ... +STATIC mp_obj_t espnow_add_peer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + espnow_obj_t *self = pos_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); - check_esp_err(esp_now_add_peer(&peer)); + _update_peer_info(n_args, pos_args, kw_args, false); _update_peer_count(self); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_add_peer_obj, 2, espnow_add_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. -//| """ -//| ... +//| def mod_peer( +//| self, +//| mac: bytes, +//| lmk: Optional[bytes], +//| channel: int = 0, +//| interface: int = 0, +//| encrypt: bool = False, +//| ) -> None: +//| """Modify peer. //| -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]; +//| :param bytes mac: The mac address of the peer. +//| :param bytes lmk: The Local Master Key (lmk) of the peer. +//| :param int channel: The peer's channel. Default: 0 ie. use the current channel. +//| :param int interface: The WiFi interface to use. Default: 0 ie. STA. +//| :param bool encrypt: Whether or not to use encryption.""" +//| ... +STATIC mp_obj_t espnow_mod_peer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + espnow_obj_t *self = pos_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); + _update_peer_info(n_args, pos_args, kw_args, true); 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. -//| """ -//| ... +//| def del_peer(self, mac: bytes) -> None: +//| """Delete peer. //| -STATIC mp_obj_t espnow_del_peer(mp_obj_t self_in, mp_obj_t peer) { +//| :param bytes mac: The mac address of the peer.""" +//| ... +STATIC mp_obj_t espnow_del_peer(mp_obj_t self_in, mp_obj_t mac) { 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); + memcpy(peer_addr, _get_peer_addr(mac), ESP_NOW_ETH_ALEN); check_esp_err(esp_now_del_peer(peer_addr)); _update_peer_count(self); @@ -735,43 +724,46 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_del_peer_obj, espnow_del_peer); // Convert a peer_info struct to python tuple // Used by espnow_get_peer() and espnow_get_peers() static mp_obj_t _peer_info_to_tuple(const esp_now_peer_info_t *peer) { - return NEW_TUPLE( + return MP_OBJ_NEW_TUPLE( mp_obj_new_bytes(peer->peer_addr, MP_ARRAY_SIZE(peer->peer_addr)), mp_obj_new_bytes(peer->lmk, MP_ARRAY_SIZE(peer->lmk)), mp_obj_new_int(peer->channel), mp_obj_new_int(peer->ifidx), - (peer->encrypt) ? mp_const_true : mp_const_false); + mp_obj_new_bool(peer->encrypt)); } -//| 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). -//| """ -//| ... +//| def get_peer(self, mac: bytes) -> Tuple[bytes, int, int, bool]: +//| """Get the peer info for mac as a `tuple`. //| -STATIC mp_obj_t espnow_get_peer(mp_obj_t self_in, mp_obj_t peer_mac) { +//| :param bytes mac: The mac address of the peer. +//| +//| :returns: A `tuple` of (mac, lmk, channel, interface, encrypt).""" +//| ... +STATIC mp_obj_t espnow_get_peer(mp_obj_t self_in, mp_obj_t mac) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer(peer_mac), ESP_NOW_ETH_ALEN); + memcpy(peer.peer_addr, _get_peer_addr(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), ...) -//| """ -//| ... +// --- Peer Related Properties --- + +//| peers: Tuple[Tuple[bytes, bytes, int, int, bool], ...] +//| """The peer info records for all registered `ESPNow` peers. (read-only) +//| +//| A `tuple` of tuples: ((mac, lmk, channel, interface, 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->num_peers, NULL); + // Build and initialize the peer info tuple. + mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peers_count, NULL); esp_now_peer_info_t peer = {0}; for (size_t i = 0; i < peerinfo_tuple->len; i++) { @@ -783,25 +775,52 @@ STATIC mp_obj_t espnow_get_peers(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers); -//| num_peers: Tuple[int, int] -//| """The number of registered peers in a tuple of (num_total_peers, num_encrypted_peers). (read-only) -//| """ +MP_PROPERTY_GETTER(espnow_peers_obj, + (mp_obj_t)&espnow_get_peers_obj); + +//| peers_count: 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) { +STATIC mp_obj_t espnow_get_peers_count(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + esp_now_peer_num_t peer_num = {0}; check_esp_err(esp_now_get_peer_num(&peer_num)); - return NEW_TUPLE( + return MP_OBJ_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_get_num_peers_obj, espnow_get_num_peers); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_count_obj, espnow_get_peers_count); + +MP_PROPERTY_GETTER(espnow_peers_count_obj, + (mp_obj_t)&espnow_get_peers_count_obj); + +//| peers_table: Dict[bytes, List[int]] +//| """The dictionary of peers we have seen. (read-only) +//| +//| A `dict` of {peer: [rssi, time], ...} +//| +//| where: +//| peer 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 mp_obj_t espnow_get_peers_table(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return self->peers_table; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_table_obj, espnow_get_peers_table); -MP_PROPERTY_GETTER(espnow_num_peers_obj, - (mp_obj_t)&espnow_get_num_peers_obj); +MP_PROPERTY_GETTER(espnow_peers_table_obj, + (mp_obj_t)&espnow_get_peers_table_obj); STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { + // Config parameters + { MP_ROM_QSTR(MP_QSTR_set_pmk), MP_ROM_PTR(&espnow_set_pmk_obj) }, { 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_buffer_size), MP_ROM_PTR(&espnow_buffer_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_phy_rate), MP_ROM_PTR(&espnow_phy_rate_obj) }, { MP_ROM_QSTR(MP_QSTR_stats), MP_ROM_PTR(&espnow_stats_obj) }, // Send and receive messages @@ -810,13 +829,15 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { { 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_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) }, + + // Peer related properties + { MP_ROM_QSTR(MP_QSTR_peers), MP_ROM_PTR(&espnow_peers_obj) }, + { MP_ROM_QSTR(MP_QSTR_peers_count), MP_ROM_PTR(&espnow_peers_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_peers_table), MP_ROM_PTR(&espnow_peers_table_obj) }, }; STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_dict_table); @@ -831,10 +852,10 @@ STATIC mp_uint_t espnow_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintpt } espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return (espnow_deinited(self)) ? 0 : // If not initialised + return (espnow_deinited(self)) ? 0 : // If not initialized arg ^ ( // If no data in the buffer, unset the Read ready flag - ((ringbuf_avail(self->recv_buffer) == 0) ? MP_STREAM_POLL_RD : 0) | + ((!ringbuf_num_filled(self->recv_buffer)) ? MP_STREAM_POLL_RD : 0) | // If still waiting for responses, unset the Write ready flag ((self->tx_responses < self->tx_packets) ? MP_STREAM_POLL_WR : 0)); } @@ -843,30 +864,11 @@ STATIC const mp_stream_p_t espnow_stream_p = { .ioctl = espnow_stream_ioctl, }; -// 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) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (dest[0] != MP_OBJ_NULL) { // Only allow "Load" operation - return; - } - if (attr == MP_QSTR_peers_table) { - dest[0] = self->peers_table; - return; - } - dest[1] = MP_OBJ_SENTINEL; // Attribute not found -} - const mp_obj_type_t espnow_type = { { &mp_type_type }, - .name = MP_QSTR_NOW, + .name = MP_QSTR_ESPNow, .make_new = espnow_make_new, .locals_dict = (mp_obj_t)&espnow_locals_dict, - .attr = espnow_attr, .flags = MP_TYPE_FLAG_EXTENDED, MP_TYPE_EXTENDED_FIELDS( .protocol = &espnow_stream_p, diff --git a/ports/espressif/bindings/espnow/Now.h b/ports/espressif/bindings/espnow/ESPNow.h similarity index 100% rename from ports/espressif/bindings/espnow/Now.h rename to ports/espressif/bindings/espnow/ESPNow.h diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c index 52325dad5811..56618859116f 100644 --- a/ports/espressif/bindings/espnow/__init__.c +++ b/ports/espressif/bindings/espnow/__init__.c @@ -27,7 +27,7 @@ #include "shared-bindings/util.h" #include "bindings/espnow/__init__.h" -#include "bindings/espnow/Now.h" +#include "bindings/espnow/ESPNow.h" //| """ESP-NOW Module //| @@ -36,7 +36,9 @@ //| protocol provided by Espressif on its SoCs //| (`API docs `_). //| -//| **Sender:** :: +//| **Sender** +//| +//| .. code-block:: python //| //| import espnow //| @@ -50,7 +52,9 @@ //| e.send(peer, str(i)*20, True) //| e.send(b'end') //| -//| **Receiver:** :: +//| **Receiver** +//| +//| .. code-block:: python //| //| import espnow //| @@ -73,7 +77,7 @@ 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) }, + { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&espnow_type) }, }; STATIC MP_DEFINE_CONST_DICT(espnow_module_globals, espnow_module_globals_table); diff --git a/py/objtuple.h b/py/objtuple.h index 7bfb447fa4c5..ded265b47e01 100644 --- a/py/objtuple.h +++ b/py/objtuple.h @@ -50,6 +50,9 @@ mp_obj_t mp_obj_tuple_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf); extern const mp_obj_type_t mp_type_attrtuple; +// Relies on gcc Variadic Macros and Statement Expressions +#define MP_OBJ_NEW_TUPLE(...) ({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z);}) + #define MP_DEFINE_ATTRTUPLE(tuple_obj_name, fields, nitems, ...) \ const mp_rom_obj_tuple_t tuple_obj_name = { \ .base = {&mp_type_attrtuple}, \ diff --git a/py/ringbuf.c b/py/ringbuf.c index f90805d6f324..5936b6423062 100644 --- a/py/ringbuf.c +++ b/py/ringbuf.c @@ -72,7 +72,6 @@ int ringbuf_get16(ringbuf_t *r) { if (r->used < 2) { return -1; } - int high_byte = ringbuf_get(r); int low_byte = ringbuf_get(r); return (high_byte << 8) | low_byte; @@ -92,6 +91,15 @@ int ringbuf_put(ringbuf_t *r, uint8_t v) { return 0; } +int ringbuf_put16(ringbuf_t *r, uint16_t v) { + if (r->size - r->used < 2) { + return -1; + } + ringbuf_put(r, (v >> 8) & 0xff); + ringbuf_put(r, v & 0xff); + return 0; +} + void ringbuf_clear(ringbuf_t *r) { r->next_write = 0; r->next_read = 0; @@ -132,57 +140,3 @@ size_t ringbuf_get_n(ringbuf_t *r, uint8_t *buf, size_t bufsize) { } return bufsize; } - -int ringbuf_put16(ringbuf_t *r, uint16_t v) { - if (r->size - r->used < 2) { - return -1; - } - - ringbuf_put(r, (v >> 8) & 0xff); - ringbuf_put(r, v & 0xff); - return 0; -} - -// Returns: -// 1: Success -// 0: Not enough data available to complete read (try again later) -// -1: Requested read is larger than buffer - will never succeed -int ringbuf_read(ringbuf_t *r, void *data, size_t data_len) { - if (ringbuf_avail(r) < data_len) { - return (r->size <= data_len) ? -1 : 0; - } - uint32_t iget = r->next_read; - uint32_t iget_a = (iget + data_len) % r->size; - uint8_t *datap = data; - if (iget_a < iget) { - // Copy part of the data from the space left at the end of the buffer - memcpy(datap, r->buf + iget, r->size - iget); - datap += (r->size - iget); - iget = 0; - } - memcpy(datap, r->buf + iget, iget_a - iget); - r->next_read = iget_a; - return 1; -} - -// Returns: -// 1: Success -// 0: Not enough free space available to complete write (try again later) -// -1: Requested write is larger than buffer - will never succeed -int ringbuf_write(ringbuf_t *r, const void *data, size_t data_len) { - if (ringbuf_free(r) < data_len) { - return (r->size <= data_len) ? -1 : 0; - } - uint32_t iput = r->next_write; - uint32_t iput_a = (iput + data_len) % r->size; - const uint8_t *datap = data; - if (iput_a < iput) { - // Copy part of the data to the end of the buffer - memcpy(r->buf + iput, datap, r->size - iput); - datap += (r->size - iput); - iput = 0; - } - memcpy(r->buf + iput, datap, iput_a - iput); - r->next_write = iput_a; - return 1; -} diff --git a/py/ringbuf.h b/py/ringbuf.h index 5d54abc6b5f0..2725bedccaff 100644 --- a/py/ringbuf.h +++ b/py/ringbuf.h @@ -61,15 +61,5 @@ size_t ringbuf_get_n(ringbuf_t *r, uint8_t *buf, size_t bufsize); // Note: big-endian. Return -1 if can't read or write two bytes. int ringbuf_get16(ringbuf_t *r); int ringbuf_put16(ringbuf_t *r, uint16_t v); -int ringbuf_read(ringbuf_t *r, void *data, size_t data_len); -int ringbuf_write(ringbuf_t *r, const void *data, size_t data_len); - -static inline size_t ringbuf_free(ringbuf_t *r) { - return (r->size + r->next_read - r->next_write - 1) % r->size; -} - -static inline size_t ringbuf_avail(ringbuf_t *r) { - return (r->size + r->next_write - r->next_read) % r->size; -} #endif // MICROPY_INCLUDED_PY_RINGBUF_H From 9b98d485bb40fb316987427ab4c89b06e68d320b Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Thu, 26 Jan 2023 14:13:38 +0530 Subject: [PATCH 05/17] switch to `ReadableBuffer` and `WriteableBuffer` --- ports/espressif/bindings/espnow/ESPNow.c | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 0bcd7d39ed04..b67574360ca2 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -255,10 +255,10 @@ static uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { return (uint8_t *)bufinfo.buf; } -//| def set_pmk(self, pmk: bytes) -> None: +//| def set_pmk(self, pmk: ReadableBuffer) -> None: //| """Set the ESP-NOW Primary Master Key (pmk) for encrypted communications. //| -//| :param bytes pmk: The ESP-NOW Primary Master Key (length = 16 bytes).""" +//| :param ReadableBuffer pmk: The ESP-NOW Primary Master Key (length = 16 bytes).""" //| ... 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, MP_BUFFER_READ))); @@ -426,14 +426,14 @@ static void _wait_for_pending_responses(espnow_obj_t *self) { //| def send( //| self, -//| message: Union[bytearray, bytes, str], -//| mac: Optional[bytes], +//| message: ReadableBuffer, +//| mac: Optional[ReadableBuffer], //| sync: bool = True, //| ) -> bool: //| """Send a message to the peer's mac address. Optionally wait for a response. //| -//| :param Union[bytearray, bytes, str] message: The message to send (length < 250 bytes). -//| :param bytes mac: The peer's address (length = 6 bytes). If `None` or any non-true value, send to all registered peers. +//| :param ReadableBuffer message: The message to send (length <= 250 bytes). +//| :param ReadableBuffer mac: The peer's address (length = 6 bytes). If `None` or any non-true value, send to all registered peers. //| :param bool sync: If `True`, wait for response from peer(s) after sending. //| //| :returns: @@ -498,7 +498,7 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); -//| def recv(self, buffers: List[bytearray]) -> int: +//| def recv(self, buffers: List[WriteableBuffer]) -> int: //| """Loads mac, message, rssi and timestamp into the provided buffers. //| //| If buffers is 2 elements long, the mac and message will be @@ -506,7 +506,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); //| If buffers is 4 elements long, the rssi and timestamp values will be //| loaded into the 3rd and 4th elements. //| -//| :param List[bytearray] buffers: List of buffers to be loaded. +//| :param List[WriteableBuffer] buffers: List of buffers to be loaded. //| //| :returns: Length of the message.""" //| ... @@ -651,16 +651,16 @@ static void _update_peer_count(espnow_obj_t *self) { //| def add_peer( //| self, -//| mac: bytes, -//| lmk: Optional[bytes], +//| mac: ReadableBuffer, +//| lmk: Optional[ReadableBuffer], //| channel: int = 0, //| interface: int = 0, //| encrypt: bool = False, //| ) -> None: //| """Add peer. //| -//| :param bytes mac: The mac address of the peer. -//| :param bytes lmk: The Local Master Key (lmk) of the peer. +//| :param ReadableBuffer mac: The mac address of the peer. +//| :param ReadableBuffer lmk: The Local Master Key (lmk) of the peer. //| :param int channel: The peer's channel. Default: 0 ie. use the current channel. //| :param int interface: The WiFi interface to use. Default: 0 ie. STA. //| :param bool encrypt: Whether or not to use encryption.""" @@ -678,16 +678,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_add_peer_obj, 2, espnow_add_peer); //| def mod_peer( //| self, -//| mac: bytes, -//| lmk: Optional[bytes], +//| mac: ReadableBuffer, +//| lmk: Optional[ReadableBuffer], //| channel: int = 0, //| interface: int = 0, //| encrypt: bool = False, //| ) -> None: //| """Modify peer. //| -//| :param bytes mac: The mac address of the peer. -//| :param bytes lmk: The Local Master Key (lmk) of the peer. +//| :param ReadableBuffer mac: The mac address of the peer. +//| :param ReadableBuffer lmk: The Local Master Key (lmk) of the peer. //| :param int channel: The peer's channel. Default: 0 ie. use the current channel. //| :param int interface: The WiFi interface to use. Default: 0 ie. STA. //| :param bool encrypt: Whether or not to use encryption.""" @@ -702,10 +702,10 @@ STATIC mp_obj_t espnow_mod_peer(size_t n_args, const mp_obj_t *pos_args, mp_map_ } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_mod_peer_obj, 2, espnow_mod_peer); -//| def del_peer(self, mac: bytes) -> None: +//| def del_peer(self, mac: ReadableBuffer) -> None: //| """Delete peer. //| -//| :param bytes mac: The mac address of the peer.""" +//| :param ReadableBuffer mac: The mac address of the peer.""" //| ... STATIC mp_obj_t espnow_del_peer(mp_obj_t self_in, mp_obj_t mac) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -732,10 +732,10 @@ static mp_obj_t _peer_info_to_tuple(const esp_now_peer_info_t *peer) { mp_obj_new_bool(peer->encrypt)); } -//| def get_peer(self, mac: bytes) -> Tuple[bytes, int, int, bool]: +//| def get_peer(self, mac: ReadableBuffer) -> Tuple[bytes, int, int, bool]: //| """Get the peer info for mac as a `tuple`. //| -//| :param bytes mac: The mac address of the peer. +//| :param ReadableBuffer mac: The mac address of the peer. //| //| :returns: A `tuple` of (mac, lmk, channel, interface, encrypt).""" //| ... From 9c430d45d15ef2f3a63459d12831343e0e45677a Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:27:55 +0530 Subject: [PATCH 06/17] use truthiness of the object itself instead of `any` --- ports/espressif/bindings/espnow/ESPNow.c | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index b67574360ca2..d50c41c7c70a 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -561,20 +561,6 @@ STATIC mp_obj_t espnow_recv(mp_obj_t self_in, mp_obj_t buffers) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_recv_obj, espnow_recv); -//| any: bool -//| """`True` if data is available to read from the buffers.""" -//| -STATIC mp_obj_t espnow_get_any(const mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_for_deinit(self); - - return ringbuf_num_filled(self->recv_buffer) ? mp_const_true : mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_any_obj, espnow_get_any); - -MP_PROPERTY_GETTER(espnow_any_obj, - (mp_obj_t)&espnow_get_any_obj); - // --- Peer Management Functions --- // Common code for add_peer() and mod_peer() to process the args. @@ -826,7 +812,6 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { // Send and receive messages { 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_add_peer), MP_ROM_PTR(&espnow_add_peer_obj) }, @@ -864,6 +849,27 @@ STATIC const mp_stream_p_t espnow_stream_p = { .ioctl = espnow_stream_ioctl, }; +//| def __bool__(self) -> bool: +//| """``True`` if `len()` is greater than zero. +//| This is an easy way to check if the buffer is empty. +//| """ +//| ... +//| def __len__(self) -> int: +//| """Return the number of `bytes` available to read. Used to implement ``len()``.""" +//| ... +STATIC mp_obj_t espnow_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + size_t len = ringbuf_num_filled(self->recv_buffer); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(len != 0); + case MP_UNARY_OP_LEN: + return mp_obj_new_int_from_uint(len); + default: + return MP_OBJ_NULL; // op not supported + } +} + const mp_obj_type_t espnow_type = { { &mp_type_type }, .name = MP_QSTR_ESPNow, @@ -872,5 +878,6 @@ const mp_obj_type_t espnow_type = { .flags = MP_TYPE_FLAG_EXTENDED, MP_TYPE_EXTENDED_FIELDS( .protocol = &espnow_stream_p, + .unary_op = &espnow_unary_op ), }; From ae4bb75e295ac6f598ccc7802d446f67691cddfe Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:54:51 +0530 Subject: [PATCH 07/17] split espnow between bindings and common-hal --- locale/circuitpython.pot | 6 + ports/espressif/Makefile | 4 +- ports/espressif/bindings/espnow/ESPNow.c | 341 ++---------------- .../espressif/bindings/espnow/ESPNowPacket.c | 70 ++++ .../espressif/bindings/espnow/ESPNowPacket.h | 30 ++ ports/espressif/bindings/espnow/__init__.c | 7 +- ports/espressif/common-hal/espnow/ESPNow.c | 327 +++++++++++++++++ ports/espressif/common-hal/espnow/ESPNow.h | 57 +++ 8 files changed, 535 insertions(+), 307 deletions(-) create mode 100644 ports/espressif/bindings/espnow/ESPNowPacket.c create mode 100644 ports/espressif/bindings/espnow/ESPNowPacket.h create mode 100644 ports/espressif/common-hal/espnow/ESPNow.c create mode 100644 ports/espressif/common-hal/espnow/ESPNow.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index cca9e1b0670f..1b4438308610 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -491,6 +491,7 @@ msgstr "" msgid "Already have all-matches listener" msgstr "" +#: ports/espressif/bindings/espnow/ESPNow.c #: ports/espressif/common-hal/espulp/ULP.c #: shared-module/memorymonitor/AllocationAlarm.c #: shared-module/memorymonitor/AllocationSize.c @@ -2431,6 +2432,7 @@ msgid "addresses is empty" msgstr "" #: ports/espressif/bindings/espnow/ESPNow.c +#: ports/espressif/common-hal/espnow/ESPNow.c msgid "an error occured" msgstr "" @@ -3818,6 +3820,10 @@ msgstr "" msgid "peer already exists" msgstr "" +#: ports/espressif/bindings/espnow/ESPNow.c +msgid "peer not found" +msgstr "" + #: shared-bindings/bitmaptools/__init__.c shared-bindings/displayio/Bitmap.c msgid "pixel coordinates out of bounds" msgstr "" diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index 92e91e808a8a..76d443dd6846 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -260,7 +260,9 @@ CFLAGS += -isystem esp32-camera/conversions/include endif ifneq ($(CIRCUITPY_ESPNOW),0) -SRC_ESPNOW := $(wildcard bindings/espnow/*.c) +SRC_ESPNOW := \ + $(wildcard common-hal/espnow/*.c) \ + $(wildcard bindings/espnow/*.c) SRC_C += $(SRC_ESPNOW) endif diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index d50c41c7c70a..b120b2cdffba 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -41,57 +41,8 @@ #include "bindings/espnow/ESPNow.h" #include "shared-bindings/util.h" -#include "shared-bindings/wifi/__init__.h" - -#define ESPNOW_MAGIC 0x99 - -// The maximum length of an espnow packet (bytes) -#define MAX_PACKET_LEN (sizeof(espnow_packet_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 - uint32_t time_ms; // Timestamp (ms) when packet is received - int8_t rssi; // RSSI value (dBm) (-127 to 0) -} __attribute__((packed)) espnow_header_t; - -typedef struct { - espnow_header_t header; // The header - uint8_t peer[6]; // Peer address - uint8_t msg[0]; // Message is up to 250 bytes -} __attribute__((packed)) espnow_packet_t; - -// The data structure for the espnow_singleton. -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 - wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. - volatile size_t rx_packets; // # of received packets - 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 peers_count; // Cache the # of peers for send(sync=True) - mp_obj_t peers_table; // A dictionary of discovered peers -} espnow_obj_t; + +#include "common-hal/espnow/ESPNow.h" static void check_esp_err(esp_err_t status) { if (status != ESP_OK) { @@ -101,154 +52,59 @@ static void check_esp_err(esp_err_t status) { // --- Initialisation and Config functions --- -// Return a pointer to the ESPNow module singleton -static espnow_obj_t *_get_singleton(void) { - return MP_STATE_PORT(espnow_singleton); -} - -static bool espnow_deinited(espnow_obj_t *self) { - return self->recv_buffer == NULL; -} - static void check_for_deinit(espnow_obj_t *self) { - if (espnow_deinited(self)) { + if (common_hal_espnow_deinited(self)) { raise_deinited_error(); } } -static void _set_buffer_size(espnow_obj_t *self, mp_int_t value) { - self->recv_buffer_size = mp_arg_validate_int_min(value, MAX_PACKET_LEN, MP_QSTR_buffer_size); -}; - -static void _set_phy_rate(espnow_obj_t *self, mp_int_t value) { - self->phy_rate = mp_arg_validate_int_range(value, 0, WIFI_PHY_RATE_MAX - 1, MP_QSTR_phy_rate); -}; - //| class ESPNow: //| """Provides access to the ESP-NOW protocol.""" //| //| def __init__(self, buffer_size: Optional[int], phy_rate: Optional[int]) -> None: //| """Allocate and initialize `ESPNow` instance as a singleton. //| -//| :param int buffer_size: The size of the internal ring buffer (length > 263 bytes). Default: 526 bytes. -//| :param int phy_rate: The ESP-NOW physical layer rate. Default 1 Mbps.""" +//| :param int buffer_size: The size of the internal ring buffer. Default: 526 bytes. +//| :param int phy_rate: The ESP-NOW physical layer rate. Default: 1 Mbps.""" //| ... 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) { enum { ARG_buffer_size, ARG_phy_rate }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_buffer_size, MP_ARG_INT, { .u_int = DEFAULT_RECV_BUFFER_SIZE } }, + { MP_QSTR_buffer_size, MP_ARG_INT, { .u_int = 526 } }, { MP_QSTR_phy_rate, MP_ARG_INT, { .u_int = WIFI_PHY_RATE_1M_L } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - espnow_obj_t *self = _get_singleton(); + espnow_obj_t *self = MP_STATE_PORT(espnow_singleton); - if (self == NULL) { - self = m_new_obj(espnow_obj_t); - self->base.type = &espnow_type; - - _set_buffer_size(self, args[ARG_buffer_size].u_int); - _set_phy_rate(self, args[ARG_phy_rate].u_int); - - 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; - - // Set the global singleton pointer for the espnow protocol. - MP_STATE_PORT(espnow_singleton) = self; + if (self != NULL) { + mp_raise_RuntimeError(translate("Already running")); } - return self; -} + self = m_new_obj(espnow_obj_t); + self->base.type = &espnow_type; -// --- The ESP-NOW send and recv callback routines --- + common_hal_espnow_set_buffer_size(self, args[ARG_buffer_size].u_int); + common_hal_espnow_set_phy_rate(self, args[ARG_phy_rate].u_int); -// 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, esp_now_send_status_t status) { - espnow_obj_t *self = _get_singleton(); - self->tx_responses++; - if (status != ESP_NOW_SEND_SUCCESS) { - self->tx_failures++; - } -} + self->peers_table = mp_obj_new_dict(0); -static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg); + // Prevent user code modifying the dict + mp_obj_dict_get_map(self->peers_table)->is_fixed = 1; -// 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. -static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { - espnow_obj_t *self = _get_singleton(); - ringbuf_t *buf = self->recv_buffer; + // Set the global singleton pointer for the espnow protocol. + MP_STATE_PORT(espnow_singleton) = self; - if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { - self->rx_failures++; - return; - } + common_hal_espnow_init(self); - espnow_header_t header; - header.magic = ESPNOW_MAGIC; - header.msg_len = msg_len; - header.rssi = _get_rssi_from_wifi_packet(msg); - header.time_ms = mp_hal_ticks_ms(); - - ringbuf_put_n(buf, (uint8_t *)&header, sizeof(header)); - ringbuf_put_n(buf, mac, ESP_NOW_ETH_ALEN); - ringbuf_put_n(buf, msg, msg_len); - - self->rx_packets++; -} - -// Initialize the ESP-NOW software stack, -// register callbacks and allocate the recv data buffers. -static void espnow_init(espnow_obj_t *self) { - if (espnow_deinited(self)) { - 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); - } - - if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { - common_hal_wifi_init(false); - common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true); - } - - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, self->phy_rate)); - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, self->phy_rate)); - - check_esp_err(esp_now_init()); - check_esp_err(esp_now_register_send_cb(send_cb)); - check_esp_err(esp_now_register_recv_cb(recv_cb)); - } -} - -// De-initialize 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->peers_count = 0; // esp_now_deinit() removes all peers. - self->tx_packets = self->tx_responses; - } -} - -void espnow_reset(void) { - espnow_deinit(_get_singleton()); - MP_STATE_PORT(espnow_singleton) = NULL; + return self; } // 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(mp_obj_t obj, size_t len, mp_uint_t rw) { +static const uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(obj, &bufinfo, rw); mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); @@ -261,7 +117,8 @@ static uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { //| :param ReadableBuffer pmk: The ESP-NOW Primary Master Key (length = 16 bytes).""" //| ... 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, MP_BUFFER_READ))); + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_set_pmk(self, _get_bytes_len(key, ESP_NOW_KEY_LEN, MP_BUFFER_READ)); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_pmk_obj, espnow_set_pmk); @@ -271,13 +128,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_pmk_obj, espnow_set_pmk); //| STATIC mp_obj_t espnow_get_active(const mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(!espnow_deinited(self)); + return mp_obj_new_bool(!common_hal_espnow_deinited(self)); } MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_active_obj, espnow_get_active); STATIC mp_obj_t espnow_set_active(const mp_obj_t self_in, const mp_obj_t value) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_is_true(value) ? espnow_init(self) : espnow_deinit(self); + mp_obj_is_true(value) ? common_hal_espnow_init(self) : common_hal_espnow_deinit(self); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_active_obj, espnow_set_active); @@ -298,7 +155,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_buffer_size_obj, espnow_get_buffer_size); STATIC mp_obj_t espnow_set_buffer_size(const mp_obj_t self_in, const mp_obj_t value) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - _set_buffer_size(self, mp_obj_get_int(value)); + common_hal_espnow_set_buffer_size(self, mp_obj_get_int(value)); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_buffer_size_obj, espnow_set_buffer_size); @@ -318,7 +175,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_phy_rate_obj, espnow_get_phy_rate); STATIC mp_obj_t espnow_set_phy_rate(const mp_obj_t self_in, const mp_obj_t value) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - _set_phy_rate(self, mp_obj_get_int(value)); + common_hal_espnow_set_phy_rate(self, mp_obj_get_int(value)); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_phy_rate_obj, espnow_set_phy_rate); @@ -368,36 +225,6 @@ static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { return wifi_packet->rx_ctrl.rssi; } -// 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. - mp_map_t *map = mp_obj_dict_get_map(self->peers_table); - mp_obj_str_t peer_obj = {{&mp_type_bytes}, 0, ESP_NOW_ETH_ALEN, peer}; - mp_map_elem_t *item = mp_map_lookup(map, &peer_obj, MP_MAP_LOOKUP); - if (item == NULL) { - // If not found, add the peer using a new bytestring - map->is_fixed = 0; // Allow to modify the dict - mp_obj_t new_peer = mp_obj_new_bytes(peer, ESP_NOW_ETH_ALEN); - item = mp_map_lookup(map, new_peer, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); - item->value = mp_obj_new_list(2, NULL); - map->is_fixed = 1; // Relock the dict - } - return item; -} - -// Update the peers table with the new rssi value from a received packet and -// return a reference to the item in the peers_table. -static void _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); - list->items[0] = MP_OBJ_NEW_SMALL_INT(rssi); - list->items[1] = mp_obj_new_int(time_ms); -} - // --- Handling espnow packets in the recv buffer --- // --- Send and Receive ESP-NOW data --- @@ -408,22 +235,6 @@ static const uint8_t *_get_peer_addr(mp_obj_t mac) { return mp_obj_is_true(mac) ? _get_bytes_len(mac, ESP_NOW_ETH_ALEN, MP_BUFFER_READ) : NULL; } -// 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(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); - } - if (t > PENDING_RESPONSES_BUSY_POLL_MS) { - // After 10ms of busy waiting give other tasks a look in. - RUN_BACKGROUND_TASKS; - } - } -} - //| def send( //| self, //| message: ReadableBuffer, @@ -458,106 +269,26 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k check_for_deinit(self); const bool sync = mp_obj_is_true(args[ARG_sync].u_obj); - - if (sync) { - // Flush out any pending responses. - // If the last call was sync == False there may be outstanding responses - // still to be received (possible many if we just had a burst of unsync send()s). - // We need to wait for all pending responses if this call has sync = True. - _wait_for_pending_responses(self); - } - const uint8_t *peer_addr = _get_peer_addr(args[ARG_mac].u_obj); // Get a pointer to the data buffer of the message mp_buffer_info_t message; mp_get_buffer_raise(args[ARG_message].u_obj, &message, MP_BUFFER_READ); - // Send the packet - try, try again if internal esp-now buffers are full. - esp_err_t err; - size_t saved_failures = self->tx_failures; - mp_uint_t start = mp_hal_ticks_ms(); - - while ((ESP_ERR_ESPNOW_NO_MEM == (err = esp_now_send(peer_addr, message.buf, message.len))) && - (mp_hal_ticks_ms() - start) <= DEFAULT_SEND_TIMEOUT_MS) { - RUN_BACKGROUND_TASKS; - } - check_esp_err(err); - - // 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_addr == NULL) ? self->peers_count : 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)); + return common_hal_espnow_send(self, sync, peer_addr, &message); } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); -//| def recv(self, buffers: List[WriteableBuffer]) -> int: -//| """Loads mac, message, rssi and timestamp into the provided buffers. -//| -//| If buffers is 2 elements long, the 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. -//| -//| :param List[WriteableBuffer] buffers: List of buffers to be loaded. +//| def recv(self) -> Optional[ESPNowPacket]: +//| """Receive a message from the peer(s). //| -//| :returns: Length of the message.""" +//| :returns: An `ESPNowPacket` if available in the buffer, otherwise `None`.""" //| ... STATIC mp_obj_t espnow_recv(mp_obj_t self_in, mp_obj_t buffers) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); - mp_obj_list_t *list = MP_OBJ_TO_PTR(buffers); - 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 = _get_bytes_len(list->items[0], ESP_NOW_ETH_ALEN, MP_BUFFER_WRITE); - uint8_t *msg_buf = _get_bytes_len(msg, ESP_NOW_MAX_DATA_LEN, MP_BUFFER_WRITE); - - // Read the packet header from the incoming buffer - espnow_header_t header; - if (!ringbuf_get_n(self->recv_buffer, (uint8_t *)&header, sizeof(header))) { - return MP_OBJ_NEW_SMALL_INT(0); - } - - uint8_t msg_len = header.msg_len; - - // Check the message packet header format and read the message data - if (header.magic != ESPNOW_MAGIC || - msg_len > ESP_NOW_MAX_DATA_LEN || - !ringbuf_get_n(self->recv_buffer, peer_buf, ESP_NOW_ETH_ALEN) || - !ringbuf_get_n(self->recv_buffer, msg_buf, msg_len)) { - 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 - _update_rssi(self, peer_buf, header.rssi, header.time_ms); - if (list->len == 4) { - list->items[2] = MP_OBJ_NEW_SMALL_INT(header.rssi); - list->items[3] = mp_obj_new_int(header.time_ms); - } - - return MP_OBJ_NEW_SMALL_INT(msg_len); + return common_hal_espnow_recv(self); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_recv_obj, espnow_recv); @@ -581,7 +312,9 @@ static void _update_peer_info(size_t n_args, const mp_obj_t *pos_args, mp_map_t memcpy(peer.peer_addr, _get_peer_addr(args[ARG_mac].u_obj), ESP_NOW_ETH_ALEN); if (modify) { - check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); + if (esp_now_get_peer(peer.peer_addr, &peer) != ESP_OK) { + mp_raise_RuntimeError(translate("peer not found")); + } } else { if (esp_now_is_peer_exist(peer.peer_addr)) { mp_raise_RuntimeError(translate("peer already exists")); @@ -593,7 +326,7 @@ static void _update_peer_info(size_t n_args, const mp_obj_t *pos_args, mp_map_t const mp_obj_t channel = args[ARG_channel].u_obj; if (channel != mp_const_none) { - peer.channel = mp_arg_validate_int_range(mp_obj_get_int(channel), 1, 14, MP_QSTR_channel); + peer.channel = mp_arg_validate_int_range(mp_obj_get_int(channel), 0, 14, MP_QSTR_channel); } const mp_obj_t interface = args[ARG_interface].u_obj; @@ -837,7 +570,7 @@ STATIC mp_uint_t espnow_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintpt } espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return (espnow_deinited(self)) ? 0 : // If not initialized + return (common_hal_espnow_deinited(self)) ? 0 : // If not initialized arg ^ ( // If no data in the buffer, unset the Read ready flag ((!ringbuf_num_filled(self->recv_buffer)) ? MP_STREAM_POLL_RD : 0) | diff --git a/ports/espressif/bindings/espnow/ESPNowPacket.c b/ports/espressif/bindings/espnow/ESPNowPacket.c new file mode 100644 index 000000000000..82fcfcce98d8 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNowPacket.c @@ -0,0 +1,70 @@ +/* + * This file is part of the CircuitPython project, https://github.com/adafruit/circuitpython + * + * 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 "bindings/espnow/ESPNowPacket.h" + +//| class ESPNowPacket: +//| """A packet retreived from ESP-NOW communication protocol""" +//| +//| mac: ReadableBuffer +//| """The sender's mac address (length = 6 bytes)""" +//| +//| msg: RedableBuffer +//| """The message sent by the peer (length <= 250 bytes)""" +//| +//| rssi: int +//| """The received signal strength indication (in dBm from -127 to 0)""" +//| +//| time: int +//| """The is the time in milliseconds since device last booted""" +//| + +const mp_obj_namedtuple_type_t espnow_packet_type_obj = { + .base = { + .base = { + .type = &mp_type_type + }, + .flags = MP_TYPE_FLAG_EXTENDED, + .name = MP_QSTR_ESPNowPacket, + .print = namedtuple_print, + .parent = &mp_type_tuple, + .make_new = namedtuple_make_new, + .attr = namedtuple_attr, + MP_TYPE_EXTENDED_FIELDS( + .unary_op = mp_obj_tuple_unary_op, + .binary_op = mp_obj_tuple_binary_op, + .subscr = mp_obj_tuple_subscr, + .getiter = mp_obj_tuple_getiter, + ), + }, + .n_fields = 4, + .fields = { + MP_QSTR_mac, + MP_QSTR_msg, + MP_QSTR_rssi, + MP_QSTR_time, + }, +}; diff --git a/ports/espressif/bindings/espnow/ESPNowPacket.h b/ports/espressif/bindings/espnow/ESPNowPacket.h new file mode 100644 index 000000000000..87fc51ee9232 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNowPacket.h @@ -0,0 +1,30 @@ +/* + * 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 + +#include "py/objnamedtuple.h" +extern const mp_obj_namedtuple_type_t espnow_packet_type_obj; diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c index 56618859116f..746e9696a985 100644 --- a/ports/espressif/bindings/espnow/__init__.c +++ b/ports/espressif/bindings/espnow/__init__.c @@ -28,6 +28,7 @@ #include "bindings/espnow/__init__.h" #include "bindings/espnow/ESPNow.h" +#include "bindings/espnow/ESPNowPacket.h" //| """ESP-NOW Module //| @@ -75,9 +76,11 @@ 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) }, + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_espnow) }, + // module classes - { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&espnow_type) }, + { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&espnow_type) }, + { MP_ROM_QSTR(MP_QSTR_ESPNowPacket),MP_ROM_PTR(&espnow_packet_type_obj) }, }; STATIC MP_DEFINE_CONST_DICT(espnow_module_globals, espnow_module_globals_table); diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c new file mode 100644 index 000000000000..6e49f5f54a93 --- /dev/null +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -0,0 +1,327 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017-2020 Nick Moore + * Copyright (c) 2018 shawwwn + * Copyright (c) 2020-2021 Glenn Moloney @glenn20 + * 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 "py/mperrno.h" +#include "py/runtime.h" + +#include "bindings/espnow/ESPNowPacket.h" +#include "shared-bindings/wifi/__init__.h" + +#include "common-hal/espnow/ESPNow.h" + +#include "mphalport.h" + +#include "esp_now.h" + +#define ESPNOW_MAGIC 0x99 + +// The maximum length of an espnow packet (bytes) +#define MAX_PACKET_LEN (sizeof(espnow_packet_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) + +// 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 + uint32_t time_ms; // Timestamp (ms) when packet is received + int8_t rssi; // RSSI value (dBm) (-127 to 0) +} __attribute__((packed)) espnow_header_t; + +typedef struct { + espnow_header_t header; // The header + uint8_t peer[6]; // Peer address + uint8_t msg[0]; // Message is up to 250 bytes +} __attribute__((packed)) espnow_packet_t; + +static void check_esp_err(esp_err_t status) { + if (status != ESP_OK) { + mp_raise_RuntimeError(translate("an error occured")); + } +} + +// Return a pointer to the ESPNow module singleton +static espnow_obj_t *_get_singleton(void) { + return MP_STATE_PORT(espnow_singleton); +} + +// --- The ESP-NOW send and recv callback routines --- + +// 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, 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_packet(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. +static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { + espnow_obj_t *self = _get_singleton(); + ringbuf_t *buf = self->recv_buffer; + + if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { + self->rx_failures++; + return; + } + + espnow_header_t header; + header.magic = ESPNOW_MAGIC; + header.msg_len = msg_len; + header.rssi = _get_rssi_from_wifi_packet(msg); + header.time_ms = mp_hal_ticks_ms(); + + ringbuf_put_n(buf, (uint8_t *)&header, sizeof(header)); + ringbuf_put_n(buf, mac, ESP_NOW_ETH_ALEN); + ringbuf_put_n(buf, msg, msg_len); + + self->rx_packets++; +} + +bool common_hal_espnow_deinited(espnow_obj_t *self) { + return self->recv_buffer == NULL; +} + +// Initialize the ESP-NOW software stack, +// register callbacks and allocate the recv data buffers. +void common_hal_espnow_init(espnow_obj_t *self) { + if (!common_hal_espnow_deinited(self)) { + return; + } + + 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); + } + + if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { + common_hal_wifi_init(false); + common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true); + } + + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, self->phy_rate)); + check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, self->phy_rate)); + + check_esp_err(esp_now_init()); + check_esp_err(esp_now_register_send_cb(send_cb)); + check_esp_err(esp_now_register_recv_cb(recv_cb)); +} + +// De-initialize the ESP-NOW software stack, +// disable callbacks and deallocate the recv data buffers. +void common_hal_espnow_deinit(espnow_obj_t *self) { + if (self == NULL || common_hal_espnow_deinited(self)) { + return; + } + + 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->peers_count = 0; // esp_now_deinit() removes all peers. + self->tx_packets = self->tx_responses; +} + +void espnow_reset(void) { + common_hal_espnow_deinit(_get_singleton()); + MP_STATE_PORT(espnow_singleton) = NULL; +} + +void common_hal_espnow_set_buffer_size(espnow_obj_t *self, mp_int_t value) { + self->recv_buffer_size = mp_arg_validate_int_min(value, MAX_PACKET_LEN, MP_QSTR_buffer_size); +}; + +void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value) { + self->phy_rate = mp_arg_validate_int_range(value, 0, WIFI_PHY_RATE_MAX - 1, MP_QSTR_phy_rate); +}; + +void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key) { + check_esp_err(esp_now_set_pmk(key)); +} + +// --- 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 +// - to return unique bytestrings for each peer which supports more efficient +// application memory usage and peer handling. + +// Get the RSSI value from the wifi packet header +static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { + // Warning: Secret magic to get the rssi from the wifi packet header + // See espnow.c:espnow_recv_cb() at https://github.com/espressif/esp-now/ + // In the wifi packet the msg comes after a wifi_promiscuous_pkt_t + // and a espnow_frame_format_t. + // Backtrack to get a pointer to the wifi_promiscuous_pkt_t. + #define SIZEOF_ESPNOW_FRAME_FORMAT 39 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + wifi_promiscuous_pkt_t *wifi_packet = (wifi_promiscuous_pkt_t *)( + msg - SIZEOF_ESPNOW_FRAME_FORMAT - sizeof(wifi_promiscuous_pkt_t)); + #pragma GCC diagnostic pop + return wifi_packet->rx_ctrl.rssi; +} + +// 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. + mp_map_t *map = mp_obj_dict_get_map(self->peers_table); + mp_obj_str_t peer_obj = {{&mp_type_bytes}, 0, ESP_NOW_ETH_ALEN, peer}; + mp_map_elem_t *item = mp_map_lookup(map, &peer_obj, MP_MAP_LOOKUP); + if (item == NULL) { + // If not found, add the peer using a new bytestring + map->is_fixed = 0; // Allow to modify the dict + mp_obj_t new_peer = mp_obj_new_bytes(peer, ESP_NOW_ETH_ALEN); + item = mp_map_lookup(map, new_peer, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + item->value = mp_obj_new_list(2, NULL); + map->is_fixed = 1; // Relock the dict + } + return item; +} + +// Update the peers table with the new rssi value from a received packet and +// return a reference to the item in the peers_table. +static void _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); + list->items[0] = MP_OBJ_NEW_SMALL_INT(rssi); + list->items[1] = mp_obj_new_int(time_ms); +} + +// --- Send and Receive ESP-NOW data --- + +// 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(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); + } + if (t > PENDING_RESPONSES_BUSY_POLL_MS) { + // After 10ms of busy waiting give other tasks a look in. + RUN_BACKGROUND_TASKS; + } + } +} + +mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const bool sync, const uint8_t *mac, const mp_buffer_info_t *message) { + if (sync) { + // Flush out any pending responses. + // If the last call was sync == False there may be outstanding responses + // still to be received (possible many if we just had a burst of unsync send()s). + // We need to wait for all pending responses if this call has sync = True. + _wait_for_pending_responses(self); + } + + // Send the packet - try, try again if internal esp-now buffers are full. + esp_err_t err; + size_t saved_failures = self->tx_failures; + mp_uint_t start = mp_hal_ticks_ms(); + + while ((ESP_ERR_ESPNOW_NO_MEM == (err = esp_now_send(mac, message->buf, message->len))) && + (mp_hal_ticks_ms() - start) <= DEFAULT_SEND_TIMEOUT_MS) { + RUN_BACKGROUND_TASKS; + } + check_esp_err(err); + + // Increment the sent packet count. + // If mac == NULL msg will be sent to all peers EXCEPT any broadcast or multicast addresses. + self->tx_packets += ((mac == NULL) ? self->peers_count : 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)); +} + +mp_obj_t common_hal_espnow_recv(espnow_obj_t *self) { + if (!ringbuf_num_filled(self->recv_buffer)) { + return mp_const_none; + } + + // Read the packet header from the incoming buffer + espnow_header_t header; + if (ringbuf_get_n(self->recv_buffer, (uint8_t *)&header, sizeof(header)) != sizeof(header)) { + mp_arg_error_invalid(MP_QSTR_buffer); + } + + uint8_t msg_len = header.msg_len; + + uint8_t mac_buf[ESP_NOW_ETH_ALEN]; + uint8_t msg_buf[msg_len]; + + // Check the message packet header format and read the message data + if (header.magic != ESPNOW_MAGIC || + msg_len > ESP_NOW_MAX_DATA_LEN || + ringbuf_get_n(self->recv_buffer, mac_buf, ESP_NOW_ETH_ALEN) != ESP_NOW_ETH_ALEN || + ringbuf_get_n(self->recv_buffer, msg_buf, msg_len) != msg_len) { + mp_arg_error_invalid(MP_QSTR_buffer); + } + + // Update rssi value in the peer device table + _update_rssi(self, mac_buf, header.rssi, header.time_ms); + + mp_obj_t elems[4] = { + mp_obj_new_bytes(mac_buf, ESP_NOW_ETH_ALEN), + mp_obj_new_bytes(msg_buf, msg_len), + MP_OBJ_NEW_SMALL_INT(header.rssi), + mp_obj_new_int(header.time_ms), + }; + + return namedtuple_make_new((const mp_obj_type_t *)&espnow_packet_type_obj, 4, 0, elems); +} diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h new file mode 100644 index 000000000000..54761c00df0d --- /dev/null +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -0,0 +1,57 @@ +/* + * 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 "py/ringbuf.h" + +#include "esp_wifi.h" + +// The data structure for the espnow_singleton. +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 + wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. + volatile size_t rx_packets; // # of received packets + 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 peers_count; // Cache the # of peers for send(sync=True) + mp_obj_t peers_table; // A dictionary of discovered peers +} espnow_obj_t; + +extern void espnow_reset(void); + +extern void common_hal_espnow_init(espnow_obj_t *self); +extern void common_hal_espnow_deinit(espnow_obj_t *self); +extern bool common_hal_espnow_deinited(espnow_obj_t *self); + +extern void common_hal_espnow_set_buffer_size(espnow_obj_t *self, mp_int_t value); +extern void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value); +extern void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key); + +extern mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const bool sync, const uint8_t *mac, const mp_buffer_info_t *message); +extern mp_obj_t common_hal_espnow_recv(espnow_obj_t *self); From 7028a3adfe370be6fc93bc3f2456c36cbbb593c9 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Thu, 2 Feb 2023 21:34:24 +0530 Subject: [PATCH 08/17] rework espnow api --- locale/circuitpython.pot | 12 +- ports/espressif/Makefile | 6 +- ports/espressif/bindings/espnow/ESPNow.c | 352 +++--------------- .../espressif/bindings/espnow/ESPNowPacket.c | 2 +- ports/espressif/bindings/espnow/Peer.c | 254 +++++++++++++ ports/espressif/bindings/espnow/Peer.h | 37 ++ ports/espressif/bindings/espnow/Peers.c | 221 +++++++++++ ports/espressif/bindings/espnow/Peers.h | 37 ++ ports/espressif/bindings/espnow/__init__.c | 29 +- ports/espressif/common-hal/espnow/ESPNow.c | 8 +- ports/espressif/common-hal/espnow/ESPNow.h | 7 +- 11 files changed, 641 insertions(+), 324 deletions(-) create mode 100644 ports/espressif/bindings/espnow/Peer.c create mode 100644 ports/espressif/bindings/espnow/Peer.h create mode 100644 ports/espressif/bindings/espnow/Peers.c create mode 100644 ports/espressif/bindings/espnow/Peers.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 1b4438308610..da9e0ac0f2ae 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -124,7 +124,7 @@ msgstr "" msgid "%q init failed" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c shared-bindings/dualbank/__init__.c +#: ports/espressif/bindings/espnow/Peer.c shared-bindings/dualbank/__init__.c msgid "%q is %q" msgstr "" @@ -2431,7 +2431,7 @@ msgstr "" msgid "addresses is empty" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c +#: ports/espressif/bindings/espnow/Peers.c #: ports/espressif/common-hal/espnow/ESPNow.c msgid "an error occured" msgstr "" @@ -3816,14 +3816,6 @@ msgstr "" msgid "parameters must be registers in sequence r0 to r3" msgstr "" -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "peer already exists" -msgstr "" - -#: ports/espressif/bindings/espnow/ESPNow.c -msgid "peer not found" -msgstr "" - #: shared-bindings/bitmaptools/__init__.c shared-bindings/displayio/Bitmap.c msgid "pixel coordinates out of bounds" msgstr "" diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index 76d443dd6846..8e326e18ab24 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -126,9 +126,13 @@ ifeq ($(DEBUG), 1) # CFLAGS += -fno-inline -fno-ipa-sra else CFLAGS += -DNDEBUG -ggdb3 - OPTIMIZATION_FLAGS ?= -O2 # RISC-V is larger than xtensa # Use -Os for RISC-V when it overflows + ifeq ($(IDF_TARGET_ARCH),riscv) + OPTIMIZATION_FLAGS ?= -Os + else + OPTIMIZATION_FLAGS ?= -O2 + endif endif # option to override compiler optimization level, set in boards/$(BOARD)/mpconfigboard.mk diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index b120b2cdffba..9a1ddfb80f24 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -27,27 +27,32 @@ * THE SOFTWARE. */ -#include "esp_now.h" - #include "py/runtime.h" #include "py/objarray.h" #include "py/objproperty.h" #include "py/stream.h" -#include "py/ringbuf.h" - -#include "mphalport.h" -#include "bindings/espnow/__init__.h" #include "bindings/espnow/ESPNow.h" #include "shared-bindings/util.h" #include "common-hal/espnow/ESPNow.h" -static void check_esp_err(esp_err_t status) { - if (status != ESP_OK) { - mp_raise_RuntimeError(translate("an error occured")); - } +#include "esp_now.h" + +// Return C pointer to byte memory string/bytes/bytearray in obj. +// Raise ValueError if the length does not match expected len. +static const uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(obj, &bufinfo, rw); + mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); + return (uint8_t *)bufinfo.buf; +} + +// Return C pointer to the MAC address. +// Raise ValueError if mac is wrong type or is not 6 bytes long. +static const uint8_t *_get_peer_addr(mp_obj_t mac) { + return mp_obj_is_true(mac) ? _get_bytes_len(mac, ESP_NOW_ETH_ALEN, MP_BUFFER_READ) : NULL; } // --- Initialisation and Config functions --- @@ -89,6 +94,7 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t common_hal_espnow_set_buffer_size(self, args[ARG_buffer_size].u_int); common_hal_espnow_set_phy_rate(self, args[ARG_phy_rate].u_int); + self->peers = espnow_peers_new(); self->peers_table = mp_obj_new_dict(0); // Prevent user code modifying the dict @@ -102,14 +108,30 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t return self; } -// Return C pointer to byte memory string/bytes/bytearray in obj. -// Raise ValueError if the length does not match expected len. -static const uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(obj, &bufinfo, rw); - mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); - return (uint8_t *)bufinfo.buf; +//| def deinit(self) -> None: +//| """Deinitializes ESP-NOW and releases it for another program.""" +//| ... +STATIC mp_obj_t espnow_deinit(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_deinit(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_deinit_obj, espnow_deinit); + +//| def __enter__(self) -> ESPNow: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +STATIC mp_obj_t espnow_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + return espnow_deinit(args[0]); } +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow___exit___obj, 4, 4, espnow_obj___exit__); //| def set_pmk(self, pmk: ReadableBuffer) -> None: //| """Set the ESP-NOW Primary Master Key (pmk) for encrypted communications. @@ -123,27 +145,6 @@ STATIC mp_obj_t espnow_set_pmk(mp_obj_t self_in, mp_obj_t key) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_pmk_obj, espnow_set_pmk); -//| active: bool -//| """Initialize or de-initialize the `ESPNow` communication protocol.""" -//| -STATIC mp_obj_t espnow_get_active(const mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(!common_hal_espnow_deinited(self)); -} -MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_active_obj, espnow_get_active); - -STATIC mp_obj_t espnow_set_active(const mp_obj_t self_in, const mp_obj_t value) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_is_true(value) ? common_hal_espnow_init(self) : common_hal_espnow_deinit(self); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_active_obj, espnow_set_active); - -MP_PROPERTY_GETSET(espnow_active_obj, - (mp_obj_t)&espnow_get_active_obj, - (mp_obj_t)&espnow_set_active_obj); - - //| buffer_size: int //| """The size of the internal ring buffer.""" //| @@ -202,39 +203,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_stats_obj, espnow_get_stats); 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 -// - to return unique bytestrings for each peer which supports more efficient -// application memory usage and peer handling. - -// Get the RSSI value from the wifi packet header -static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { - // Warning: Secret magic to get the rssi from the wifi packet header - // See espnow.c:espnow_recv_cb() at https://github.com/espressif/esp-now/ - // In the wifi packet the msg comes after a wifi_promiscuous_pkt_t - // and a espnow_frame_format_t. - // Backtrack to get a pointer to the wifi_promiscuous_pkt_t. - #define SIZEOF_ESPNOW_FRAME_FORMAT 39 - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wcast-align" - wifi_promiscuous_pkt_t *wifi_packet = (wifi_promiscuous_pkt_t *)( - msg - SIZEOF_ESPNOW_FRAME_FORMAT - sizeof(wifi_promiscuous_pkt_t)); - #pragma GCC diagnostic pop - return wifi_packet->rx_ctrl.rssi; -} - -// --- Handling espnow packets in the recv buffer --- - // --- Send and Receive ESP-NOW data --- -// Return C pointer to the MAC address. -// Raise ValueError if mac is wrong type or is not 6 bytes long. -static const uint8_t *_get_peer_addr(mp_obj_t mac) { - return mp_obj_is_true(mac) ? _get_bytes_len(mac, ESP_NOW_ETH_ALEN, MP_BUFFER_READ) : NULL; -} - //| def send( //| self, //| message: ReadableBuffer, @@ -284,246 +254,38 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); //| //| :returns: An `ESPNowPacket` if available in the buffer, otherwise `None`.""" //| ... -STATIC mp_obj_t espnow_recv(mp_obj_t self_in, mp_obj_t buffers) { +STATIC mp_obj_t espnow_recv(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); return common_hal_espnow_recv(self); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_recv_obj, espnow_recv); - -// --- Peer Management Functions --- - -// Common code for add_peer() and mod_peer() to process the args. -static void _update_peer_info(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, bool modify) { - enum { ARG_mac, ARG_lmk, ARG_channel, ARG_interface, ARG_encrypt }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_mac, MP_ARG_OBJ | MP_ARG_REQUIRED }, - { MP_QSTR_lmk, MP_ARG_OBJ, { .u_obj = mp_const_none } }, - { MP_QSTR_channel, MP_ARG_INT, { .u_obj = mp_const_none } }, - { MP_QSTR_interface,MP_ARG_INT, { .u_obj = mp_const_none } }, - { MP_QSTR_encrypt, MP_ARG_BOOL,{ .u_obj = mp_const_none } }, - }; - - 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); - - esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer_addr(args[ARG_mac].u_obj), ESP_NOW_ETH_ALEN); - - if (modify) { - if (esp_now_get_peer(peer.peer_addr, &peer) != ESP_OK) { - mp_raise_RuntimeError(translate("peer not found")); - } - } else { - if (esp_now_is_peer_exist(peer.peer_addr)) { - mp_raise_RuntimeError(translate("peer already exists")); - } - peer.channel = 0; - peer.ifidx = WIFI_IF_STA; - peer.encrypt = false; - } - - const mp_obj_t channel = args[ARG_channel].u_obj; - if (channel != mp_const_none) { - peer.channel = mp_arg_validate_int_range(mp_obj_get_int(channel), 0, 14, MP_QSTR_channel); - } - - const mp_obj_t interface = args[ARG_interface].u_obj; - if (interface != mp_const_none) { - peer.ifidx = (wifi_interface_t)mp_arg_validate_int_range(mp_obj_get_int(interface), 0, 1, MP_QSTR_interface); - } - - const mp_obj_t encrypt = args[ARG_encrypt].u_obj; - if (encrypt != mp_const_none) { - peer.encrypt = mp_obj_is_true(encrypt); - } - - const mp_obj_t lmk = args[ARG_lmk].u_obj; - if (lmk != mp_const_none) { - memcpy(peer.lmk, _get_bytes_len(lmk, ESP_NOW_KEY_LEN, MP_BUFFER_READ), ESP_NOW_KEY_LEN); - } else if (peer.encrypt) { - mp_raise_ValueError_varg(translate("%q is %q"), MP_QSTR_lmk, MP_QSTR_None); - } - - check_esp_err((modify) ? esp_now_mod_peer(&peer) : esp_now_add_peer(&peer)); -} - -// Update the cached peer count in self->peers_count; -// The peers_count 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(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; - if (++count >= ESP_NOW_MAX_TOTAL_PEER_NUM) { - break; // Should not happen - } - } - - self->peers_count = count; -} - -//| def add_peer( -//| self, -//| mac: ReadableBuffer, -//| lmk: Optional[ReadableBuffer], -//| channel: int = 0, -//| interface: int = 0, -//| encrypt: bool = False, -//| ) -> None: -//| """Add peer. -//| -//| :param ReadableBuffer mac: The mac address of the peer. -//| :param ReadableBuffer lmk: The Local Master Key (lmk) of the peer. -//| :param int channel: The peer's channel. Default: 0 ie. use the current channel. -//| :param int interface: The WiFi interface to use. Default: 0 ie. STA. -//| :param bool encrypt: Whether or not to use encryption.""" -//| ... -STATIC mp_obj_t espnow_add_peer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - espnow_obj_t *self = pos_args[0]; - check_for_deinit(self); - - _update_peer_info(n_args, pos_args, kw_args, false); - _update_peer_count(self); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_add_peer_obj, 2, espnow_add_peer); - -//| def mod_peer( -//| self, -//| mac: ReadableBuffer, -//| lmk: Optional[ReadableBuffer], -//| channel: int = 0, -//| interface: int = 0, -//| encrypt: bool = False, -//| ) -> None: -//| """Modify peer. -//| -//| :param ReadableBuffer mac: The mac address of the peer. -//| :param ReadableBuffer lmk: The Local Master Key (lmk) of the peer. -//| :param int channel: The peer's channel. Default: 0 ie. use the current channel. -//| :param int interface: The WiFi interface to use. Default: 0 ie. STA. -//| :param bool encrypt: Whether or not to use encryption.""" -//| ... -STATIC mp_obj_t espnow_mod_peer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - espnow_obj_t *self = pos_args[0]; - check_for_deinit(self); - - _update_peer_info(n_args, pos_args, kw_args, true); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_mod_peer_obj, 2, espnow_mod_peer); - -//| def del_peer(self, mac: ReadableBuffer) -> None: -//| """Delete peer. -//| -//| :param ReadableBuffer mac: The mac address of the peer.""" -//| ... -STATIC mp_obj_t espnow_del_peer(mp_obj_t self_in, mp_obj_t mac) { - 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_addr(mac), ESP_NOW_ETH_ALEN); - - check_esp_err(esp_now_del_peer(peer_addr)); - _update_peer_count(self); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_del_peer_obj, espnow_del_peer); - -// Convert a peer_info struct to python tuple -// Used by espnow_get_peer() and espnow_get_peers() -static mp_obj_t _peer_info_to_tuple(const esp_now_peer_info_t *peer) { - return MP_OBJ_NEW_TUPLE( - mp_obj_new_bytes(peer->peer_addr, MP_ARRAY_SIZE(peer->peer_addr)), - mp_obj_new_bytes(peer->lmk, MP_ARRAY_SIZE(peer->lmk)), - mp_obj_new_int(peer->channel), - mp_obj_new_int(peer->ifidx), - mp_obj_new_bool(peer->encrypt)); -} - -//| def get_peer(self, mac: ReadableBuffer) -> Tuple[bytes, int, int, bool]: -//| """Get the peer info for mac as a `tuple`. -//| -//| :param ReadableBuffer mac: The mac address of the peer. -//| -//| :returns: A `tuple` of (mac, lmk, channel, interface, encrypt).""" -//| ... -STATIC mp_obj_t espnow_get_peer(mp_obj_t self_in, mp_obj_t mac) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_for_deinit(self); - - esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer_addr(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); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_recv_obj, espnow_recv); // --- Peer Related Properties --- -//| peers: Tuple[Tuple[bytes, bytes, int, int, bool], ...] -//| """The peer info records for all registered `ESPNow` peers. (read-only) -//| -//| A `tuple` of tuples: ((mac, lmk, channel, interface, encrypt), ...).""" +//| peers: Peers +//| """The peer info records for all registered `ESPNow` peers. (read-only)""" //| 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 initialize the peer info tuple. - mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peers_count, 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); - } - - return peerinfo_tuple; + return MP_OBJ_FROM_PTR(self->peers); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers); MP_PROPERTY_GETTER(espnow_peers_obj, (mp_obj_t)&espnow_get_peers_obj); -//| peers_count: 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_peers_count(mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_for_deinit(self); - - esp_now_peer_num_t peer_num = {0}; - check_esp_err(esp_now_get_peer_num(&peer_num)); - return MP_OBJ_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_get_peers_count_obj, espnow_get_peers_count); - -MP_PROPERTY_GETTER(espnow_peers_count_obj, - (mp_obj_t)&espnow_get_peers_count_obj); - //| peers_table: Dict[bytes, List[int]] //| """The dictionary of peers we have seen. (read-only) //| //| A `dict` of {peer: [rssi, time], ...} //| //| where: -//| peer 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.""" +//| * peer 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 mp_obj_t espnow_get_peers_table(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -535,26 +297,25 @@ MP_PROPERTY_GETTER(espnow_peers_table_obj, (mp_obj_t)&espnow_get_peers_table_obj); STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { + // Context managers + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&espnow___exit___obj) }, + + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&espnow_deinit_obj) }, + // Config parameters { MP_ROM_QSTR(MP_QSTR_set_pmk), MP_ROM_PTR(&espnow_set_pmk_obj) }, - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&espnow_active_obj) }, { MP_ROM_QSTR(MP_QSTR_buffer_size), MP_ROM_PTR(&espnow_buffer_size_obj) }, { MP_ROM_QSTR(MP_QSTR_phy_rate), MP_ROM_PTR(&espnow_phy_rate_obj) }, + { MP_ROM_QSTR(MP_QSTR_stats), MP_ROM_PTR(&espnow_stats_obj) }, // Send and receive messages { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&espnow_recv_obj) }, - // Peer management functions - { 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) }, - // Peer related properties { MP_ROM_QSTR(MP_QSTR_peers), MP_ROM_PTR(&espnow_peers_obj) }, - { MP_ROM_QSTR(MP_QSTR_peers_count), MP_ROM_PTR(&espnow_peers_count_obj) }, { MP_ROM_QSTR(MP_QSTR_peers_table), MP_ROM_PTR(&espnow_peers_table_obj) }, }; STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_dict_table); @@ -570,7 +331,7 @@ STATIC mp_uint_t espnow_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintpt } espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return (common_hal_espnow_deinited(self)) ? 0 : // If not initialized + return (common_hal_espnow_deinited(self)) ? 0 : // If not initialized arg ^ ( // If no data in the buffer, unset the Read ready flag ((!ringbuf_num_filled(self->recv_buffer)) ? MP_STREAM_POLL_RD : 0) | @@ -590,6 +351,7 @@ STATIC const mp_stream_p_t espnow_stream_p = { //| def __len__(self) -> int: //| """Return the number of `bytes` available to read. Used to implement ``len()``.""" //| ... +//| STATIC mp_obj_t espnow_unary_op(mp_unary_op_t op, mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); size_t len = ringbuf_num_filled(self->recv_buffer); @@ -599,7 +361,7 @@ STATIC mp_obj_t espnow_unary_op(mp_unary_op_t op, mp_obj_t self_in) { case MP_UNARY_OP_LEN: return mp_obj_new_int_from_uint(len); default: - return MP_OBJ_NULL; // op not supported + return MP_OBJ_NULL; // op not supported } } diff --git a/ports/espressif/bindings/espnow/ESPNowPacket.c b/ports/espressif/bindings/espnow/ESPNowPacket.c index 82fcfcce98d8..9d582a00eb9f 100644 --- a/ports/espressif/bindings/espnow/ESPNowPacket.c +++ b/ports/espressif/bindings/espnow/ESPNowPacket.c @@ -32,7 +32,7 @@ //| mac: ReadableBuffer //| """The sender's mac address (length = 6 bytes)""" //| -//| msg: RedableBuffer +//| msg: ReadableBuffer //| """The message sent by the peer (length <= 250 bytes)""" //| //| rssi: int diff --git a/ports/espressif/bindings/espnow/Peer.c b/ports/espressif/bindings/espnow/Peer.c new file mode 100644 index 000000000000..6dcf2732aa46 --- /dev/null +++ b/ports/espressif/bindings/espnow/Peer.c @@ -0,0 +1,254 @@ +/* + * 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 "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" + +#include "bindings/espnow/Peer.h" + +// TODO: check peer already exist +// TODO: check peer dosen't exist + +// Return C pointer to the ReadableBuffer. +// Raise ValueError if the length does not match expected len. +static const uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(obj, &bufinfo, rw); + mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); + return (uint8_t *)bufinfo.buf; +} + +// Return C pointer to the MAC address. +// Raise ValueError if mac is wrong type or is not 6 bytes long. +static const uint8_t *_get_peer_addr(mp_obj_t mac) { + return mp_obj_is_true(mac) ? _get_bytes_len(mac, ESP_NOW_ETH_ALEN, MP_BUFFER_READ) : NULL; +} + +//| class Peer: +//| """A data class to store parameters specific to a peer.""" +//| +//| def __init__( +//| self, +//| mac: bytes, +//| lmk: Optional[bytes], +//| channel: int = 0, +//| interface: int = 0, +//| encrypt: bool = False, +//| ) -> None: +//| """Construct a new peer object. +//| +//| :param bytes mac: The mac address of the peer. +//| :param bytes lmk: The Local Master Key (lmk) of the peer. +//| :param int channel: The peer's channel. Default: 0 ie. use the current channel. +//| :param int interface: The WiFi interface to use. Default: 0 ie. STA. +//| :param bool encrypt: Whether or not to use encryption. +//| """ +//| ... +STATIC mp_obj_t espnow_peer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_mac, ARG_lmk, ARG_channel, ARG_interface, ARG_encrypt }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mac, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_lmk, MP_ARG_OBJ, { .u_obj = mp_const_none } }, + { MP_QSTR_channel, MP_ARG_INT, { .u_obj = mp_const_none } }, + { MP_QSTR_interface,MP_ARG_INT, { .u_obj = mp_const_none } }, + { MP_QSTR_encrypt, MP_ARG_BOOL,{ .u_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + espnow_peer_obj_t *self = m_new_obj(espnow_peer_obj_t); + self->base.type = &espnow_peer_type; + self->peer_info = (esp_now_peer_info_t) { + .channel = 0, + .ifidx = WIFI_IF_STA, + .encrypt = false + }; + + memcpy(self->peer_info.peer_addr, _get_peer_addr(args[ARG_mac].u_obj), ESP_NOW_ETH_ALEN); + + const mp_obj_t channel = args[ARG_channel].u_obj; + if (channel != mp_const_none) { + self->peer_info.channel = mp_arg_validate_int_range(mp_obj_get_int(channel), 0, 14, MP_QSTR_channel); + } + + const mp_obj_t interface = args[ARG_interface].u_obj; + if (interface != mp_const_none) { + self->peer_info.ifidx = (wifi_interface_t)mp_arg_validate_int_range(mp_obj_get_int(interface), 0, 1, MP_QSTR_interface); + } + + const mp_obj_t encrypt = args[ARG_encrypt].u_obj; + if (encrypt != mp_const_none) { + self->peer_info.encrypt = mp_obj_is_true(encrypt); + } + + const mp_obj_t lmk = args[ARG_lmk].u_obj; + if (lmk != mp_const_none) { + memcpy(self->peer_info.lmk, _get_bytes_len(lmk, ESP_NOW_KEY_LEN, MP_BUFFER_READ), ESP_NOW_KEY_LEN); + } else if (self->peer_info.encrypt && !self->peer_info.lmk) { + mp_raise_ValueError_varg(translate("%q is %q"), MP_QSTR_lmk, MP_QSTR_None); + } + + return self; +} + +//| mac: ReadableBuffer +//| """The WiFi mac to use.""" +//| +STATIC mp_obj_t espnow_peer_get_mac(const mp_obj_t self_in) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bytes(self->peer_info.peer_addr, MP_ARRAY_SIZE(self->peer_info.peer_addr)); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_get_mac_obj, espnow_peer_get_mac); + +STATIC mp_obj_t espnow_peer_set_mac(const mp_obj_t self_in, const mp_obj_t value) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + + memcpy(self->peer_info.peer_addr, _get_peer_addr(value), ESP_NOW_ETH_ALEN); + esp_now_mod_peer(&self->peer_info); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(espnow_peer_set_mac_obj, espnow_peer_set_mac); + +MP_PROPERTY_GETSET(espnow_peer_mac_obj, + (mp_obj_t)&espnow_peer_get_mac_obj, + (mp_obj_t)&espnow_peer_set_mac_obj); + +//| lmk: ReadableBuffer +//| """The WiFi lmk to use.""" +//| +STATIC mp_obj_t espnow_peer_get_lmk(const mp_obj_t self_in) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bytes(self->peer_info.lmk, MP_ARRAY_SIZE(self->peer_info.lmk)); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_get_lmk_obj, espnow_peer_get_lmk); + +STATIC mp_obj_t espnow_peer_set_lmk(const mp_obj_t self_in, const mp_obj_t value) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + + memcpy(self->peer_info.lmk, _get_bytes_len(value, ESP_NOW_KEY_LEN, MP_BUFFER_READ), ESP_NOW_KEY_LEN); + esp_now_mod_peer(&self->peer_info); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(espnow_peer_set_lmk_obj, espnow_peer_set_lmk); + +MP_PROPERTY_GETSET(espnow_peer_lmk_obj, + (mp_obj_t)&espnow_peer_get_lmk_obj, + (mp_obj_t)&espnow_peer_set_lmk_obj); + +//| channel: int +//| """The WiFi channel to use.""" +//| +STATIC mp_obj_t espnow_peer_get_channel(const mp_obj_t self_in) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->peer_info.channel); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_get_channel_obj, espnow_peer_get_channel); + +STATIC mp_obj_t espnow_peer_set_channel(const mp_obj_t self_in, const mp_obj_t value) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->peer_info.channel = mp_arg_validate_int_range(mp_obj_get_int(value), 0, 14, MP_QSTR_channel); + esp_now_mod_peer(&self->peer_info); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(espnow_peer_set_channel_obj, espnow_peer_set_channel); + +MP_PROPERTY_GETSET(espnow_peer_channel_obj, + (mp_obj_t)&espnow_peer_get_channel_obj, + (mp_obj_t)&espnow_peer_set_channel_obj); + +//| interface: int +//| """The WiFi interface to use.""" +//| +STATIC mp_obj_t espnow_peer_get_interface(const mp_obj_t self_in) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->peer_info.ifidx); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_get_interface_obj, espnow_peer_get_interface); + +STATIC mp_obj_t espnow_peer_set_interface(const mp_obj_t self_in, const mp_obj_t value) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->peer_info.ifidx = (wifi_interface_t)mp_arg_validate_int_range(mp_obj_get_int(value), 0, 1, MP_QSTR_interface); + esp_now_mod_peer(&self->peer_info); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(espnow_peer_set_interface_obj, espnow_peer_set_interface); + +MP_PROPERTY_GETSET(espnow_peer_interface_obj, + (mp_obj_t)&espnow_peer_get_interface_obj, + (mp_obj_t)&espnow_peer_set_interface_obj); + +//| encrypted: bool +//| """Whether or not to use encryption.""" +//| +STATIC mp_obj_t espnow_peer_get_encrypted(const mp_obj_t self_in) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(self->peer_info.encrypt); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_get_encrypted_obj, espnow_peer_get_encrypted); + +STATIC mp_obj_t espnow_peer_set_encrypted(const mp_obj_t self_in, const mp_obj_t value) { + espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->peer_info.encrypt = mp_obj_is_true(value); + + if (!self->peer_info.lmk) { + mp_raise_ValueError_varg(translate("%q is %q"), MP_QSTR_lmk, MP_QSTR_None); + } + + esp_now_mod_peer(&self->peer_info); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(espnow_peer_set_encrypted_obj, espnow_peer_set_encrypted); + +MP_PROPERTY_GETSET(espnow_peer_encrypted_obj, + (mp_obj_t)&espnow_peer_get_encrypted_obj, + (mp_obj_t)&espnow_peer_set_encrypted_obj); + +STATIC const mp_rom_map_elem_t espnow_peer_locals_dict_table[] = { + // Peer parameters + { MP_ROM_QSTR(MP_QSTR_mac), MP_ROM_PTR(&espnow_peer_mac_obj) }, + { MP_ROM_QSTR(MP_QSTR_lmk), MP_ROM_PTR(&espnow_peer_lmk_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel), MP_ROM_PTR(&espnow_peer_channel_obj) }, + { MP_ROM_QSTR(MP_QSTR_interface), MP_ROM_PTR(&espnow_peer_interface_obj) }, + { MP_ROM_QSTR(MP_QSTR_encrypted), MP_ROM_PTR(&espnow_peer_encrypted_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(espnow_peer_locals_dict, espnow_peer_locals_dict_table); + +const mp_obj_type_t espnow_peer_type = { + { &mp_type_type }, + .name = MP_QSTR_Peer, + .make_new = espnow_peer_make_new, + .locals_dict = (mp_obj_t)&espnow_peer_locals_dict, +}; diff --git a/ports/espressif/bindings/espnow/Peer.h b/ports/espressif/bindings/espnow/Peer.h new file mode 100644 index 000000000000..e4cb828472a4 --- /dev/null +++ b/ports/espressif/bindings/espnow/Peer.h @@ -0,0 +1,37 @@ +/* + * 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 + +#include "py/obj.h" +#include "esp_now.h" + +typedef struct { + mp_obj_base_t base; + esp_now_peer_info_t peer_info; +} espnow_peer_obj_t; + +const mp_obj_type_t espnow_peer_type; diff --git a/ports/espressif/bindings/espnow/Peers.c b/ports/espressif/bindings/espnow/Peers.c new file mode 100644 index 000000000000..4f0c310f3da5 --- /dev/null +++ b/ports/espressif/bindings/espnow/Peers.c @@ -0,0 +1,221 @@ +/* + * 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 "py/obj.h" +#include "py/objlist.h" +#include "py/runtime.h" + +#include "bindings/espnow/Peer.h" +#include "bindings/espnow/Peers.h" + +#include "esp_now.h" + +static void check_esp_err(esp_err_t status) { + if (status != ESP_OK) { + mp_raise_RuntimeError(translate("an error occured")); + } +} + +//| class Peers: +//| """A class that provides peer managment functions. Sequence[Peer].""" +//| + +//| def append(self, peer: Peer) -> None: +//| """Append peer. +//| +//| :param Peer peer: The peer object to append. +//| """ +//| ... +STATIC mp_obj_t espnow_peers_append(mp_obj_t self_in, mp_obj_t arg) { + espnow_peer_obj_t *peer = MP_OBJ_TO_PTR(mp_arg_validate_type(arg, &espnow_peer_type, MP_QSTR_Peer)); + check_esp_err(esp_now_add_peer(&peer->peer_info)); + espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_list_append(self->list, arg); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_peers_append_obj, espnow_peers_append); + +//| def remove(self, peer: Peer) -> None: +//| """Remove peer. +//| +//| :param Peer peer: The peer object to remove. +//| """ +//| ... +//| +STATIC mp_obj_t espnow_peers_remove(mp_obj_t self_in, mp_obj_t arg) { + espnow_peer_obj_t *peer = MP_OBJ_TO_PTR(mp_arg_validate_type(arg, &espnow_peer_type, MP_QSTR_Peer)); + check_esp_err(esp_now_del_peer(peer->peer_info.peer_addr)); + espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_list_remove(self->list, arg); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_peers_remove_obj, espnow_peers_remove); + +STATIC const mp_rom_map_elem_t espnow_peers_locals_dict_table[] = { + // Peer management functions + { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&espnow_peers_append_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&espnow_peers_remove_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(espnow_peers_locals_dict, espnow_peers_locals_dict_table); + +/******************************************************************************/ +/* peers print */ + +STATIC void espnow_peers_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); + const char *item_separator = ", "; + if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { + kind = PRINT_REPR; + } else { + #if MICROPY_PY_UJSON_SEPARATORS + item_separator = MP_PRINT_GET_EXT(print)->item_separator; + #endif + } + mp_print_str(print, "["); + for (size_t i = 0; i < list->len; i++) { + if (i > 0) { + mp_print_str(print, item_separator); + } + mp_obj_print_helper(print, list->items[i], kind); + } + mp_print_str(print, "]"); +} + +/******************************************************************************/ +/* peers unary_op */ + +STATIC mp_obj_t espnow_peers_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(list->len != 0); + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(list->len); + #if MICROPY_PY_SYS_GETSIZEOF + case MP_UNARY_OP_SIZEOF: { + size_t sz = sizeof(*list) + sizeof(mp_obj_t) * list->alloc; + return MP_OBJ_NEW_SMALL_INT(sz); + } + #endif + default: + return MP_OBJ_NULL; // op not supported + } +} + +/******************************************************************************/ +/* peers subscript */ + +STATIC mp_obj_t espnow_peers_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value != MP_OBJ_SENTINEL) { + return MP_OBJ_NULL; // op not supported + } + + espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); + + // load + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(list->len, index, &slice)) { + return mp_seq_extract_slice(list->len, list->items, &slice); + } + + mp_obj_list_t *res = MP_OBJ_TO_PTR(mp_obj_new_list(slice.stop - slice.start, NULL)); + mp_seq_copy(res->items, list->items + slice.start, res->len, mp_obj_t); + return MP_OBJ_FROM_PTR(res); + } + #endif + size_t index_val = mp_get_index(list->base.type, list->len, index, false); + return list->items[index_val]; +} + +/******************************************************************************/ +/* peers iterator */ + +typedef struct _espnow_peers_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t peers; + size_t cur; +} espnow_peers_it_t; + +STATIC mp_obj_t espnow_peers_it_iternext(mp_obj_t self_in) { + espnow_peers_it_t *self = MP_OBJ_TO_PTR(self_in); + espnow_peers_obj_t *peers = MP_OBJ_TO_PTR(self->peers); + mp_obj_list_t *list = MP_OBJ_TO_PTR(peers->list); + if (self->cur < list->len) { + mp_obj_t o_out = list->items[self->cur]; + self->cur += 1; + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +STATIC mp_obj_t espnow_peers_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(espnow_peers_it_t) <= sizeof(mp_obj_iter_buf_t)); + espnow_peers_it_t *o = (espnow_peers_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = espnow_peers_it_iternext; + o->peers = o_in; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} + +espnow_peers_obj_t *espnow_peers_new(void) { + espnow_peers_obj_t *self = m_new_obj(espnow_peers_obj_t); + self->base.type = &espnow_peers_type; + self->list = mp_obj_new_list(0, NULL); + return self; +} + +const mp_obj_type_t espnow_peers_type = { + { &mp_type_type }, + .name = MP_QSTR_Peers, + .print = espnow_peers_print, + // .make_new = espnow_peers_make_new, + .locals_dict = (mp_obj_t)&espnow_peers_locals_dict, + .flags = MP_TYPE_FLAG_EXTENDED, + MP_TYPE_EXTENDED_FIELDS( + .unary_op = espnow_peers_unary_op, + .subscr = espnow_peers_subscr, + .getiter = espnow_peers_getiter, + ), +}; + +/* +STATIC mp_obj_t espnow_peers_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + mp_arg_check_num(n_args, n_kw, 0, 1, false); + + espnow_peers_obj_t *self = m_new_obj(espnow_peers_obj_t); + self->base.type = &espnow_peers_type; + self->list = mp_obj_new_list_from_iter(args[0]); + + return MP_OBJ_FROM_PTR(self); +} +*/ diff --git a/ports/espressif/bindings/espnow/Peers.h b/ports/espressif/bindings/espnow/Peers.h new file mode 100644 index 000000000000..e871ae86c0e9 --- /dev/null +++ b/ports/espressif/bindings/espnow/Peers.h @@ -0,0 +1,37 @@ +/* + * 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 + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + mp_obj_t list; +} espnow_peers_obj_t; + +extern const mp_obj_type_t espnow_peers_type; +extern espnow_peers_obj_t *espnow_peers_new(void); diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c index 746e9696a985..d92221c6913e 100644 --- a/ports/espressif/bindings/espnow/__init__.c +++ b/ports/espressif/bindings/espnow/__init__.c @@ -29,6 +29,8 @@ #include "bindings/espnow/__init__.h" #include "bindings/espnow/ESPNow.h" #include "bindings/espnow/ESPNowPacket.h" +#include "bindings/espnow/Peer.h" +#include "bindings/espnow/Peers.h" //| """ESP-NOW Module //| @@ -44,14 +46,13 @@ //| 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) +//| peer = espnow.Peer(mac=b'\xaa\xaa\xaa\xaa\xaa\xaa') +//| e.peers.append(peer) //| -//| e.send("Starting...") # Send to all peers +//| e.send("Starting...") //| for i in range(100): //| e.send(peer, str(i)*20, True) -//| e.send(b'end') +//| e.send(b'end') //| //| **Receiver** //| @@ -60,16 +61,18 @@ //| 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) +//| packets = [] //| //| while True: -//| host, msg = e.recv() -//| if msg: # msg == None if timeout in recv() -//| print(host, msg) -//| if msg == b'end': +//| if e: +//| packet = e.recv() +//| packets.append(packet) +//| if packet.msg == b'end': //| break +//| +//| print("packets:", f"length={len(packets)}") +//| for packet in packets: +//| print(packet) //| """ //| ... //| @@ -81,6 +84,8 @@ STATIC const mp_rom_map_elem_t espnow_module_globals_table[] = { // module classes { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&espnow_type) }, { MP_ROM_QSTR(MP_QSTR_ESPNowPacket),MP_ROM_PTR(&espnow_packet_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_Peer), MP_ROM_PTR(&espnow_peer_type) }, + { MP_ROM_QSTR(MP_QSTR_Peers), MP_ROM_PTR(&espnow_peers_type) }, }; STATIC MP_DEFINE_CONST_DICT(espnow_module_globals, espnow_module_globals_table); diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index 6e49f5f54a93..e906c2a9d62d 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -123,7 +123,7 @@ static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { } bool common_hal_espnow_deinited(espnow_obj_t *self) { - return self->recv_buffer == NULL; + return self == NULL || self->recv_buffer == NULL; } // Initialize the ESP-NOW software stack, @@ -154,7 +154,7 @@ void common_hal_espnow_init(espnow_obj_t *self) { // De-initialize the ESP-NOW software stack, // disable callbacks and deallocate the recv data buffers. void common_hal_espnow_deinit(espnow_obj_t *self) { - if (self == NULL || common_hal_espnow_deinited(self)) { + if (common_hal_espnow_deinited(self)) { return; } @@ -164,7 +164,7 @@ void common_hal_espnow_deinit(espnow_obj_t *self) { self->recv_buffer->buf = NULL; self->recv_buffer = NULL; - self->peers_count = 0; // esp_now_deinit() removes all peers. + // self->peers_count = 0; // esp_now_deinit() removes all peers. self->tx_packets = self->tx_responses; } @@ -278,7 +278,7 @@ mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const bool sync, const uint8 // Increment the sent packet count. // If mac == NULL msg will be sent to all peers EXCEPT any broadcast or multicast addresses. - self->tx_packets += ((mac == NULL) ? self->peers_count : 1); + self->tx_packets += ((mac == NULL) ? ((mp_obj_list_t *)self->peers->list)->len : 1); if (sync) { // Wait for and tally all the expected responses from peers diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index 54761c00df0d..85108af7a776 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -24,8 +24,13 @@ * THE SOFTWARE. */ +#pragma once + +#include "py/obj.h" #include "py/ringbuf.h" +#include "bindings/espnow/Peers.h" + #include "esp_wifi.h" // The data structure for the espnow_singleton. @@ -39,7 +44,7 @@ typedef struct _espnow_obj_t { 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 peers_count; // Cache the # of peers for send(sync=True) + espnow_peers_obj_t *peers; // Cache the # of peers for send(sync=True) mp_obj_t peers_table; // A dictionary of discovered peers } espnow_obj_t; From f100838ae56aa8e2ecbc00c039c35be80c91b8e1 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Fri, 3 Feb 2023 10:14:50 +0530 Subject: [PATCH 09/17] remove `peers_table` from espnow --- ports/espressif/bindings/espnow/ESPNow.c | 24 -------- ports/espressif/common-hal/espnow/ESPNow.c | 72 +++++----------------- ports/espressif/common-hal/espnow/ESPNow.h | 1 - 3 files changed, 15 insertions(+), 82 deletions(-) diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 9a1ddfb80f24..622f80020dc3 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -95,10 +95,6 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t common_hal_espnow_set_phy_rate(self, args[ARG_phy_rate].u_int); self->peers = espnow_peers_new(); - 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; // Set the global singleton pointer for the espnow protocol. MP_STATE_PORT(espnow_singleton) = self; @@ -277,25 +273,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers); MP_PROPERTY_GETTER(espnow_peers_obj, (mp_obj_t)&espnow_get_peers_obj); -//| peers_table: Dict[bytes, List[int]] -//| """The dictionary of peers we have seen. (read-only) -//| -//| A `dict` of {peer: [rssi, time], ...} -//| -//| where: -//| * peer 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 mp_obj_t espnow_get_peers_table(mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return self->peers_table; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_table_obj, espnow_get_peers_table); - -MP_PROPERTY_GETTER(espnow_peers_table_obj, - (mp_obj_t)&espnow_get_peers_table_obj); - STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { // Context managers { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, @@ -316,7 +293,6 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { // Peer related properties { MP_ROM_QSTR(MP_QSTR_peers), MP_ROM_PTR(&espnow_peers_obj) }, - { MP_ROM_QSTR(MP_QSTR_peers_table), MP_ROM_PTR(&espnow_peers_table_obj) }, }; STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_dict_table); diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index e906c2a9d62d..a9e2e45f1fb3 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -95,7 +95,21 @@ static void send_cb(const uint8_t *mac, esp_now_send_status_t status) { } } -static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg); +// Get the RSSI value from the wifi packet header +static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { + // Warning: Secret magic to get the rssi from the wifi packet header + // See espnow.c:espnow_recv_cb() at https://github.com/espressif/esp-now/ + // In the wifi packet the msg comes after a wifi_promiscuous_pkt_t + // and a espnow_frame_format_t. + // Backtrack to get a pointer to the wifi_promiscuous_pkt_t. + #define SIZEOF_ESPNOW_FRAME_FORMAT 39 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + wifi_promiscuous_pkt_t *wifi_packet = (wifi_promiscuous_pkt_t *)( + msg - SIZEOF_ESPNOW_FRAME_FORMAT - sizeof(wifi_promiscuous_pkt_t)); + #pragma GCC diagnostic pop + return wifi_packet->rx_ctrl.rssi; +} // 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. @@ -185,59 +199,6 @@ void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key) { check_esp_err(esp_now_set_pmk(key)); } -// --- 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 -// - to return unique bytestrings for each peer which supports more efficient -// application memory usage and peer handling. - -// Get the RSSI value from the wifi packet header -static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { - // Warning: Secret magic to get the rssi from the wifi packet header - // See espnow.c:espnow_recv_cb() at https://github.com/espressif/esp-now/ - // In the wifi packet the msg comes after a wifi_promiscuous_pkt_t - // and a espnow_frame_format_t. - // Backtrack to get a pointer to the wifi_promiscuous_pkt_t. - #define SIZEOF_ESPNOW_FRAME_FORMAT 39 - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wcast-align" - wifi_promiscuous_pkt_t *wifi_packet = (wifi_promiscuous_pkt_t *)( - msg - SIZEOF_ESPNOW_FRAME_FORMAT - sizeof(wifi_promiscuous_pkt_t)); - #pragma GCC diagnostic pop - return wifi_packet->rx_ctrl.rssi; -} - -// 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. - mp_map_t *map = mp_obj_dict_get_map(self->peers_table); - mp_obj_str_t peer_obj = {{&mp_type_bytes}, 0, ESP_NOW_ETH_ALEN, peer}; - mp_map_elem_t *item = mp_map_lookup(map, &peer_obj, MP_MAP_LOOKUP); - if (item == NULL) { - // If not found, add the peer using a new bytestring - map->is_fixed = 0; // Allow to modify the dict - mp_obj_t new_peer = mp_obj_new_bytes(peer, ESP_NOW_ETH_ALEN); - item = mp_map_lookup(map, new_peer, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); - item->value = mp_obj_new_list(2, NULL); - map->is_fixed = 1; // Relock the dict - } - return item; -} - -// Update the peers table with the new rssi value from a received packet and -// return a reference to the item in the peers_table. -static void _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); - list->items[0] = MP_OBJ_NEW_SMALL_INT(rssi); - list->items[1] = mp_obj_new_int(time_ms); -} - // --- Send and Receive ESP-NOW data --- // Used by espnow_send() for sends() with sync==True. @@ -313,9 +274,6 @@ mp_obj_t common_hal_espnow_recv(espnow_obj_t *self) { mp_arg_error_invalid(MP_QSTR_buffer); } - // Update rssi value in the peer device table - _update_rssi(self, mac_buf, header.rssi, header.time_ms); - mp_obj_t elems[4] = { mp_obj_new_bytes(mac_buf, ESP_NOW_ETH_ALEN), mp_obj_new_bytes(msg_buf, msg_len), diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index 85108af7a776..63d5f94f2675 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -45,7 +45,6 @@ typedef struct _espnow_obj_t { volatile size_t tx_responses; // # of sent packet responses received volatile size_t tx_failures; // # of sent packet responses failed espnow_peers_obj_t *peers; // Cache the # of peers for send(sync=True) - mp_obj_t peers_table; // A dictionary of discovered peers } espnow_obj_t; extern void espnow_reset(void); From e30126e335abcdd078db2ec14cf1a30d5dc6cdbf Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Fri, 3 Feb 2023 11:08:13 +0530 Subject: [PATCH 10/17] remove `sync` from espnow --- ports/espressif/bindings/espnow/ESPNow.c | 13 +------ ports/espressif/common-hal/espnow/ESPNow.c | 43 ++-------------------- ports/espressif/common-hal/espnow/ESPNow.h | 2 +- 3 files changed, 7 insertions(+), 51 deletions(-) diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 622f80020dc3..07e80c3b6737 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -205,18 +205,11 @@ MP_PROPERTY_GETTER(espnow_stats_obj, //| self, //| message: ReadableBuffer, //| mac: Optional[ReadableBuffer], -//| sync: bool = True, //| ) -> bool: -//| """Send a message to the peer's mac address. Optionally wait for a response. +//| """Send a message to the peer's mac address. //| //| :param ReadableBuffer message: The message to send (length <= 250 bytes). //| :param ReadableBuffer mac: The peer's address (length = 6 bytes). If `None` or any non-true value, send to all registered peers. -//| :param bool sync: If `True`, wait for response from peer(s) after sending. -//| -//| :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.""" //| ... @@ -225,7 +218,6 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k static const mp_arg_t allowed_args[] = { { MP_QSTR_message, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_mac, MP_ARG_OBJ, { .u_obj = mp_const_none } }, - { MP_QSTR_sync, MP_ARG_BOOL, { .u_bool = mp_const_true } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -234,14 +226,13 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k espnow_obj_t *self = pos_args[0]; check_for_deinit(self); - const bool sync = mp_obj_is_true(args[ARG_sync].u_obj); const uint8_t *peer_addr = _get_peer_addr(args[ARG_mac].u_obj); // Get a pointer to the data buffer of the message mp_buffer_info_t message; mp_get_buffer_raise(args[ARG_message].u_obj, &message, MP_BUFFER_READ); - return common_hal_espnow_send(self, sync, peer_addr, &message); + return common_hal_espnow_send(self, peer_addr, &message); } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index a9e2e45f1fb3..b95258f5f5ba 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -49,12 +49,7 @@ #define DEFAULT_RECV_BUFFER_SIZE (2 * MAX_PACKET_LEN) // 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 +#define DEFAULT_SEND_TIMEOUT_MS (1000) // ESPNow packet format for the receive buffer. // Use this for peeking at the header of the next packet in the buffer. @@ -201,34 +196,10 @@ void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key) { // --- Send and Receive ESP-NOW data --- -// 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(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); - } - if (t > PENDING_RESPONSES_BUSY_POLL_MS) { - // After 10ms of busy waiting give other tasks a look in. - RUN_BACKGROUND_TASKS; - } - } -} -mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const bool sync, const uint8_t *mac, const mp_buffer_info_t *message) { - if (sync) { - // Flush out any pending responses. - // If the last call was sync == False there may be outstanding responses - // still to be received (possible many if we just had a burst of unsync send()s). - // We need to wait for all pending responses if this call has sync = True. - _wait_for_pending_responses(self); - } - - // Send the packet - try, try again if internal esp-now buffers are full. +mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const uint8_t *mac, const mp_buffer_info_t *message) { + // Send the packet - keep trying until timeout if the internal esp-now buffers are full. esp_err_t err; - size_t saved_failures = self->tx_failures; mp_uint_t start = mp_hal_ticks_ms(); while ((ESP_ERR_ESPNOW_NO_MEM == (err = esp_now_send(mac, message->buf, message->len))) && @@ -241,13 +212,7 @@ mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const bool sync, const uint8 // If mac == NULL msg will be sent to all peers EXCEPT any broadcast or multicast addresses. self->tx_packets += ((mac == NULL) ? ((mp_obj_list_t *)self->peers->list)->len : 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)); + return mp_const_none; } mp_obj_t common_hal_espnow_recv(espnow_obj_t *self) { diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index 63d5f94f2675..02ca90d5c411 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -57,5 +57,5 @@ extern void common_hal_espnow_set_buffer_size(espnow_obj_t *self, mp_int_t value extern void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value); extern void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key); -extern mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const bool sync, const uint8_t *mac, const mp_buffer_info_t *message); +extern mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const uint8_t *mac, const mp_buffer_info_t *message); extern mp_obj_t common_hal_espnow_recv(espnow_obj_t *self); From cac90a6969a02c7879764c80c672af4b4eb75708 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Fri, 3 Feb 2023 12:35:06 +0530 Subject: [PATCH 11/17] refactor common espnow functions --- ports/espressif/bindings/espnow/ESPNow.c | 20 ++--------- ports/espressif/bindings/espnow/Peer.c | 24 +++---------- ports/espressif/common-hal/espnow/__init__.c | 37 ++++++++++++++++++++ ports/espressif/common-hal/espnow/__init__.h | 30 ++++++++++++++++ 4 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 ports/espressif/common-hal/espnow/__init__.c create mode 100644 ports/espressif/common-hal/espnow/__init__.h diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 07e80c3b6737..39eb7a501901 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -36,25 +36,11 @@ #include "shared-bindings/util.h" +#include "common-hal/espnow/__init__.h" #include "common-hal/espnow/ESPNow.h" #include "esp_now.h" -// Return C pointer to byte memory string/bytes/bytearray in obj. -// Raise ValueError if the length does not match expected len. -static const uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(obj, &bufinfo, rw); - mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); - return (uint8_t *)bufinfo.buf; -} - -// Return C pointer to the MAC address. -// Raise ValueError if mac is wrong type or is not 6 bytes long. -static const uint8_t *_get_peer_addr(mp_obj_t mac) { - return mp_obj_is_true(mac) ? _get_bytes_len(mac, ESP_NOW_ETH_ALEN, MP_BUFFER_READ) : NULL; -} - // --- Initialisation and Config functions --- static void check_for_deinit(espnow_obj_t *self) { @@ -136,7 +122,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow___exit___obj, 4, 4, espnow_obj //| ... STATIC mp_obj_t espnow_set_pmk(mp_obj_t self_in, mp_obj_t key) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_set_pmk(self, _get_bytes_len(key, ESP_NOW_KEY_LEN, MP_BUFFER_READ)); + common_hal_espnow_set_pmk(self, common_hal_espnow_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); @@ -226,7 +212,7 @@ STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k espnow_obj_t *self = pos_args[0]; check_for_deinit(self); - const uint8_t *peer_addr = _get_peer_addr(args[ARG_mac].u_obj); + const uint8_t *peer_addr = common_hal_espnow_get_bytes_len(args[ARG_mac].u_obj, ESP_NOW_ETH_ALEN); // Get a pointer to the data buffer of the message mp_buffer_info_t message; diff --git a/ports/espressif/bindings/espnow/Peer.c b/ports/espressif/bindings/espnow/Peer.c index 6dcf2732aa46..7b6cc5f66822 100644 --- a/ports/espressif/bindings/espnow/Peer.c +++ b/ports/espressif/bindings/espnow/Peer.c @@ -29,25 +29,11 @@ #include "py/runtime.h" #include "bindings/espnow/Peer.h" +#include "common-hal/espnow/__init__.h" // TODO: check peer already exist // TODO: check peer dosen't exist -// Return C pointer to the ReadableBuffer. -// Raise ValueError if the length does not match expected len. -static const uint8_t *_get_bytes_len(mp_obj_t obj, size_t len, mp_uint_t rw) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(obj, &bufinfo, rw); - mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); - return (uint8_t *)bufinfo.buf; -} - -// Return C pointer to the MAC address. -// Raise ValueError if mac is wrong type or is not 6 bytes long. -static const uint8_t *_get_peer_addr(mp_obj_t mac) { - return mp_obj_is_true(mac) ? _get_bytes_len(mac, ESP_NOW_ETH_ALEN, MP_BUFFER_READ) : NULL; -} - //| class Peer: //| """A data class to store parameters specific to a peer.""" //| @@ -89,7 +75,7 @@ STATIC mp_obj_t espnow_peer_make_new(const mp_obj_type_t *type, size_t n_args, s .encrypt = false }; - memcpy(self->peer_info.peer_addr, _get_peer_addr(args[ARG_mac].u_obj), ESP_NOW_ETH_ALEN); + memcpy(self->peer_info.peer_addr, common_hal_espnow_get_bytes_len(args[ARG_mac].u_obj, ESP_NOW_ETH_ALEN), ESP_NOW_ETH_ALEN); const mp_obj_t channel = args[ARG_channel].u_obj; if (channel != mp_const_none) { @@ -108,7 +94,7 @@ STATIC mp_obj_t espnow_peer_make_new(const mp_obj_type_t *type, size_t n_args, s const mp_obj_t lmk = args[ARG_lmk].u_obj; if (lmk != mp_const_none) { - memcpy(self->peer_info.lmk, _get_bytes_len(lmk, ESP_NOW_KEY_LEN, MP_BUFFER_READ), ESP_NOW_KEY_LEN); + memcpy(self->peer_info.lmk, common_hal_espnow_get_bytes_len(lmk, ESP_NOW_KEY_LEN), ESP_NOW_KEY_LEN); } else if (self->peer_info.encrypt && !self->peer_info.lmk) { mp_raise_ValueError_varg(translate("%q is %q"), MP_QSTR_lmk, MP_QSTR_None); } @@ -128,7 +114,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_get_mac_obj, espnow_peer_get_mac); STATIC mp_obj_t espnow_peer_set_mac(const mp_obj_t self_in, const mp_obj_t value) { espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); - memcpy(self->peer_info.peer_addr, _get_peer_addr(value), ESP_NOW_ETH_ALEN); + memcpy(self->peer_info.peer_addr, common_hal_espnow_get_bytes_len(value, ESP_NOW_ETH_ALEN), ESP_NOW_ETH_ALEN); esp_now_mod_peer(&self->peer_info); return mp_const_none; @@ -151,7 +137,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_get_lmk_obj, espnow_peer_get_lmk); STATIC mp_obj_t espnow_peer_set_lmk(const mp_obj_t self_in, const mp_obj_t value) { espnow_peer_obj_t *self = MP_OBJ_TO_PTR(self_in); - memcpy(self->peer_info.lmk, _get_bytes_len(value, ESP_NOW_KEY_LEN, MP_BUFFER_READ), ESP_NOW_KEY_LEN); + memcpy(self->peer_info.lmk, common_hal_espnow_get_bytes_len(value, ESP_NOW_KEY_LEN), ESP_NOW_KEY_LEN); esp_now_mod_peer(&self->peer_info); return mp_const_none; diff --git a/ports/espressif/common-hal/espnow/__init__.c b/ports/espressif/common-hal/espnow/__init__.c new file mode 100644 index 000000000000..498cd49dc945 --- /dev/null +++ b/ports/espressif/common-hal/espnow/__init__.c @@ -0,0 +1,37 @@ +/* + * 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 "common-hal/espnow/__init__.h" +#include "py/runtime.h" + +// Return C pointer to byte memory string/bytes/bytearray in obj. +// Raise ValueError if the length does not match expected len. +const uint8_t *common_hal_espnow_get_bytes_len(mp_obj_t obj, size_t len) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(obj, &bufinfo, MP_BUFFER_READ); + mp_arg_validate_length(bufinfo.len, len, MP_QSTR_buffer); + return (uint8_t *)bufinfo.buf; +} diff --git a/ports/espressif/common-hal/espnow/__init__.h b/ports/espressif/common-hal/espnow/__init__.h new file mode 100644 index 000000000000..bb1950d98c7a --- /dev/null +++ b/ports/espressif/common-hal/espnow/__init__.h @@ -0,0 +1,30 @@ +/* + * 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 + +#include "py/obj.h" +extern const uint8_t *common_hal_espnow_get_bytes_len(mp_obj_t obj, size_t len); From a1644f15ea7278cdf2fb96a25f549bafc3d8c8a9 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Fri, 3 Feb 2023 15:42:44 +0530 Subject: [PATCH 12/17] use esp error for espnow --- locale/circuitpython.pot | 32 +++---- ports/espressif/Makefile | 10 ++- ports/espressif/bindings/espnow/Peers.c | 12 +-- ports/espressif/common-hal/espidf/__init__.c | 9 +- ports/espressif/common-hal/espnow/ESPNow.c | 30 +++---- ports/espressif/esp_error.c | 91 -------------------- 6 files changed, 46 insertions(+), 138 deletions(-) delete mode 100644 ports/espressif/esp_error.c diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 10993652aaf0..433799172146 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -222,7 +222,7 @@ msgstr "" msgid "%q=%q" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c #, c-format msgid "%s error 0x%x" msgstr "" @@ -680,7 +680,7 @@ msgstr "" msgid "CIRCUITPY drive could not be found or created." msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "CRC or checksum was invalid" msgstr "" @@ -1067,7 +1067,7 @@ msgstr "" msgid "GNSS init" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Generic Failure" msgstr "" @@ -1235,8 +1235,7 @@ msgstr "" msgid "Invalid MAC address" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c -#: py/moduerrno.c +#: ports/espressif/common-hal/espidf/__init__.c py/moduerrno.c msgid "Invalid argument" msgstr "" @@ -1266,7 +1265,7 @@ msgstr "" msgid "Invalid multicast MAC address" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Invalid size" msgstr "" @@ -1275,7 +1274,7 @@ msgstr "" msgid "Invalid socket for TLS" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Invalid state" msgstr "" @@ -1307,7 +1306,7 @@ msgstr "" msgid "Layer must be a Group or TileGrid subclass" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "MAC address was invalid" msgstr "" @@ -1667,11 +1666,11 @@ msgstr "" msgid "Operation not permitted" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Operation or feature not supported" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Operation timed out" msgstr "" @@ -1679,7 +1678,7 @@ msgstr "" msgid "Out of MDNS service slots" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Out of memory" msgstr "" @@ -1855,7 +1854,7 @@ msgstr "" msgid "Read-only filesystem" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Received response was invalid" msgstr "" @@ -1875,7 +1874,7 @@ msgstr "" msgid "Requested AES mode is unsupported" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Requested resource not found" msgstr "" @@ -2341,7 +2340,7 @@ msgstr "" msgid "Value length > max_length" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c ports/espressif/esp_error.c +#: ports/espressif/common-hal/espidf/__init__.c msgid "Version was invalid" msgstr "" @@ -2431,11 +2430,6 @@ msgstr "" msgid "addresses is empty" msgstr "" -#: ports/espressif/bindings/espnow/Peers.c -#: ports/espressif/common-hal/espnow/ESPNow.c -msgid "an error occured" -msgstr "" - #: py/compile.c msgid "annotation must be an identifier" msgstr "" diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index d844fc88bd54..cc6d451153e7 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -218,7 +218,6 @@ endif SRC_C += \ background.c \ mphalport.c \ - bindings/espidf/__init__.c \ boards/$(BOARD)/board.c \ boards/$(BOARD)/pins.c \ shared/netutils/netutils.c \ @@ -252,8 +251,6 @@ ifneq ($(CIRCUITPY_BLEIO),0) SRC_C += common-hal/_bleio/ble_events.c endif -SRC_C += $(wildcard common-hal/espidf/*.c) - ifneq ($(CIRCUITPY_ESPCAMERA),0) SRC_CAMERA := \ $(wildcard common-hal/espcamera/*.c) \ @@ -263,6 +260,13 @@ CFLAGS += -isystem esp32-camera/driver/include CFLAGS += -isystem esp32-camera/conversions/include endif +ifneq ($(CIRCUITPY_ESPIDF),0) +SRC_ESPIDF := \ + $(wildcard common-hal/espidf/*.c) \ + $(wildcard bindings/espidf/*.c) +SRC_C += $(SRC_ESPIDF) +endif + ifneq ($(CIRCUITPY_ESPNOW),0) SRC_ESPNOW := \ $(wildcard common-hal/espnow/*.c) \ diff --git a/ports/espressif/bindings/espnow/Peers.c b/ports/espressif/bindings/espnow/Peers.c index 4f0c310f3da5..13c44c3423af 100644 --- a/ports/espressif/bindings/espnow/Peers.c +++ b/ports/espressif/bindings/espnow/Peers.c @@ -28,17 +28,13 @@ #include "py/objlist.h" #include "py/runtime.h" +#include "bindings/espidf/__init__.h" + #include "bindings/espnow/Peer.h" #include "bindings/espnow/Peers.h" #include "esp_now.h" -static void check_esp_err(esp_err_t status) { - if (status != ESP_OK) { - mp_raise_RuntimeError(translate("an error occured")); - } -} - //| class Peers: //| """A class that provides peer managment functions. Sequence[Peer].""" //| @@ -51,7 +47,7 @@ static void check_esp_err(esp_err_t status) { //| ... STATIC mp_obj_t espnow_peers_append(mp_obj_t self_in, mp_obj_t arg) { espnow_peer_obj_t *peer = MP_OBJ_TO_PTR(mp_arg_validate_type(arg, &espnow_peer_type, MP_QSTR_Peer)); - check_esp_err(esp_now_add_peer(&peer->peer_info)); + CHECK_ESP_RESULT(esp_now_add_peer(&peer->peer_info)); espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); return mp_obj_list_append(self->list, arg); } @@ -66,7 +62,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_peers_append_obj, espnow_peers_append); //| STATIC mp_obj_t espnow_peers_remove(mp_obj_t self_in, mp_obj_t arg) { espnow_peer_obj_t *peer = MP_OBJ_TO_PTR(mp_arg_validate_type(arg, &espnow_peer_type, MP_QSTR_Peer)); - check_esp_err(esp_now_del_peer(peer->peer_info.peer_addr)); + CHECK_ESP_RESULT(esp_now_del_peer(peer->peer_info.peer_addr)); espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); return mp_obj_list_remove(self->list, arg); } diff --git a/ports/espressif/common-hal/espidf/__init__.c b/ports/espressif/common-hal/espidf/__init__.c index 183ebb381707..d2d00ebc5618 100644 --- a/ports/espressif/common-hal/espidf/__init__.c +++ b/ports/espressif/common-hal/espidf/__init__.c @@ -29,7 +29,9 @@ #include "supervisor/memory.h" #include "py/runtime.h" +#include "esp_now.h" #include "esp_log.h" + #define TAG "espidf" #ifdef CONFIG_SPIRAM @@ -180,14 +182,19 @@ void raise_esp_error(esp_err_t err) { // tests must be in descending order MP_STATIC_ASSERT(ESP_ERR_FLASH_BASE > ESP_ERR_MESH_BASE); - MP_STATIC_ASSERT(ESP_ERR_MESH_BASE > ESP_ERR_WIFI_BASE); + MP_STATIC_ASSERT(ESP_ERR_MESH_BASE > ESP_ERR_ESPNOW_BASE); + MP_STATIC_ASSERT(ESP_ERR_ESPNOW_BASE > ESP_ERR_WIFI_BASE); + if (err >= ESP_ERR_FLASH_BASE) { group = "Flash"; } else if (err >= ESP_ERR_MESH_BASE) { group = "Mesh"; + } else if (err >= ESP_ERR_ESPNOW_BASE) { + group = "ESP-NOW"; } else if (err >= ESP_ERR_WIFI_BASE) { group = "WiFi"; } + mp_raise_msg_varg(exception_type, translate("%s error 0x%x"), group, err); } diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index b95258f5f5ba..19c0e4a11685 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -30,7 +30,9 @@ #include "py/mperrno.h" #include "py/runtime.h" +#include "bindings/espidf/__init__.h" #include "bindings/espnow/ESPNowPacket.h" + #include "shared-bindings/wifi/__init__.h" #include "common-hal/espnow/ESPNow.h" @@ -41,6 +43,8 @@ #define ESPNOW_MAGIC 0x99 +// TODO: deinit wifi? + // The maximum length of an espnow packet (bytes) #define MAX_PACKET_LEN (sizeof(espnow_packet_t) + ESP_NOW_MAX_DATA_LEN) @@ -66,12 +70,6 @@ typedef struct { uint8_t msg[0]; // Message is up to 250 bytes } __attribute__((packed)) espnow_packet_t; -static void check_esp_err(esp_err_t status) { - if (status != ESP_OK) { - mp_raise_RuntimeError(translate("an error occured")); - } -} - // Return a pointer to the ESPNow module singleton static espnow_obj_t *_get_singleton(void) { return MP_STATE_PORT(espnow_singleton); @@ -152,12 +150,12 @@ void common_hal_espnow_init(espnow_obj_t *self) { common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true); } - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, self->phy_rate)); - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, self->phy_rate)); + CHECK_ESP_RESULT(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, self->phy_rate)); + CHECK_ESP_RESULT(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, self->phy_rate)); - check_esp_err(esp_now_init()); - check_esp_err(esp_now_register_send_cb(send_cb)); - check_esp_err(esp_now_register_recv_cb(recv_cb)); + CHECK_ESP_RESULT(esp_now_init()); + CHECK_ESP_RESULT(esp_now_register_send_cb(send_cb)); + CHECK_ESP_RESULT(esp_now_register_recv_cb(recv_cb)); } // De-initialize the ESP-NOW software stack, @@ -167,9 +165,9 @@ void common_hal_espnow_deinit(espnow_obj_t *self) { return; } - check_esp_err(esp_now_unregister_send_cb()); - check_esp_err(esp_now_unregister_recv_cb()); - check_esp_err(esp_now_deinit()); + CHECK_ESP_RESULT(esp_now_unregister_send_cb()); + CHECK_ESP_RESULT(esp_now_unregister_recv_cb()); + CHECK_ESP_RESULT(esp_now_deinit()); self->recv_buffer->buf = NULL; self->recv_buffer = NULL; @@ -191,7 +189,7 @@ void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value) { }; void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key) { - check_esp_err(esp_now_set_pmk(key)); + CHECK_ESP_RESULT(esp_now_set_pmk(key)); } // --- Send and Receive ESP-NOW data --- @@ -206,7 +204,7 @@ mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const uint8_t *mac, const mp (mp_hal_ticks_ms() - start) <= DEFAULT_SEND_TIMEOUT_MS) { RUN_BACKGROUND_TASKS; } - check_esp_err(err); + CHECK_ESP_RESULT(err); // Increment the sent packet count. // If mac == NULL msg will be sent to all peers EXCEPT any broadcast or multicast addresses. diff --git a/ports/espressif/esp_error.c b/ports/espressif/esp_error.c deleted file mode 100644 index 4bc44674b7fb..000000000000 --- a/ports/espressif/esp_error.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2020 Jeff Epler for Adafruit Industries - * - * 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 "esp_error.h" -#include "py/runtime.h" - -#include "bindings/espidf/__init__.h" - -void raise_esp_error(esp_err_t err) { - const compressed_string_t *msg = NULL; - const mp_obj_type_t *exception_type = &mp_type_espidf_IDFError; - switch (err) { - case ESP_FAIL: - msg = translate("Generic Failure"); - break; - case ESP_ERR_NO_MEM: - exception_type = &mp_type_espidf_MemoryError; - msg = translate("Out of memory"); - break; - case ESP_ERR_INVALID_ARG: - msg = translate("Invalid argument"); - break; - case ESP_ERR_INVALID_STATE: - msg = translate("Invalid state"); - break; - case ESP_ERR_INVALID_SIZE: - msg = translate("Invalid size"); - break; - case ESP_ERR_NOT_FOUND: - msg = translate("Requested resource not found"); - break; - case ESP_ERR_NOT_SUPPORTED: - msg = translate("Operation or feature not supported"); - break; - case ESP_ERR_TIMEOUT: - msg = translate("Operation timed out"); - break; - case ESP_ERR_INVALID_RESPONSE: - msg = translate("Received response was invalid"); - break; - case ESP_ERR_INVALID_CRC: - msg = translate("CRC or checksum was invalid"); - break; - case ESP_ERR_INVALID_VERSION: - msg = translate("Version was invalid"); - break; - case ESP_ERR_INVALID_MAC: - msg = translate("MAC address was invalid"); - break; - } - if (msg) { - mp_raise_msg(exception_type, msg); - } - - const char *group = "ESP-IDF"; - - // tests must be in descending order - MP_STATIC_ASSERT(ESP_ERR_FLASH_BASE > ESP_ERR_MESH_BASE); - MP_STATIC_ASSERT(ESP_ERR_MESH_BASE > ESP_ERR_WIFI_BASE); - if (err >= ESP_ERR_FLASH_BASE) { - group = "Flash"; - } else if (err >= ESP_ERR_MESH_BASE) { - group = "Mesh"; - } else if (err >= ESP_ERR_WIFI_BASE) { - group = "WiFi"; - } - mp_raise_msg_varg(exception_type, translate("%s error 0x%x"), group, err); -} From ff95e9616099e2b7f14b46c69a556714d41359ed Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Sat, 4 Feb 2023 01:03:33 +0530 Subject: [PATCH 13/17] add `ESPNowStats` class and more --- ports/espressif/bindings/espnow/ESPNow.c | 81 +++++++++-------- .../espressif/bindings/espnow/ESPNowPacket.c | 4 +- ports/espressif/bindings/espnow/ESPNowStats.c | 87 +++++++++++++++++++ ports/espressif/bindings/espnow/ESPNowStats.h | 38 ++++++++ ports/espressif/bindings/espnow/Peers.c | 2 + ports/espressif/bindings/espnow/__init__.c | 4 +- ports/espressif/common-hal/espnow/ESPNow.c | 77 ++++++++-------- ports/espressif/common-hal/espnow/ESPNow.h | 11 ++- 8 files changed, 220 insertions(+), 84 deletions(-) create mode 100644 ports/espressif/bindings/espnow/ESPNowStats.c create mode 100644 ports/espressif/bindings/espnow/ESPNowStats.h diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 39eb7a501901..33bb882873d1 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -27,9 +27,8 @@ * THE SOFTWARE. */ -#include "py/runtime.h" -#include "py/objarray.h" #include "py/objproperty.h" +#include "py/runtime.h" #include "py/stream.h" #include "bindings/espnow/ESPNow.h" @@ -74,19 +73,15 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t mp_raise_RuntimeError(translate("Already running")); } + // Allocate a new object self = m_new_obj(espnow_obj_t); self->base.type = &espnow_type; - common_hal_espnow_set_buffer_size(self, args[ARG_buffer_size].u_int); - common_hal_espnow_set_phy_rate(self, args[ARG_phy_rate].u_int); - - self->peers = espnow_peers_new(); + // Construct the object + common_hal_espnow_construct(self, args[ARG_buffer_size].u_int, args[ARG_phy_rate].u_int); // Set the global singleton pointer for the espnow protocol. MP_STATE_PORT(espnow_singleton) = self; - - common_hal_espnow_init(self); - return self; } @@ -167,23 +162,29 @@ MP_PROPERTY_GETSET(espnow_phy_rate_obj, (mp_obj_t)&espnow_get_phy_rate_obj, (mp_obj_t)&espnow_set_phy_rate_obj); -//| stats: Tuple[int, int, int, int, int] -//| """Provide some useful stats in a `tuple` of -//| (tx_packets, tx_responses, tx_failures, rx_packets, rx_failures). (read-only)""" +//| tx_stats: ESPNowStats +//| """The ``TX`` packet statistics.""" +//| +STATIC mp_obj_t espnow_get_tx_stats(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_FROM_PTR(self->tx_stats); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_tx_stats_obj, espnow_get_tx_stats); + +MP_PROPERTY_GETTER(espnow_tx_stats_obj, + (mp_obj_t)&espnow_get_tx_stats_obj); + +//| rx_stats: ESPNowStats +//| """The ``RX`` packet statistics.""" //| -STATIC mp_obj_t espnow_get_stats(mp_obj_t self_in) { +STATIC mp_obj_t espnow_get_rx_stats(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_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->rx_failures)); + return MP_OBJ_FROM_PTR(self->rx_stats); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_stats_obj, espnow_get_stats); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_rx_stats_obj, espnow_get_rx_stats); -MP_PROPERTY_GETTER(espnow_stats_obj, - (mp_obj_t)&espnow_get_stats_obj); +MP_PROPERTY_GETTER(espnow_rx_stats_obj, + (mp_obj_t)&espnow_get_rx_stats_obj); // --- Send and Receive ESP-NOW data --- @@ -195,9 +196,8 @@ MP_PROPERTY_GETTER(espnow_stats_obj, //| """Send a message to the peer's mac address. //| //| :param ReadableBuffer message: The message to send (length <= 250 bytes). -//| :param ReadableBuffer mac: The peer's address (length = 6 bytes). If `None` or any non-true value, send to all registered peers. -//| -//| :raises EAGAIN: if the internal espnow buffers are full.""" +//| :param ReadableBuffer mac: The peer's address (length = 6 bytes). +//| If `None` or any non-true value, send to all registered peers.""" //| ... STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_message, ARG_mac, ARG_sync }; @@ -255,6 +255,7 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&espnow___exit___obj) }, + // Deinit the object { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&espnow_deinit_obj) }, // Config parameters @@ -262,7 +263,9 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_buffer_size), MP_ROM_PTR(&espnow_buffer_size_obj) }, { MP_ROM_QSTR(MP_QSTR_phy_rate), MP_ROM_PTR(&espnow_phy_rate_obj) }, - { MP_ROM_QSTR(MP_QSTR_stats), MP_ROM_PTR(&espnow_stats_obj) }, + // Packet statistics + { MP_ROM_QSTR(MP_QSTR_tx_stats), MP_ROM_PTR(&espnow_tx_stats_obj) }, + { MP_ROM_QSTR(MP_QSTR_rx_stats), MP_ROM_PTR(&espnow_rx_stats_obj) }, // Send and receive messages { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, @@ -278,21 +281,25 @@ STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_dict_table); // 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) { - if (request != MP_STREAM_POLL) { - *errcode = MP_EINVAL; - return MP_STREAM_ERROR; - } - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return (common_hal_espnow_deinited(self)) ? 0 : // If not initialized - arg ^ ( - // If no data in the buffer, unset the Read ready flag - ((!ringbuf_num_filled(self->recv_buffer)) ? MP_STREAM_POLL_RD : 0) | - // If still waiting for responses, unset the Write ready flag - ((self->tx_responses < self->tx_packets) ? MP_STREAM_POLL_WR : 0)); + check_for_deinit(self); + switch (request) { + case MP_STREAM_POLL: { + mp_uint_t flags = arg; + mp_uint_t ret = 0; + if ((flags & MP_STREAM_POLL_RD) && ringbuf_num_filled(self->recv_buffer) > 0) { + ret |= MP_STREAM_POLL_RD; + } + return ret; + } + default: + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } } STATIC const mp_stream_p_t espnow_stream_p = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream) .ioctl = espnow_stream_ioctl, }; diff --git a/ports/espressif/bindings/espnow/ESPNowPacket.c b/ports/espressif/bindings/espnow/ESPNowPacket.c index 9d582a00eb9f..7cbf2caed02d 100644 --- a/ports/espressif/bindings/espnow/ESPNowPacket.c +++ b/ports/espressif/bindings/espnow/ESPNowPacket.c @@ -27,7 +27,7 @@ #include "bindings/espnow/ESPNowPacket.h" //| class ESPNowPacket: -//| """A packet retreived from ESP-NOW communication protocol""" +//| """A packet retrieved from ESP-NOW communication protocol""" //| //| mac: ReadableBuffer //| """The sender's mac address (length = 6 bytes)""" @@ -39,7 +39,7 @@ //| """The received signal strength indication (in dBm from -127 to 0)""" //| //| time: int -//| """The is the time in milliseconds since device last booted""" +//| """The time in milliseconds since the device last booted when the packet was received""" //| const mp_obj_namedtuple_type_t espnow_packet_type_obj = { diff --git a/ports/espressif/bindings/espnow/ESPNowStats.c b/ports/espressif/bindings/espnow/ESPNowStats.c new file mode 100644 index 000000000000..babd8e4d8358 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNowStats.c @@ -0,0 +1,87 @@ +/* + * This file is part of the CircuitPython project, https://github.com/adafruit/circuitpython + * + * 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 "bindings/espnow/ESPNowStats.h" +#include "py/objproperty.h" + +//| class ESPNowStats: +//| """Provide some useful packet tx/rx statistics.""" +//| + +//| success: int +//| """The number of successes. (read-only) +//| +//| * In case of transmit ``TX``: +//| The number of tx packets received by the peer(s) ``ESP_NOW_SEND_SUCCESS``. +//| +//| * In case of receive ``RX``: +//| The number of rx packets captured in the buffer.""" +//| +STATIC mp_obj_t espnow_stats_get_success(const mp_obj_t self_in) { + espnow_stats_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->success); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_get_success_obj, espnow_stats_get_success); + +MP_PROPERTY_GETTER(espnow_stats_success_obj, + (mp_obj_t)&espnow_stats_get_success_obj); + +//| failure: int +//| """The number of failures. (read-only) +//| +//| * In case of transmit ``TX``: +//| The number of failed tx packets ``ESP_NOW_SEND_FAIL``. +//| +//| * In case of receive ``RX``: +//| The number of dropped rx packets due to buffer overflow.""" +//| +STATIC mp_obj_t espnow_stats_get_failure(const mp_obj_t self_in) { + espnow_stats_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->failure); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_get_failure_obj, espnow_stats_get_failure); + +MP_PROPERTY_GETTER(espnow_stats_failure_obj, + (mp_obj_t)&espnow_stats_get_failure_obj); + +STATIC const mp_rom_map_elem_t espnow_stats_locals_dict_table[] = { + // Peer parameters + { MP_ROM_QSTR(MP_QSTR_success), MP_ROM_PTR(&espnow_stats_success_obj) }, + { MP_ROM_QSTR(MP_QSTR_failure), MP_ROM_PTR(&espnow_stats_failure_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(espnow_stats_locals_dict, espnow_stats_locals_dict_table); + +espnow_stats_obj_t *espnow_stats_new(void) { + espnow_stats_obj_t *self = m_new_obj(espnow_stats_obj_t); + self->base.type = &espnow_stats_type; + return self; +} + +const mp_obj_type_t espnow_stats_type = { + { &mp_type_type }, + .name = MP_QSTR_ESPNowStats, + .locals_dict = (mp_obj_t)&espnow_stats_locals_dict, +}; diff --git a/ports/espressif/bindings/espnow/ESPNowStats.h b/ports/espressif/bindings/espnow/ESPNowStats.h new file mode 100644 index 000000000000..a9a578a8bfc3 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNowStats.h @@ -0,0 +1,38 @@ +/* + * 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 + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + volatile size_t success; + volatile size_t failure; +} espnow_stats_obj_t; + +const mp_obj_type_t espnow_stats_type; +extern espnow_stats_obj_t *espnow_stats_new(void); diff --git a/ports/espressif/bindings/espnow/Peers.c b/ports/espressif/bindings/espnow/Peers.c index 13c44c3423af..843bff644a98 100644 --- a/ports/espressif/bindings/espnow/Peers.c +++ b/ports/espressif/bindings/espnow/Peers.c @@ -35,6 +35,8 @@ #include "esp_now.h" +// TODO: Check for deinit + //| class Peers: //| """A class that provides peer managment functions. Sequence[Peer].""" //| diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c index d92221c6913e..f64a6433ddc9 100644 --- a/ports/espressif/bindings/espnow/__init__.c +++ b/ports/espressif/bindings/espnow/__init__.c @@ -29,6 +29,7 @@ #include "bindings/espnow/__init__.h" #include "bindings/espnow/ESPNow.h" #include "bindings/espnow/ESPNowPacket.h" +#include "bindings/espnow/ESPNowStats.h" #include "bindings/espnow/Peer.h" #include "bindings/espnow/Peers.h" @@ -51,7 +52,7 @@ //| //| e.send("Starting...") //| for i in range(100): -//| e.send(peer, str(i)*20, True) +//| e.send(str(i)*20) //| e.send(b'end') //| //| **Receiver** @@ -84,6 +85,7 @@ STATIC const mp_rom_map_elem_t espnow_module_globals_table[] = { // module classes { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&espnow_type) }, { MP_ROM_QSTR(MP_QSTR_ESPNowPacket),MP_ROM_PTR(&espnow_packet_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_ESPNowStats), MP_ROM_PTR(&espnow_stats_type) }, { MP_ROM_QSTR(MP_QSTR_Peer), MP_ROM_PTR(&espnow_peer_type) }, { MP_ROM_QSTR(MP_QSTR_Peers), MP_ROM_PTR(&espnow_peers_type) }, }; diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index 19c0e4a11685..96a7cfdfaab3 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -45,14 +45,15 @@ // TODO: deinit wifi? -// The maximum length of an espnow packet (bytes) +// The min/max length of an espnow packet (bytes) +#define MIN_PACKET_LEN (sizeof(espnow_packet_t)) #define MAX_PACKET_LEN (sizeof(espnow_packet_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) -// Time to wait (millisec) for responses from sent packets: (2 seconds). +// Time to wait (millisec) for responses from sent packets: (1 seconds). #define DEFAULT_SEND_TIMEOUT_MS (1000) // ESPNow packet format for the receive buffer. @@ -70,27 +71,34 @@ typedef struct { uint8_t msg[0]; // Message is up to 250 bytes } __attribute__((packed)) espnow_packet_t; -// Return a pointer to the ESPNow module singleton -static espnow_obj_t *_get_singleton(void) { - return MP_STATE_PORT(espnow_singleton); -} - // --- The ESP-NOW send and recv callback routines --- // 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, esp_now_send_status_t status) { - espnow_obj_t *self = _get_singleton(); - self->tx_responses++; - if (status != ESP_NOW_SEND_SUCCESS) { - self->tx_failures++; + espnow_obj_t *self = MP_STATE_PORT(espnow_singleton); + if (status == ESP_NOW_SEND_SUCCESS) { + self->tx_stats->success++; + } else { + self->tx_stats->failure++; } } -// Get the RSSI value from the wifi packet header -static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { - // Warning: Secret magic to get the rssi from the wifi packet header +// 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. +static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { + espnow_obj_t *self = MP_STATE_PORT(espnow_singleton); + ringbuf_t *buf = self->recv_buffer; + + if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { + self->rx_stats->failure++; + return; + } + + // Get the RSSI value from the wifi packet header + // Secret magic to get the rssi from the wifi packet header // See espnow.c:espnow_recv_cb() at https://github.com/espressif/esp-now/ // In the wifi packet the msg comes after a wifi_promiscuous_pkt_t // and a espnow_frame_format_t. @@ -101,38 +109,37 @@ static inline int8_t _get_rssi_from_wifi_packet(const uint8_t *msg) { wifi_promiscuous_pkt_t *wifi_packet = (wifi_promiscuous_pkt_t *)( msg - SIZEOF_ESPNOW_FRAME_FORMAT - sizeof(wifi_promiscuous_pkt_t)); #pragma GCC diagnostic pop - return wifi_packet->rx_ctrl.rssi; -} - -// 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. -static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { - espnow_obj_t *self = _get_singleton(); - ringbuf_t *buf = self->recv_buffer; - - if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { - self->rx_failures++; - return; - } espnow_header_t header; header.magic = ESPNOW_MAGIC; header.msg_len = msg_len; - header.rssi = _get_rssi_from_wifi_packet(msg); + header.rssi = wifi_packet->rx_ctrl.rssi; header.time_ms = mp_hal_ticks_ms(); ringbuf_put_n(buf, (uint8_t *)&header, sizeof(header)); ringbuf_put_n(buf, mac, ESP_NOW_ETH_ALEN); ringbuf_put_n(buf, msg, msg_len); - self->rx_packets++; + self->rx_stats->success++; } bool common_hal_espnow_deinited(espnow_obj_t *self) { return self == NULL || self->recv_buffer == NULL; } +// Construct the ESPNow object +void common_hal_espnow_construct(espnow_obj_t *self, mp_int_t buffer_size, mp_int_t phy_rate) { + common_hal_espnow_set_buffer_size(self, buffer_size); + common_hal_espnow_set_phy_rate(self, phy_rate); + + self->tx_stats = espnow_stats_new(); + self->rx_stats = espnow_stats_new(); + + self->peers = espnow_peers_new(); + + common_hal_espnow_init(self); +} + // Initialize the ESP-NOW software stack, // register callbacks and allocate the recv data buffers. void common_hal_espnow_init(espnow_obj_t *self) { @@ -171,17 +178,15 @@ void common_hal_espnow_deinit(espnow_obj_t *self) { self->recv_buffer->buf = NULL; self->recv_buffer = NULL; - // self->peers_count = 0; // esp_now_deinit() removes all peers. - self->tx_packets = self->tx_responses; } void espnow_reset(void) { - common_hal_espnow_deinit(_get_singleton()); + common_hal_espnow_deinit(MP_STATE_PORT(espnow_singleton)); MP_STATE_PORT(espnow_singleton) = NULL; } void common_hal_espnow_set_buffer_size(espnow_obj_t *self, mp_int_t value) { - self->recv_buffer_size = mp_arg_validate_int_min(value, MAX_PACKET_LEN, MP_QSTR_buffer_size); + self->recv_buffer_size = mp_arg_validate_int_min(value, MIN_PACKET_LEN, MP_QSTR_buffer_size); }; void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value) { @@ -206,10 +211,6 @@ mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const uint8_t *mac, const mp } CHECK_ESP_RESULT(err); - // Increment the sent packet count. - // If mac == NULL msg will be sent to all peers EXCEPT any broadcast or multicast addresses. - self->tx_packets += ((mac == NULL) ? ((mp_obj_list_t *)self->peers->list)->len : 1); - return mp_const_none; } diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index 02ca90d5c411..87e3fb7168db 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -29,6 +29,7 @@ #include "py/obj.h" #include "py/ringbuf.h" +#include "bindings/espnow/ESPNowStats.h" #include "bindings/espnow/Peers.h" #include "esp_wifi.h" @@ -39,16 +40,14 @@ typedef struct _espnow_obj_t { ringbuf_t *recv_buffer; // A buffer for received packets size_t recv_buffer_size; // The size of the recv_buffer wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. - volatile size_t rx_packets; // # of received packets - 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 - espnow_peers_obj_t *peers; // Cache the # of peers for send(sync=True) + espnow_stats_obj_t *tx_stats; // The tx packet stats + espnow_stats_obj_t *rx_stats; // The rx packet stats + espnow_peers_obj_t *peers; // The sequence of peers } espnow_obj_t; extern void espnow_reset(void); +extern void common_hal_espnow_construct(espnow_obj_t *self, mp_int_t buffer_size, mp_int_t phy_rate); extern void common_hal_espnow_init(espnow_obj_t *self); extern void common_hal_espnow_deinit(espnow_obj_t *self); extern bool common_hal_espnow_deinited(espnow_obj_t *self); From c0a9c71057be49e87327ec487e5f924d9d3d9e7c Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Sun, 5 Feb 2023 01:18:18 +0530 Subject: [PATCH 14/17] replace `ESPNowStats` with `Communicate` class and more --- ports/espressif/bindings/espnow/Communicate.c | 197 ++++++++++++++++++ .../espnow/{ESPNowStats.h => Communicate.h} | 7 +- ports/espressif/bindings/espnow/ESPNow.c | 105 ++-------- .../espressif/bindings/espnow/ESPNowPacket.c | 10 +- ports/espressif/bindings/espnow/ESPNowStats.c | 87 -------- ports/espressif/bindings/espnow/Peers.c | 17 +- ports/espressif/bindings/espnow/__init__.c | 8 +- ports/espressif/common-hal/espnow/ESPNow.c | 18 +- ports/espressif/common-hal/espnow/ESPNow.h | 16 +- ports/espressif/common-hal/espnow/__init__.c | 9 + ports/espressif/common-hal/espnow/__init__.h | 4 +- 11 files changed, 264 insertions(+), 214 deletions(-) create mode 100644 ports/espressif/bindings/espnow/Communicate.c rename ports/espressif/bindings/espnow/{ESPNowStats.h => Communicate.h} (91%) delete mode 100644 ports/espressif/bindings/espnow/ESPNowStats.c diff --git a/ports/espressif/bindings/espnow/Communicate.c b/ports/espressif/bindings/espnow/Communicate.c new file mode 100644 index 000000000000..2e6e63fe4673 --- /dev/null +++ b/ports/espressif/bindings/espnow/Communicate.c @@ -0,0 +1,197 @@ +/* + * This file is part of the CircuitPython project, https://github.com/adafruit/circuitpython + * + * 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 "py/objproperty.h" +#include "py/runtime.h" + +#include "bindings/espnow/Peer.h" +#include "common-hal/espnow/__init__.h" + +// --- Send and Receive ESP-NOW data --- + +//| class Communicate: +//| """Provides methods and statistics related to communication +//| with the ESP-NOW peers. +//| +//| Dictionary: +//| * "Send" = "Transmit" = ``TX`` +//| * "Recv" = "Receive" = ``RX`` +//| """ +//| +//| def __init__(self) -> None: +//| """You cannot create an instance of `Communicate`.""" +//| ... + +//| job: str +//| """Used to decide whether to call `__send` or `__recv`. (read-only)""" +//| +STATIC mp_obj_t espnow_com_get_job(const mp_obj_t self_in) { + espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_QSTR(self->job); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_com_get_job_obj, espnow_com_get_job); + +MP_PROPERTY_GETTER(espnow_com_job_obj, + (mp_obj_t)&espnow_com_get_job_obj); + +//| success: int +//| """The number of successes. (read-only) +//| +//| * In case of transmit ``TX``: +//| The number of tx packets received by the peer(s) ``ESP_NOW_SEND_SUCCESS``. +//| +//| * In case of receive ``RX``: +//| The number of rx packets captured in the buffer.""" +//| +STATIC mp_obj_t espnow_com_get_success(const mp_obj_t self_in) { + espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->success); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_com_get_success_obj, espnow_com_get_success); + +MP_PROPERTY_GETTER(espnow_com_success_obj, + (mp_obj_t)&espnow_com_get_success_obj); + +//| failure: int +//| """The number of failures. (read-only) +//| +//| * In case of transmit ``TX``: +//| The number of failed tx packets ``ESP_NOW_SEND_FAIL``. +//| +//| * In case of receive ``RX``: +//| The number of dropped rx packets due to buffer overflow.""" +//| +STATIC mp_obj_t espnow_com_get_failure(const mp_obj_t self_in) { + espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->failure); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_com_get_failure_obj, espnow_com_get_failure); + +MP_PROPERTY_GETTER(espnow_com_failure_obj, + (mp_obj_t)&espnow_com_get_failure_obj); + +//| def __send( +//| self, +//| message: ReadableBuffer, +//| peer: Peer, +//| ) -> bool: +//| """Send a message to the peer's mac address. +//| +//| :param ReadableBuffer message: The message to send (length <= 250 bytes). +//| :param Peer peer: Send message to this peer. If `None`, send to all registered peers. +//| """ +//| ... +STATIC mp_obj_t espnow_com___send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_message, ARG_peer }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_message, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_peer, MP_ARG_OBJ, { .u_obj = mp_const_none } }, + }; + + 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); + + espnow_obj_t *self = pos_args[0]; + common_hal_espnow_check_for_deinit(self); + + // Get a pointer to the data buffer of the message + mp_buffer_info_t message; + mp_get_buffer_raise(args[ARG_message].u_obj, &message, MP_BUFFER_READ); + + const uint8_t *mac = NULL; + if (args[ARG_peer].u_obj != mp_const_none) { + const espnow_peer_obj_t *peer = MP_OBJ_FROM_PTR(mp_arg_validate_type_or_none(args[ARG_peer].u_obj, &espnow_peer_type, MP_QSTR_peer)); + mac = peer->peer_info.peer_addr; + } + + return common_hal_espnow_send(self, &message, mac); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_com___send_obj, 2, espnow_com___send); + +//| def __recv(self) -> Optional[ESPNowPacket]: +//| """Receive a message from the peer(s). +//| +//| :returns: An `ESPNowPacket` if available in the buffer, otherwise `None`.""" +//| ... +STATIC mp_obj_t espnow_com___recv(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_check_for_deinit(self); + + return common_hal_espnow_recv(self); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_com___recv_obj, espnow_com___recv); + +STATIC const mp_rom_map_elem_t espnow_com_locals_dict_table[] = { + // Config parameters + { MP_ROM_QSTR(MP_QSTR_job), MP_ROM_PTR(&espnow_com_job_obj) }, + + // Packet statistics + { MP_ROM_QSTR(MP_QSTR_success), MP_ROM_PTR(&espnow_com_success_obj) }, + { MP_ROM_QSTR(MP_QSTR_failure), MP_ROM_PTR(&espnow_com_failure_obj) }, + + // Communication methods + { MP_ROM_QSTR(MP_QSTR___send), MP_ROM_PTR(&espnow_com___send_obj) }, + { MP_ROM_QSTR(MP_QSTR___recv), MP_ROM_PTR(&espnow_com___recv_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(espnow_com_locals_dict, espnow_com_locals_dict_table); + +//| def __call__(self, *args: Optional[Any], **kwargs: Optional[Any]) -> Optional[Any]: +//| """Calls the private `__send` or `__recv` methods with the supplied ``args`` and ``kwargs`` +//| based on whether the job parameter is set to ``send`` or ``recv``.""" +//| ... +//| +STATIC mp_obj_t espnow_com_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t meth = NULL; + switch (self->job) { + case MP_QSTR_send: + meth = MP_OBJ_FROM_PTR(&espnow_com___send_obj); + break; + case MP_QSTR_recv: + meth = MP_OBJ_FROM_PTR(&espnow_com___recv_obj); + break; + default: + break; + } + return meth ? mp_call_method_self_n_kw(meth, MP_STATE_PORT(espnow_singleton), n_args, n_kw, args) : mp_const_none; +} + +espnow_com_obj_t *espnow_com_new(qstr job) { + espnow_com_obj_t *self = m_new_obj(espnow_com_obj_t); + self->base.type = &espnow_com_type; + self->job = job; + return self; +} + +const mp_obj_type_t espnow_com_type = { + { &mp_type_type }, + .name = MP_QSTR_Communicate, + .locals_dict = (mp_obj_t)&espnow_com_locals_dict, + .flags = MP_TYPE_FLAG_EXTENDED, + MP_TYPE_EXTENDED_FIELDS( + .call = &espnow_com_call, + ), +}; diff --git a/ports/espressif/bindings/espnow/ESPNowStats.h b/ports/espressif/bindings/espnow/Communicate.h similarity index 91% rename from ports/espressif/bindings/espnow/ESPNowStats.h rename to ports/espressif/bindings/espnow/Communicate.h index a9a578a8bfc3..194784f8b496 100644 --- a/ports/espressif/bindings/espnow/ESPNowStats.h +++ b/ports/espressif/bindings/espnow/Communicate.h @@ -32,7 +32,8 @@ typedef struct { mp_obj_base_t base; volatile size_t success; volatile size_t failure; -} espnow_stats_obj_t; + qstr job; +} espnow_com_obj_t; -const mp_obj_type_t espnow_stats_type; -extern espnow_stats_obj_t *espnow_stats_new(void); +const mp_obj_type_t espnow_com_type; +extern espnow_com_obj_t *espnow_com_new(qstr job); diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 33bb882873d1..8eb82af31a87 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -32,22 +32,12 @@ #include "py/stream.h" #include "bindings/espnow/ESPNow.h" - -#include "shared-bindings/util.h" - #include "common-hal/espnow/__init__.h" -#include "common-hal/espnow/ESPNow.h" #include "esp_now.h" // --- Initialisation and Config functions --- -static void check_for_deinit(espnow_obj_t *self) { - if (common_hal_espnow_deinited(self)) { - raise_deinited_error(); - } -} - //| class ESPNow: //| """Provides access to the ESP-NOW protocol.""" //| @@ -82,7 +72,7 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t // Set the global singleton pointer for the espnow protocol. MP_STATE_PORT(espnow_singleton) = self; - return self; + return MP_OBJ_FROM_PTR(self); } //| def deinit(self) -> None: @@ -90,6 +80,7 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t //| ... STATIC mp_obj_t espnow_deinit(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_check_for_deinit(self); common_hal_espnow_deinit(self); return mp_const_none; } @@ -117,6 +108,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow___exit___obj, 4, 4, espnow_obj //| ... STATIC mp_obj_t espnow_set_pmk(mp_obj_t self_in, mp_obj_t key) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_check_for_deinit(self); common_hal_espnow_set_pmk(self, common_hal_espnow_get_bytes_len(key, ESP_NOW_KEY_LEN)); return mp_const_none; } @@ -133,6 +125,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_buffer_size_obj, espnow_get_buffer_size); STATIC mp_obj_t espnow_set_buffer_size(const mp_obj_t self_in, const mp_obj_t value) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_check_for_deinit(self); common_hal_espnow_set_buffer_size(self, mp_obj_get_int(value)); return mp_const_none; } @@ -153,6 +146,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_phy_rate_obj, espnow_get_phy_rate); STATIC mp_obj_t espnow_set_phy_rate(const mp_obj_t self_in, const mp_obj_t value) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_check_for_deinit(self); common_hal_espnow_set_phy_rate(self, mp_obj_get_int(value)); return mp_const_none; } @@ -162,78 +156,29 @@ MP_PROPERTY_GETSET(espnow_phy_rate_obj, (mp_obj_t)&espnow_get_phy_rate_obj, (mp_obj_t)&espnow_set_phy_rate_obj); -//| tx_stats: ESPNowStats -//| """The ``TX`` packet statistics.""" +//| send: Communicate +//| """A `Communicate` object with ``job`` set to ``send``. (read-only)""" //| -STATIC mp_obj_t espnow_get_tx_stats(mp_obj_t self_in) { +STATIC mp_obj_t espnow_get_send(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(self->tx_stats); + return MP_OBJ_FROM_PTR(self->send); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_tx_stats_obj, espnow_get_tx_stats); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_send_obj, espnow_get_send); -MP_PROPERTY_GETTER(espnow_tx_stats_obj, - (mp_obj_t)&espnow_get_tx_stats_obj); +MP_PROPERTY_GETTER(espnow_send_call_obj, + (mp_obj_t)&espnow_get_send_obj); -//| rx_stats: ESPNowStats -//| """The ``RX`` packet statistics.""" +//| recv: Communicate +//| """A `Communicate` object with ``job`` set to ``recv``. (read-only)""" //| -STATIC mp_obj_t espnow_get_rx_stats(mp_obj_t self_in) { +STATIC mp_obj_t espnow_get_recv(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(self->rx_stats); + return MP_OBJ_FROM_PTR(self->recv); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_rx_stats_obj, espnow_get_rx_stats); - -MP_PROPERTY_GETTER(espnow_rx_stats_obj, - (mp_obj_t)&espnow_get_rx_stats_obj); - -// --- Send and Receive ESP-NOW data --- +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_recv_obj, espnow_get_recv); -//| def send( -//| self, -//| message: ReadableBuffer, -//| mac: Optional[ReadableBuffer], -//| ) -> bool: -//| """Send a message to the peer's mac address. -//| -//| :param ReadableBuffer message: The message to send (length <= 250 bytes). -//| :param ReadableBuffer mac: The peer's address (length = 6 bytes). -//| If `None` or any non-true value, send to all registered peers.""" -//| ... -STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_message, ARG_mac, ARG_sync }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_message, MP_ARG_OBJ | MP_ARG_REQUIRED }, - { MP_QSTR_mac, MP_ARG_OBJ, { .u_obj = mp_const_none } }, - }; - - 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); - - espnow_obj_t *self = pos_args[0]; - check_for_deinit(self); - - const uint8_t *peer_addr = common_hal_espnow_get_bytes_len(args[ARG_mac].u_obj, ESP_NOW_ETH_ALEN); - - // Get a pointer to the data buffer of the message - mp_buffer_info_t message; - mp_get_buffer_raise(args[ARG_message].u_obj, &message, MP_BUFFER_READ); - - return common_hal_espnow_send(self, peer_addr, &message); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); - -//| def recv(self) -> Optional[ESPNowPacket]: -//| """Receive a message from the peer(s). -//| -//| :returns: An `ESPNowPacket` if available in the buffer, otherwise `None`.""" -//| ... -STATIC mp_obj_t espnow_recv(mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_for_deinit(self); - - return common_hal_espnow_recv(self); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_recv_obj, espnow_recv); +MP_PROPERTY_GETTER(espnow_recv_call_obj, + (mp_obj_t)&espnow_get_recv_obj); // --- Peer Related Properties --- @@ -242,7 +187,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_recv_obj, espnow_recv); //| 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); return MP_OBJ_FROM_PTR(self->peers); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers); @@ -263,13 +207,9 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_buffer_size), MP_ROM_PTR(&espnow_buffer_size_obj) }, { MP_ROM_QSTR(MP_QSTR_phy_rate), MP_ROM_PTR(&espnow_phy_rate_obj) }, - // Packet statistics - { MP_ROM_QSTR(MP_QSTR_tx_stats), MP_ROM_PTR(&espnow_tx_stats_obj) }, - { MP_ROM_QSTR(MP_QSTR_rx_stats), MP_ROM_PTR(&espnow_rx_stats_obj) }, - // Send and receive messages - { 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_send), MP_ROM_PTR(&espnow_send_call_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&espnow_recv_call_obj) }, // Peer related properties { MP_ROM_QSTR(MP_QSTR_peers), MP_ROM_PTR(&espnow_peers_obj) }, @@ -282,7 +222,7 @@ STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_dict_table); // 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) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_for_deinit(self); + common_hal_espnow_check_for_deinit(self); switch (request) { case MP_STREAM_POLL: { mp_uint_t flags = arg; @@ -314,6 +254,7 @@ STATIC const mp_stream_p_t espnow_stream_p = { //| STATIC mp_obj_t espnow_unary_op(mp_unary_op_t op, mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_espnow_check_for_deinit(self); size_t len = ringbuf_num_filled(self->recv_buffer); switch (op) { case MP_UNARY_OP_BOOL: diff --git a/ports/espressif/bindings/espnow/ESPNowPacket.c b/ports/espressif/bindings/espnow/ESPNowPacket.c index 7cbf2caed02d..f8bc8e8418d0 100644 --- a/ports/espressif/bindings/espnow/ESPNowPacket.c +++ b/ports/espressif/bindings/espnow/ESPNowPacket.c @@ -27,19 +27,19 @@ #include "bindings/espnow/ESPNowPacket.h" //| class ESPNowPacket: -//| """A packet retrieved from ESP-NOW communication protocol""" +//| """A packet retrieved from ESP-NOW communication protocol. A namedtuple.""" //| //| mac: ReadableBuffer -//| """The sender's mac address (length = 6 bytes)""" +//| """The sender's mac address (length = 6 bytes).""" //| //| msg: ReadableBuffer -//| """The message sent by the peer (length <= 250 bytes)""" +//| """The message sent by the peer (length <= 250 bytes).""" //| //| rssi: int -//| """The received signal strength indication (in dBm from -127 to 0)""" +//| """The received signal strength indication (in dBm from -127 to 0).""" //| //| time: int -//| """The time in milliseconds since the device last booted when the packet was received""" +//| """The time in milliseconds since the device last booted when the packet was received.""" //| const mp_obj_namedtuple_type_t espnow_packet_type_obj = { diff --git a/ports/espressif/bindings/espnow/ESPNowStats.c b/ports/espressif/bindings/espnow/ESPNowStats.c deleted file mode 100644 index babd8e4d8358..000000000000 --- a/ports/espressif/bindings/espnow/ESPNowStats.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This file is part of the CircuitPython project, https://github.com/adafruit/circuitpython - * - * 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 "bindings/espnow/ESPNowStats.h" -#include "py/objproperty.h" - -//| class ESPNowStats: -//| """Provide some useful packet tx/rx statistics.""" -//| - -//| success: int -//| """The number of successes. (read-only) -//| -//| * In case of transmit ``TX``: -//| The number of tx packets received by the peer(s) ``ESP_NOW_SEND_SUCCESS``. -//| -//| * In case of receive ``RX``: -//| The number of rx packets captured in the buffer.""" -//| -STATIC mp_obj_t espnow_stats_get_success(const mp_obj_t self_in) { - espnow_stats_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_int_from_uint(self->success); -} -MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_get_success_obj, espnow_stats_get_success); - -MP_PROPERTY_GETTER(espnow_stats_success_obj, - (mp_obj_t)&espnow_stats_get_success_obj); - -//| failure: int -//| """The number of failures. (read-only) -//| -//| * In case of transmit ``TX``: -//| The number of failed tx packets ``ESP_NOW_SEND_FAIL``. -//| -//| * In case of receive ``RX``: -//| The number of dropped rx packets due to buffer overflow.""" -//| -STATIC mp_obj_t espnow_stats_get_failure(const mp_obj_t self_in) { - espnow_stats_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_int_from_uint(self->failure); -} -MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_get_failure_obj, espnow_stats_get_failure); - -MP_PROPERTY_GETTER(espnow_stats_failure_obj, - (mp_obj_t)&espnow_stats_get_failure_obj); - -STATIC const mp_rom_map_elem_t espnow_stats_locals_dict_table[] = { - // Peer parameters - { MP_ROM_QSTR(MP_QSTR_success), MP_ROM_PTR(&espnow_stats_success_obj) }, - { MP_ROM_QSTR(MP_QSTR_failure), MP_ROM_PTR(&espnow_stats_failure_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(espnow_stats_locals_dict, espnow_stats_locals_dict_table); - -espnow_stats_obj_t *espnow_stats_new(void) { - espnow_stats_obj_t *self = m_new_obj(espnow_stats_obj_t); - self->base.type = &espnow_stats_type; - return self; -} - -const mp_obj_type_t espnow_stats_type = { - { &mp_type_type }, - .name = MP_QSTR_ESPNowStats, - .locals_dict = (mp_obj_t)&espnow_stats_locals_dict, -}; diff --git a/ports/espressif/bindings/espnow/Peers.c b/ports/espressif/bindings/espnow/Peers.c index 843bff644a98..e3f858c1c940 100644 --- a/ports/espressif/bindings/espnow/Peers.c +++ b/ports/espressif/bindings/espnow/Peers.c @@ -40,6 +40,9 @@ //| class Peers: //| """A class that provides peer managment functions. Sequence[Peer].""" //| +//| def __init__(self) -> None: +//| """You cannot create an instance of `Peers`.""" +//| ... //| def append(self, peer: Peer) -> None: //| """Append peer. @@ -195,7 +198,6 @@ const mp_obj_type_t espnow_peers_type = { { &mp_type_type }, .name = MP_QSTR_Peers, .print = espnow_peers_print, - // .make_new = espnow_peers_make_new, .locals_dict = (mp_obj_t)&espnow_peers_locals_dict, .flags = MP_TYPE_FLAG_EXTENDED, MP_TYPE_EXTENDED_FIELDS( @@ -204,16 +206,3 @@ const mp_obj_type_t espnow_peers_type = { .getiter = espnow_peers_getiter, ), }; - -/* -STATIC mp_obj_t espnow_peers_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - (void)type_in; - mp_arg_check_num(n_args, n_kw, 0, 1, false); - - espnow_peers_obj_t *self = m_new_obj(espnow_peers_obj_t); - self->base.type = &espnow_peers_type; - self->list = mp_obj_new_list_from_iter(args[0]); - - return MP_OBJ_FROM_PTR(self); -} -*/ diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c index f64a6433ddc9..2b95085c5d0e 100644 --- a/ports/espressif/bindings/espnow/__init__.c +++ b/ports/espressif/bindings/espnow/__init__.c @@ -24,12 +24,10 @@ * THE SOFTWARE. */ -#include "shared-bindings/util.h" - #include "bindings/espnow/__init__.h" #include "bindings/espnow/ESPNow.h" #include "bindings/espnow/ESPNowPacket.h" -#include "bindings/espnow/ESPNowStats.h" +#include "bindings/espnow/Communicate.h" #include "bindings/espnow/Peer.h" #include "bindings/espnow/Peers.h" @@ -51,7 +49,7 @@ //| e.peers.append(peer) //| //| e.send("Starting...") -//| for i in range(100): +//| for i in range(10): //| e.send(str(i)*20) //| e.send(b'end') //| @@ -85,7 +83,7 @@ STATIC const mp_rom_map_elem_t espnow_module_globals_table[] = { // module classes { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&espnow_type) }, { MP_ROM_QSTR(MP_QSTR_ESPNowPacket),MP_ROM_PTR(&espnow_packet_type_obj) }, - { MP_ROM_QSTR(MP_QSTR_ESPNowStats), MP_ROM_PTR(&espnow_stats_type) }, + { MP_ROM_QSTR(MP_QSTR_Communicate), MP_ROM_PTR(&espnow_com_type) }, { MP_ROM_QSTR(MP_QSTR_Peer), MP_ROM_PTR(&espnow_peer_type) }, { MP_ROM_QSTR(MP_QSTR_Peers), MP_ROM_PTR(&espnow_peers_type) }, }; diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index 96a7cfdfaab3..df766ad32fd0 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -53,8 +53,8 @@ // Will allocate an additional 7 bytes for buffer overhead #define DEFAULT_RECV_BUFFER_SIZE (2 * MAX_PACKET_LEN) -// Time to wait (millisec) for responses from sent packets: (1 seconds). -#define DEFAULT_SEND_TIMEOUT_MS (1000) +// Time to wait (millisec) for responses from sent packets: (2 seconds). +#define DEFAULT_SEND_TIMEOUT_MS (2000) // ESPNow packet format for the receive buffer. // Use this for peeking at the header of the next packet in the buffer. @@ -79,9 +79,9 @@ typedef struct { static void send_cb(const uint8_t *mac, esp_now_send_status_t status) { espnow_obj_t *self = MP_STATE_PORT(espnow_singleton); if (status == ESP_NOW_SEND_SUCCESS) { - self->tx_stats->success++; + self->send->success++; } else { - self->tx_stats->failure++; + self->send->failure++; } } @@ -93,7 +93,7 @@ static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { ringbuf_t *buf = self->recv_buffer; if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { - self->rx_stats->failure++; + self->recv->failure++; return; } @@ -120,7 +120,7 @@ static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { ringbuf_put_n(buf, mac, ESP_NOW_ETH_ALEN); ringbuf_put_n(buf, msg, msg_len); - self->rx_stats->success++; + self->recv->success++; } bool common_hal_espnow_deinited(espnow_obj_t *self) { @@ -132,8 +132,8 @@ void common_hal_espnow_construct(espnow_obj_t *self, mp_int_t buffer_size, mp_in common_hal_espnow_set_buffer_size(self, buffer_size); common_hal_espnow_set_phy_rate(self, phy_rate); - self->tx_stats = espnow_stats_new(); - self->rx_stats = espnow_stats_new(); + self->send = espnow_com_new(MP_QSTR_send); + self->recv = espnow_com_new(MP_QSTR_recv); self->peers = espnow_peers_new(); @@ -200,7 +200,7 @@ void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key) { // --- Send and Receive ESP-NOW data --- -mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const uint8_t *mac, const mp_buffer_info_t *message) { +mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const mp_buffer_info_t *message, const uint8_t *mac) { // Send the packet - keep trying until timeout if the internal esp-now buffers are full. esp_err_t err; mp_uint_t start = mp_hal_ticks_ms(); diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index 87e3fb7168db..32e9a4c44c74 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -29,7 +29,7 @@ #include "py/obj.h" #include "py/ringbuf.h" -#include "bindings/espnow/ESPNowStats.h" +#include "bindings/espnow/Communicate.h" #include "bindings/espnow/Peers.h" #include "esp_wifi.h" @@ -37,12 +37,12 @@ // The data structure for the espnow_singleton. 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 - wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. - espnow_stats_obj_t *tx_stats; // The tx packet stats - espnow_stats_obj_t *rx_stats; // The rx packet stats - espnow_peers_obj_t *peers; // The sequence of peers + ringbuf_t *recv_buffer; // A buffer for received packets + size_t recv_buffer_size; // The size of the recv_buffer + wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. + espnow_com_obj_t *send; // The tx packet stats + espnow_com_obj_t *recv; // The rx packet stats + espnow_peers_obj_t *peers; // The sequence of peers } espnow_obj_t; extern void espnow_reset(void); @@ -56,5 +56,5 @@ extern void common_hal_espnow_set_buffer_size(espnow_obj_t *self, mp_int_t value extern void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value); extern void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key); -extern mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const uint8_t *mac, const mp_buffer_info_t *message); +extern mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const mp_buffer_info_t *message, const uint8_t *mac); extern mp_obj_t common_hal_espnow_recv(espnow_obj_t *self); diff --git a/ports/espressif/common-hal/espnow/__init__.c b/ports/espressif/common-hal/espnow/__init__.c index 498cd49dc945..77196f334990 100644 --- a/ports/espressif/common-hal/espnow/__init__.c +++ b/ports/espressif/common-hal/espnow/__init__.c @@ -25,7 +25,16 @@ */ #include "common-hal/espnow/__init__.h" + #include "py/runtime.h" +#include "shared-bindings/util.h" + +// Raise ValueError if the ESPNow object is deinited +void common_hal_espnow_check_for_deinit(espnow_obj_t *self) { + if (common_hal_espnow_deinited(self)) { + raise_deinited_error(); + } +} // Return C pointer to byte memory string/bytes/bytearray in obj. // Raise ValueError if the length does not match expected len. diff --git a/ports/espressif/common-hal/espnow/__init__.h b/ports/espressif/common-hal/espnow/__init__.h index bb1950d98c7a..068da3507a9f 100644 --- a/ports/espressif/common-hal/espnow/__init__.h +++ b/ports/espressif/common-hal/espnow/__init__.h @@ -26,5 +26,7 @@ #pragma once -#include "py/obj.h" +#include "common-hal/espnow/ESPNow.h" + +extern void common_hal_espnow_check_for_deinit(espnow_obj_t *self); extern const uint8_t *common_hal_espnow_get_bytes_len(mp_obj_t obj, size_t len); From 6c54bc9fd9237caf793c16a37f624154be5375d5 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Sun, 5 Feb 2023 12:31:08 +0530 Subject: [PATCH 15/17] more espnow changes - improve docs - use existing list methods - change `recv` to `read` --- ports/espressif/bindings/espnow/Communicate.c | 34 ++++---- ports/espressif/bindings/espnow/ESPNow.c | 20 ++--- ports/espressif/bindings/espnow/Peers.c | 84 ++----------------- ports/espressif/bindings/espnow/__init__.c | 2 +- ports/espressif/common-hal/espnow/ESPNow.c | 10 +-- ports/espressif/common-hal/espnow/ESPNow.h | 6 +- 6 files changed, 46 insertions(+), 110 deletions(-) diff --git a/ports/espressif/bindings/espnow/Communicate.c b/ports/espressif/bindings/espnow/Communicate.c index 2e6e63fe4673..5016dcfdda8e 100644 --- a/ports/espressif/bindings/espnow/Communicate.c +++ b/ports/espressif/bindings/espnow/Communicate.c @@ -36,9 +36,9 @@ //| """Provides methods and statistics related to communication //| with the ESP-NOW peers. //| -//| Dictionary: +//| Terminology: //| * "Send" = "Transmit" = ``TX`` -//| * "Recv" = "Receive" = ``RX`` +//| * "Read" = ``RX`` //| """ //| //| def __init__(self) -> None: @@ -46,7 +46,7 @@ //| ... //| job: str -//| """Used to decide whether to call `__send` or `__recv`. (read-only)""" +//| """Used to decide whether to call `__send` or `__read`. (read-only)""" //| STATIC mp_obj_t espnow_com_get_job(const mp_obj_t self_in) { espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -97,9 +97,11 @@ MP_PROPERTY_GETTER(espnow_com_failure_obj, //| self, //| message: ReadableBuffer, //| peer: Peer, -//| ) -> bool: +//| ) -> None: //| """Send a message to the peer's mac address. //| +//| This blocks until a timeout of ``2`` seconds if the ESP-NOW internal buffers are full. +//| //| :param ReadableBuffer message: The message to send (length <= 250 bytes). //| :param Peer peer: Send message to this peer. If `None`, send to all registered peers. //| """ @@ -131,18 +133,20 @@ STATIC mp_obj_t espnow_com___send(size_t n_args, const mp_obj_t *pos_args, mp_ma } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_com___send_obj, 2, espnow_com___send); -//| def __recv(self) -> Optional[ESPNowPacket]: -//| """Receive a message from the peer(s). +//| def __read(self) -> Optional[ESPNowPacket]: +//| """Read a packet from the receive buffer. +//| +//| This is non-blocking, the packet is received asynchronously from the peer(s). //| //| :returns: An `ESPNowPacket` if available in the buffer, otherwise `None`.""" //| ... -STATIC mp_obj_t espnow_com___recv(mp_obj_t self_in) { +STATIC mp_obj_t espnow_com___read(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); common_hal_espnow_check_for_deinit(self); - return common_hal_espnow_recv(self); + return common_hal_espnow_read(self); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_com___recv_obj, espnow_com___recv); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_com___read_obj, espnow_com___read); STATIC const mp_rom_map_elem_t espnow_com_locals_dict_table[] = { // Config parameters @@ -153,14 +157,14 @@ STATIC const mp_rom_map_elem_t espnow_com_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_failure), MP_ROM_PTR(&espnow_com_failure_obj) }, // Communication methods - { MP_ROM_QSTR(MP_QSTR___send), MP_ROM_PTR(&espnow_com___send_obj) }, - { MP_ROM_QSTR(MP_QSTR___recv), MP_ROM_PTR(&espnow_com___recv_obj) }, + { MP_ROM_QSTR(MP_QSTR___send), MP_ROM_PTR(&espnow_com___send_obj) }, + { MP_ROM_QSTR(MP_QSTR___read), MP_ROM_PTR(&espnow_com___read_obj) }, }; STATIC MP_DEFINE_CONST_DICT(espnow_com_locals_dict, espnow_com_locals_dict_table); //| def __call__(self, *args: Optional[Any], **kwargs: Optional[Any]) -> Optional[Any]: -//| """Calls the private `__send` or `__recv` methods with the supplied ``args`` and ``kwargs`` -//| based on whether the job parameter is set to ``send`` or ``recv``.""" +//| """Calls the private `__send` or `__read` methods with the supplied ``args`` and ``kwargs`` +//| based on whether the job parameter is set to ``send`` or ``read``.""" //| ... //| STATIC mp_obj_t espnow_com_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { @@ -170,8 +174,8 @@ STATIC mp_obj_t espnow_com_call(mp_obj_t self_in, size_t n_args, size_t n_kw, co case MP_QSTR_send: meth = MP_OBJ_FROM_PTR(&espnow_com___send_obj); break; - case MP_QSTR_recv: - meth = MP_OBJ_FROM_PTR(&espnow_com___recv_obj); + case MP_QSTR_read: + meth = MP_OBJ_FROM_PTR(&espnow_com___read_obj); break; default: break; diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 8eb82af31a87..bf1dcc9eb46e 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -165,20 +165,20 @@ STATIC mp_obj_t espnow_get_send(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_send_obj, espnow_get_send); -MP_PROPERTY_GETTER(espnow_send_call_obj, +MP_PROPERTY_GETTER(espnow_send_obj, (mp_obj_t)&espnow_get_send_obj); -//| recv: Communicate -//| """A `Communicate` object with ``job`` set to ``recv``. (read-only)""" +//| read: Communicate +//| """A `Communicate` object with ``job`` set to ``read``. (read-only)""" //| -STATIC mp_obj_t espnow_get_recv(mp_obj_t self_in) { +STATIC mp_obj_t espnow_get_read(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(self->recv); + return MP_OBJ_FROM_PTR(self->read); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_recv_obj, espnow_get_recv); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_read_obj, espnow_get_read); -MP_PROPERTY_GETTER(espnow_recv_call_obj, - (mp_obj_t)&espnow_get_recv_obj); +MP_PROPERTY_GETTER(espnow_read_obj, + (mp_obj_t)&espnow_get_read_obj); // --- Peer Related Properties --- @@ -208,8 +208,8 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_phy_rate), MP_ROM_PTR(&espnow_phy_rate_obj) }, // Send and receive messages - { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_call_obj) }, - { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&espnow_recv_call_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&espnow_read_obj) }, // Peer related properties { MP_ROM_QSTR(MP_QSTR_peers), MP_ROM_PTR(&espnow_peers_obj) }, diff --git a/ports/espressif/bindings/espnow/Peers.c b/ports/espressif/bindings/espnow/Peers.c index e3f858c1c940..15d07c09d0d8 100644 --- a/ports/espressif/bindings/espnow/Peers.c +++ b/ports/espressif/bindings/espnow/Peers.c @@ -38,7 +38,7 @@ // TODO: Check for deinit //| class Peers: -//| """A class that provides peer managment functions. Sequence[Peer].""" +//| """Maintains a `list` of `Peer` internally and only exposes a subset of `list` methods.""" //| //| def __init__(self) -> None: //| """You cannot create an instance of `Peers`.""" @@ -86,22 +86,7 @@ STATIC MP_DEFINE_CONST_DICT(espnow_peers_locals_dict, espnow_peers_locals_dict_t STATIC void espnow_peers_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); - const char *item_separator = ", "; - if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { - kind = PRINT_REPR; - } else { - #if MICROPY_PY_UJSON_SEPARATORS - item_separator = MP_PRINT_GET_EXT(print)->item_separator; - #endif - } - mp_print_str(print, "["); - for (size_t i = 0; i < list->len; i++) { - if (i > 0) { - mp_print_str(print, item_separator); - } - mp_obj_print_helper(print, list->items[i], kind); - } - mp_print_str(print, "]"); + return list->base.type->print(print, self->list, kind); } /******************************************************************************/ @@ -110,20 +95,7 @@ STATIC void espnow_peers_print(const mp_print_t *print, mp_obj_t self_in, mp_pri STATIC mp_obj_t espnow_peers_unary_op(mp_unary_op_t op, mp_obj_t self_in) { espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); - switch (op) { - case MP_UNARY_OP_BOOL: - return mp_obj_new_bool(list->len != 0); - case MP_UNARY_OP_LEN: - return MP_OBJ_NEW_SMALL_INT(list->len); - #if MICROPY_PY_SYS_GETSIZEOF - case MP_UNARY_OP_SIZEOF: { - size_t sz = sizeof(*list) + sizeof(mp_obj_t) * list->alloc; - return MP_OBJ_NEW_SMALL_INT(sz); - } - #endif - default: - return MP_OBJ_NULL; // op not supported - } + return list->base.type->ext->unary_op(op, self->list); } /******************************************************************************/ @@ -133,58 +105,18 @@ STATIC mp_obj_t espnow_peers_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t v if (value != MP_OBJ_SENTINEL) { return MP_OBJ_NULL; // op not supported } - espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); - - // load - #if MICROPY_PY_BUILTINS_SLICE - if (mp_obj_is_type(index, &mp_type_slice)) { - mp_bound_slice_t slice; - if (!mp_seq_get_fast_slice_indexes(list->len, index, &slice)) { - return mp_seq_extract_slice(list->len, list->items, &slice); - } - - mp_obj_list_t *res = MP_OBJ_TO_PTR(mp_obj_new_list(slice.stop - slice.start, NULL)); - mp_seq_copy(res->items, list->items + slice.start, res->len, mp_obj_t); - return MP_OBJ_FROM_PTR(res); - } - #endif - size_t index_val = mp_get_index(list->base.type, list->len, index, false); - return list->items[index_val]; + return list->base.type->ext->subscr(self->list, index, value); } /******************************************************************************/ /* peers iterator */ -typedef struct _espnow_peers_it_t { - mp_obj_base_t base; - mp_fun_1_t iternext; - mp_obj_t peers; - size_t cur; -} espnow_peers_it_t; - -STATIC mp_obj_t espnow_peers_it_iternext(mp_obj_t self_in) { - espnow_peers_it_t *self = MP_OBJ_TO_PTR(self_in); - espnow_peers_obj_t *peers = MP_OBJ_TO_PTR(self->peers); - mp_obj_list_t *list = MP_OBJ_TO_PTR(peers->list); - if (self->cur < list->len) { - mp_obj_t o_out = list->items[self->cur]; - self->cur += 1; - return o_out; - } else { - return MP_OBJ_STOP_ITERATION; - } -} - -STATIC mp_obj_t espnow_peers_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { - assert(sizeof(espnow_peers_it_t) <= sizeof(mp_obj_iter_buf_t)); - espnow_peers_it_t *o = (espnow_peers_it_t *)iter_buf; - o->base.type = &mp_type_polymorph_iter; - o->iternext = espnow_peers_it_iternext; - o->peers = o_in; - o->cur = 0; - return MP_OBJ_FROM_PTR(o); +STATIC mp_obj_t espnow_peers_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + espnow_peers_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); + return list->base.type->ext->getiter(self->list, iter_buf); } espnow_peers_obj_t *espnow_peers_new(void) { diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c index 2b95085c5d0e..b76e250f4968 100644 --- a/ports/espressif/bindings/espnow/__init__.c +++ b/ports/espressif/bindings/espnow/__init__.c @@ -64,7 +64,7 @@ //| //| while True: //| if e: -//| packet = e.recv() +//| packet = e.read() //| packets.append(packet) //| if packet.msg == b'end': //| break diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index df766ad32fd0..662da8cf45e0 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -93,7 +93,7 @@ static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { ringbuf_t *buf = self->recv_buffer; if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { - self->recv->failure++; + self->read->failure++; return; } @@ -120,7 +120,7 @@ static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { ringbuf_put_n(buf, mac, ESP_NOW_ETH_ALEN); ringbuf_put_n(buf, msg, msg_len); - self->recv->success++; + self->read->success++; } bool common_hal_espnow_deinited(espnow_obj_t *self) { @@ -133,7 +133,7 @@ void common_hal_espnow_construct(espnow_obj_t *self, mp_int_t buffer_size, mp_in common_hal_espnow_set_phy_rate(self, phy_rate); self->send = espnow_com_new(MP_QSTR_send); - self->recv = espnow_com_new(MP_QSTR_recv); + self->read = espnow_com_new(MP_QSTR_read); self->peers = espnow_peers_new(); @@ -166,7 +166,7 @@ void common_hal_espnow_init(espnow_obj_t *self) { } // De-initialize the ESP-NOW software stack, -// disable callbacks and deallocate the recv data buffers. +// disable callbacks and deallocate the recv data buffer. void common_hal_espnow_deinit(espnow_obj_t *self) { if (common_hal_espnow_deinited(self)) { return; @@ -214,7 +214,7 @@ mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const mp_buffer_info_t *mess return mp_const_none; } -mp_obj_t common_hal_espnow_recv(espnow_obj_t *self) { +mp_obj_t common_hal_espnow_read(espnow_obj_t *self) { if (!ringbuf_num_filled(self->recv_buffer)) { return mp_const_none; } diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index 32e9a4c44c74..21f1646825d4 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -40,8 +40,8 @@ typedef struct _espnow_obj_t { ringbuf_t *recv_buffer; // A buffer for received packets size_t recv_buffer_size; // The size of the recv_buffer wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. - espnow_com_obj_t *send; // The tx packet stats - espnow_com_obj_t *recv; // The rx packet stats + espnow_com_obj_t *send; // For keeping tx stats + espnow_com_obj_t *read; // For keeping rx stats espnow_peers_obj_t *peers; // The sequence of peers } espnow_obj_t; @@ -57,4 +57,4 @@ extern void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value); extern void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key); extern mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const mp_buffer_info_t *message, const uint8_t *mac); -extern mp_obj_t common_hal_espnow_recv(espnow_obj_t *self); +extern mp_obj_t common_hal_espnow_read(espnow_obj_t *self); From 874ba4ec68dae62914d04edb543dd90bf98be8ba Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+MicroDev1@users.noreply.github.com> Date: Thu, 9 Mar 2023 12:04:50 +0530 Subject: [PATCH 16/17] revert `Communicate` class and more --- ports/espressif/bindings/espnow/Communicate.c | 201 ------------------ ports/espressif/bindings/espnow/Communicate.h | 39 ---- ports/espressif/bindings/espnow/ESPNow.c | 182 ++++++++++++---- ports/espressif/bindings/espnow/__init__.c | 2 - ports/espressif/common-hal/espnow/ESPNow.c | 19 +- ports/espressif/common-hal/espnow/ESPNow.h | 17 +- ports/espressif/common-hal/espnow/__init__.c | 8 - ports/espressif/common-hal/espnow/__init__.h | 4 +- 8 files changed, 150 insertions(+), 322 deletions(-) delete mode 100644 ports/espressif/bindings/espnow/Communicate.c delete mode 100644 ports/espressif/bindings/espnow/Communicate.h diff --git a/ports/espressif/bindings/espnow/Communicate.c b/ports/espressif/bindings/espnow/Communicate.c deleted file mode 100644 index 5016dcfdda8e..000000000000 --- a/ports/espressif/bindings/espnow/Communicate.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - * This file is part of the CircuitPython project, https://github.com/adafruit/circuitpython - * - * 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 "py/objproperty.h" -#include "py/runtime.h" - -#include "bindings/espnow/Peer.h" -#include "common-hal/espnow/__init__.h" - -// --- Send and Receive ESP-NOW data --- - -//| class Communicate: -//| """Provides methods and statistics related to communication -//| with the ESP-NOW peers. -//| -//| Terminology: -//| * "Send" = "Transmit" = ``TX`` -//| * "Read" = ``RX`` -//| """ -//| -//| def __init__(self) -> None: -//| """You cannot create an instance of `Communicate`.""" -//| ... - -//| job: str -//| """Used to decide whether to call `__send` or `__read`. (read-only)""" -//| -STATIC mp_obj_t espnow_com_get_job(const mp_obj_t self_in) { - espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_NEW_QSTR(self->job); -} -MP_DEFINE_CONST_FUN_OBJ_1(espnow_com_get_job_obj, espnow_com_get_job); - -MP_PROPERTY_GETTER(espnow_com_job_obj, - (mp_obj_t)&espnow_com_get_job_obj); - -//| success: int -//| """The number of successes. (read-only) -//| -//| * In case of transmit ``TX``: -//| The number of tx packets received by the peer(s) ``ESP_NOW_SEND_SUCCESS``. -//| -//| * In case of receive ``RX``: -//| The number of rx packets captured in the buffer.""" -//| -STATIC mp_obj_t espnow_com_get_success(const mp_obj_t self_in) { - espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_int_from_uint(self->success); -} -MP_DEFINE_CONST_FUN_OBJ_1(espnow_com_get_success_obj, espnow_com_get_success); - -MP_PROPERTY_GETTER(espnow_com_success_obj, - (mp_obj_t)&espnow_com_get_success_obj); - -//| failure: int -//| """The number of failures. (read-only) -//| -//| * In case of transmit ``TX``: -//| The number of failed tx packets ``ESP_NOW_SEND_FAIL``. -//| -//| * In case of receive ``RX``: -//| The number of dropped rx packets due to buffer overflow.""" -//| -STATIC mp_obj_t espnow_com_get_failure(const mp_obj_t self_in) { - espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_int_from_uint(self->failure); -} -MP_DEFINE_CONST_FUN_OBJ_1(espnow_com_get_failure_obj, espnow_com_get_failure); - -MP_PROPERTY_GETTER(espnow_com_failure_obj, - (mp_obj_t)&espnow_com_get_failure_obj); - -//| def __send( -//| self, -//| message: ReadableBuffer, -//| peer: Peer, -//| ) -> None: -//| """Send a message to the peer's mac address. -//| -//| This blocks until a timeout of ``2`` seconds if the ESP-NOW internal buffers are full. -//| -//| :param ReadableBuffer message: The message to send (length <= 250 bytes). -//| :param Peer peer: Send message to this peer. If `None`, send to all registered peers. -//| """ -//| ... -STATIC mp_obj_t espnow_com___send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_message, ARG_peer }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_message, MP_ARG_OBJ | MP_ARG_REQUIRED }, - { MP_QSTR_peer, MP_ARG_OBJ, { .u_obj = mp_const_none } }, - }; - - 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); - - espnow_obj_t *self = pos_args[0]; - common_hal_espnow_check_for_deinit(self); - - // Get a pointer to the data buffer of the message - mp_buffer_info_t message; - mp_get_buffer_raise(args[ARG_message].u_obj, &message, MP_BUFFER_READ); - - const uint8_t *mac = NULL; - if (args[ARG_peer].u_obj != mp_const_none) { - const espnow_peer_obj_t *peer = MP_OBJ_FROM_PTR(mp_arg_validate_type_or_none(args[ARG_peer].u_obj, &espnow_peer_type, MP_QSTR_peer)); - mac = peer->peer_info.peer_addr; - } - - return common_hal_espnow_send(self, &message, mac); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_com___send_obj, 2, espnow_com___send); - -//| def __read(self) -> Optional[ESPNowPacket]: -//| """Read a packet from the receive buffer. -//| -//| This is non-blocking, the packet is received asynchronously from the peer(s). -//| -//| :returns: An `ESPNowPacket` if available in the buffer, otherwise `None`.""" -//| ... -STATIC mp_obj_t espnow_com___read(mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_check_for_deinit(self); - - return common_hal_espnow_read(self); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_com___read_obj, espnow_com___read); - -STATIC const mp_rom_map_elem_t espnow_com_locals_dict_table[] = { - // Config parameters - { MP_ROM_QSTR(MP_QSTR_job), MP_ROM_PTR(&espnow_com_job_obj) }, - - // Packet statistics - { MP_ROM_QSTR(MP_QSTR_success), MP_ROM_PTR(&espnow_com_success_obj) }, - { MP_ROM_QSTR(MP_QSTR_failure), MP_ROM_PTR(&espnow_com_failure_obj) }, - - // Communication methods - { MP_ROM_QSTR(MP_QSTR___send), MP_ROM_PTR(&espnow_com___send_obj) }, - { MP_ROM_QSTR(MP_QSTR___read), MP_ROM_PTR(&espnow_com___read_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(espnow_com_locals_dict, espnow_com_locals_dict_table); - -//| def __call__(self, *args: Optional[Any], **kwargs: Optional[Any]) -> Optional[Any]: -//| """Calls the private `__send` or `__read` methods with the supplied ``args`` and ``kwargs`` -//| based on whether the job parameter is set to ``send`` or ``read``.""" -//| ... -//| -STATIC mp_obj_t espnow_com_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - espnow_com_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_t meth = NULL; - switch (self->job) { - case MP_QSTR_send: - meth = MP_OBJ_FROM_PTR(&espnow_com___send_obj); - break; - case MP_QSTR_read: - meth = MP_OBJ_FROM_PTR(&espnow_com___read_obj); - break; - default: - break; - } - return meth ? mp_call_method_self_n_kw(meth, MP_STATE_PORT(espnow_singleton), n_args, n_kw, args) : mp_const_none; -} - -espnow_com_obj_t *espnow_com_new(qstr job) { - espnow_com_obj_t *self = m_new_obj(espnow_com_obj_t); - self->base.type = &espnow_com_type; - self->job = job; - return self; -} - -const mp_obj_type_t espnow_com_type = { - { &mp_type_type }, - .name = MP_QSTR_Communicate, - .locals_dict = (mp_obj_t)&espnow_com_locals_dict, - .flags = MP_TYPE_FLAG_EXTENDED, - MP_TYPE_EXTENDED_FIELDS( - .call = &espnow_com_call, - ), -}; diff --git a/ports/espressif/bindings/espnow/Communicate.h b/ports/espressif/bindings/espnow/Communicate.h deleted file mode 100644 index 194784f8b496..000000000000 --- a/ports/espressif/bindings/espnow/Communicate.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - volatile size_t success; - volatile size_t failure; - qstr job; -} espnow_com_obj_t; - -const mp_obj_type_t espnow_com_type; -extern espnow_com_obj_t *espnow_com_new(qstr job); diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index bf1dcc9eb46e..d2f5d2268f25 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -31,11 +31,23 @@ #include "py/runtime.h" #include "py/stream.h" +#include "shared-bindings/util.h" + #include "bindings/espnow/ESPNow.h" +#include "bindings/espnow/Peer.h" + #include "common-hal/espnow/__init__.h" +#include "common-hal/espnow/ESPNow.h" #include "esp_now.h" +// Raise ValueError if the ESPNow object is deinited +static void espnow_check_for_deinit(espnow_obj_t *self) { + if (common_hal_espnow_deinited(self)) { + raise_deinited_error(); + } +} + // --- Initialisation and Config functions --- //| class ESPNow: @@ -80,7 +92,7 @@ STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, size_t //| ... STATIC mp_obj_t espnow_deinit(mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_check_for_deinit(self); + espnow_check_for_deinit(self); common_hal_espnow_deinit(self); return mp_const_none; } @@ -101,6 +113,111 @@ STATIC mp_obj_t espnow_obj___exit__(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow___exit___obj, 4, 4, espnow_obj___exit__); +// --- Send and Read messages --- + +//| def send( +//| self, +//| message: ReadableBuffer, +//| peer: Peer, +//| ) -> None: +//| """Send a message to the peer's mac address. +//| +//| This blocks until a timeout of ``2`` seconds if the ESP-NOW internal buffers are full. +//| +//| :param ReadableBuffer message: The message to send (length <= 250 bytes). +//| :param Peer peer: Send message to this peer. If `None`, send to all registered peers. +//| """ +//| ... +STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_message, ARG_peer }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_message, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_peer, MP_ARG_OBJ, { .u_obj = mp_const_none } }, + }; + + 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); + + espnow_obj_t *self = pos_args[0]; + espnow_check_for_deinit(self); + + // Get a pointer to the data buffer of the message + mp_buffer_info_t message; + mp_get_buffer_raise(args[ARG_message].u_obj, &message, MP_BUFFER_READ); + + const uint8_t *mac = NULL; + if (args[ARG_peer].u_obj != mp_const_none) { + const espnow_peer_obj_t *peer = MP_OBJ_FROM_PTR(mp_arg_validate_type_or_none(args[ARG_peer].u_obj, &espnow_peer_type, MP_QSTR_peer)); + mac = peer->peer_info.peer_addr; + } + + return common_hal_espnow_send(self, &message, mac); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_send_obj, 2, espnow_send); + +//| def read(self) -> Optional[ESPNowPacket]: +//| """Read a packet from the receive buffer. +//| +//| This is non-blocking, the packet is received asynchronously from the peer(s). +//| +//| :returns: An `ESPNowPacket` if available in the buffer, otherwise `None`.""" +//| ... +STATIC mp_obj_t espnow_read(mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + espnow_check_for_deinit(self); + + return common_hal_espnow_read(self); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_read_obj, espnow_read); + +//| send_success: int +//| """The number of tx packets received by the peer(s) ``ESP_NOW_SEND_SUCCESS``. (read-only)""" +//| +STATIC mp_obj_t espnow_get_send_success(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->send_success); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_send_success_obj, espnow_get_send_success); + +MP_PROPERTY_GETTER(espnow_send_success_obj, + (mp_obj_t)&espnow_get_send_success_obj); + +//| send_failure: int +//| """The number of failed tx packets ``ESP_NOW_SEND_FAIL``. (read-only)""" +//| +STATIC mp_obj_t espnow_send_get_failure(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->send_failure); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_send_get_failure_obj, espnow_send_get_failure); + +MP_PROPERTY_GETTER(espnow_send_failure_obj, + (mp_obj_t)&espnow_send_get_failure_obj); + +//| read_success: int +//| """The number of rx packets captured in the buffer. (read-only)""" +//| +STATIC mp_obj_t espnow_get_read_success(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->read_success); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_read_success_obj, espnow_get_read_success); + +MP_PROPERTY_GETTER(espnow_read_success_obj, + (mp_obj_t)&espnow_get_read_success_obj); + +//| read_failure: int +//| """The number of dropped rx packets due to buffer overflow. (read-only)""" +//| +STATIC mp_obj_t espnow_read_get_failure(const mp_obj_t self_in) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int_from_uint(self->read_failure); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_read_get_failure_obj, espnow_read_get_failure); + +MP_PROPERTY_GETTER(espnow_read_failure_obj, + (mp_obj_t)&espnow_read_get_failure_obj); + //| def set_pmk(self, pmk: ReadableBuffer) -> None: //| """Set the ESP-NOW Primary Master Key (pmk) for encrypted communications. //| @@ -108,32 +225,23 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow___exit___obj, 4, 4, espnow_obj //| ... STATIC mp_obj_t espnow_set_pmk(mp_obj_t self_in, mp_obj_t key) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_check_for_deinit(self); + espnow_check_for_deinit(self); common_hal_espnow_set_pmk(self, common_hal_espnow_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); //| buffer_size: int -//| """The size of the internal ring buffer.""" +//| """The size of the internal ring buffer. (read-only)""" //| STATIC mp_obj_t espnow_get_buffer_size(const mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_NEW_SMALL_INT(self->recv_buffer_size); + return mp_obj_new_int_from_uint(self->recv_buffer_size); } MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_buffer_size_obj, espnow_get_buffer_size); -STATIC mp_obj_t espnow_set_buffer_size(const mp_obj_t self_in, const mp_obj_t value) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_check_for_deinit(self); - common_hal_espnow_set_buffer_size(self, mp_obj_get_int(value)); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_buffer_size_obj, espnow_set_buffer_size); - -MP_PROPERTY_GETSET(espnow_buffer_size_obj, - (mp_obj_t)&espnow_get_buffer_size_obj, - (mp_obj_t)&espnow_set_buffer_size_obj); +MP_PROPERTY_GETTER(espnow_buffer_size_obj, + (mp_obj_t)&espnow_get_buffer_size_obj); //| phy_rate: int //| """The ESP-NOW physical layer rate.""" @@ -146,7 +254,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_phy_rate_obj, espnow_get_phy_rate); STATIC mp_obj_t espnow_set_phy_rate(const mp_obj_t self_in, const mp_obj_t value) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_check_for_deinit(self); + espnow_check_for_deinit(self); common_hal_espnow_set_phy_rate(self, mp_obj_get_int(value)); return mp_const_none; } @@ -156,30 +264,6 @@ MP_PROPERTY_GETSET(espnow_phy_rate_obj, (mp_obj_t)&espnow_get_phy_rate_obj, (mp_obj_t)&espnow_set_phy_rate_obj); -//| send: Communicate -//| """A `Communicate` object with ``job`` set to ``send``. (read-only)""" -//| -STATIC mp_obj_t espnow_get_send(mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(self->send); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_send_obj, espnow_get_send); - -MP_PROPERTY_GETTER(espnow_send_obj, - (mp_obj_t)&espnow_get_send_obj); - -//| read: Communicate -//| """A `Communicate` object with ``job`` set to ``read``. (read-only)""" -//| -STATIC mp_obj_t espnow_get_read(mp_obj_t self_in) { - espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(self->read); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_read_obj, espnow_get_read); - -MP_PROPERTY_GETTER(espnow_read_obj, - (mp_obj_t)&espnow_get_read_obj); - // --- Peer Related Properties --- //| peers: Peers @@ -202,15 +286,21 @@ STATIC const mp_rom_map_elem_t espnow_locals_dict_table[] = { // Deinit the object { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&espnow_deinit_obj) }, + // Send messages + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_send_success),MP_ROM_PTR(&espnow_send_success_obj)}, + { MP_ROM_QSTR(MP_QSTR_send_failure),MP_ROM_PTR(&espnow_send_failure_obj)}, + + // Read messages + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&espnow_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_success),MP_ROM_PTR(&espnow_read_success_obj)}, + { MP_ROM_QSTR(MP_QSTR_read_failure),MP_ROM_PTR(&espnow_read_failure_obj)}, + // Config parameters { MP_ROM_QSTR(MP_QSTR_set_pmk), MP_ROM_PTR(&espnow_set_pmk_obj) }, { MP_ROM_QSTR(MP_QSTR_buffer_size), MP_ROM_PTR(&espnow_buffer_size_obj) }, { MP_ROM_QSTR(MP_QSTR_phy_rate), MP_ROM_PTR(&espnow_phy_rate_obj) }, - // Send and receive messages - { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&espnow_read_obj) }, - // Peer related properties { MP_ROM_QSTR(MP_QSTR_peers), MP_ROM_PTR(&espnow_peers_obj) }, }; @@ -222,7 +312,7 @@ STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_dict_table); // 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) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_check_for_deinit(self); + espnow_check_for_deinit(self); switch (request) { case MP_STREAM_POLL: { mp_uint_t flags = arg; @@ -254,7 +344,7 @@ STATIC const mp_stream_p_t espnow_stream_p = { //| STATIC mp_obj_t espnow_unary_op(mp_unary_op_t op, mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); - common_hal_espnow_check_for_deinit(self); + espnow_check_for_deinit(self); size_t len = ringbuf_num_filled(self->recv_buffer); switch (op) { case MP_UNARY_OP_BOOL: diff --git a/ports/espressif/bindings/espnow/__init__.c b/ports/espressif/bindings/espnow/__init__.c index b76e250f4968..da15be49ea40 100644 --- a/ports/espressif/bindings/espnow/__init__.c +++ b/ports/espressif/bindings/espnow/__init__.c @@ -27,7 +27,6 @@ #include "bindings/espnow/__init__.h" #include "bindings/espnow/ESPNow.h" #include "bindings/espnow/ESPNowPacket.h" -#include "bindings/espnow/Communicate.h" #include "bindings/espnow/Peer.h" #include "bindings/espnow/Peers.h" @@ -83,7 +82,6 @@ STATIC const mp_rom_map_elem_t espnow_module_globals_table[] = { // module classes { MP_ROM_QSTR(MP_QSTR_ESPNow), MP_ROM_PTR(&espnow_type) }, { MP_ROM_QSTR(MP_QSTR_ESPNowPacket),MP_ROM_PTR(&espnow_packet_type_obj) }, - { MP_ROM_QSTR(MP_QSTR_Communicate), MP_ROM_PTR(&espnow_com_type) }, { MP_ROM_QSTR(MP_QSTR_Peer), MP_ROM_PTR(&espnow_peer_type) }, { MP_ROM_QSTR(MP_QSTR_Peers), MP_ROM_PTR(&espnow_peers_type) }, }; diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index 662da8cf45e0..a048c7f9f1c0 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -79,9 +79,9 @@ typedef struct { static void send_cb(const uint8_t *mac, esp_now_send_status_t status) { espnow_obj_t *self = MP_STATE_PORT(espnow_singleton); if (status == ESP_NOW_SEND_SUCCESS) { - self->send->success++; + self->send_success++; } else { - self->send->failure++; + self->send_failure++; } } @@ -93,7 +93,7 @@ static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { ringbuf_t *buf = self->recv_buffer; if (sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { - self->read->failure++; + self->read_failure++; return; } @@ -120,7 +120,7 @@ static void recv_cb(const uint8_t *mac, const uint8_t *msg, int msg_len) { ringbuf_put_n(buf, mac, ESP_NOW_ETH_ALEN); ringbuf_put_n(buf, msg, msg_len); - self->read->success++; + self->read_success++; } bool common_hal_espnow_deinited(espnow_obj_t *self) { @@ -129,14 +129,9 @@ bool common_hal_espnow_deinited(espnow_obj_t *self) { // Construct the ESPNow object void common_hal_espnow_construct(espnow_obj_t *self, mp_int_t buffer_size, mp_int_t phy_rate) { - common_hal_espnow_set_buffer_size(self, buffer_size); common_hal_espnow_set_phy_rate(self, phy_rate); - - self->send = espnow_com_new(MP_QSTR_send); - self->read = espnow_com_new(MP_QSTR_read); - + self->recv_buffer_size = mp_arg_validate_int_min(buffer_size, MIN_PACKET_LEN, MP_QSTR_buffer_size); self->peers = espnow_peers_new(); - common_hal_espnow_init(self); } @@ -185,10 +180,6 @@ void espnow_reset(void) { MP_STATE_PORT(espnow_singleton) = NULL; } -void common_hal_espnow_set_buffer_size(espnow_obj_t *self, mp_int_t value) { - self->recv_buffer_size = mp_arg_validate_int_min(value, MIN_PACKET_LEN, MP_QSTR_buffer_size); -}; - void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value) { self->phy_rate = mp_arg_validate_int_range(value, 0, WIFI_PHY_RATE_MAX - 1, MP_QSTR_phy_rate); }; diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index 21f1646825d4..624078860361 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -29,20 +29,20 @@ #include "py/obj.h" #include "py/ringbuf.h" -#include "bindings/espnow/Communicate.h" #include "bindings/espnow/Peers.h" #include "esp_wifi.h" -// The data structure for the espnow_singleton. 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 - wifi_phy_rate_t phy_rate; // The ESP-NOW physical layer rate. - espnow_com_obj_t *send; // For keeping tx stats - espnow_com_obj_t *read; // For keeping rx stats - espnow_peers_obj_t *peers; // The sequence of peers + ringbuf_t *recv_buffer; + size_t recv_buffer_size; + wifi_phy_rate_t phy_rate; + espnow_peers_obj_t *peers; + volatile size_t send_success; + volatile size_t send_failure; + volatile size_t read_success; + volatile size_t read_failure; } espnow_obj_t; extern void espnow_reset(void); @@ -52,7 +52,6 @@ extern void common_hal_espnow_init(espnow_obj_t *self); extern void common_hal_espnow_deinit(espnow_obj_t *self); extern bool common_hal_espnow_deinited(espnow_obj_t *self); -extern void common_hal_espnow_set_buffer_size(espnow_obj_t *self, mp_int_t value); extern void common_hal_espnow_set_phy_rate(espnow_obj_t *self, mp_int_t value); extern void common_hal_espnow_set_pmk(espnow_obj_t *self, const uint8_t *key); diff --git a/ports/espressif/common-hal/espnow/__init__.c b/ports/espressif/common-hal/espnow/__init__.c index 77196f334990..effc752f79f3 100644 --- a/ports/espressif/common-hal/espnow/__init__.c +++ b/ports/espressif/common-hal/espnow/__init__.c @@ -27,14 +27,6 @@ #include "common-hal/espnow/__init__.h" #include "py/runtime.h" -#include "shared-bindings/util.h" - -// Raise ValueError if the ESPNow object is deinited -void common_hal_espnow_check_for_deinit(espnow_obj_t *self) { - if (common_hal_espnow_deinited(self)) { - raise_deinited_error(); - } -} // Return C pointer to byte memory string/bytes/bytearray in obj. // Raise ValueError if the length does not match expected len. diff --git a/ports/espressif/common-hal/espnow/__init__.h b/ports/espressif/common-hal/espnow/__init__.h index 068da3507a9f..bb1950d98c7a 100644 --- a/ports/espressif/common-hal/espnow/__init__.h +++ b/ports/espressif/common-hal/espnow/__init__.h @@ -26,7 +26,5 @@ #pragma once -#include "common-hal/espnow/ESPNow.h" - -extern void common_hal_espnow_check_for_deinit(espnow_obj_t *self); +#include "py/obj.h" extern const uint8_t *common_hal_espnow_get_bytes_len(mp_obj_t obj, size_t len); From 58f28b98be93a25ebe1d75fbfff2b4a1ebff7009 Mon Sep 17 00:00:00 2001 From: MicroDev <70126934+microdev1@users.noreply.github.com> Date: Fri, 10 Mar 2023 00:01:44 +0530 Subject: [PATCH 17/17] minor doc fix Co-authored-by: Scott Shawcroft --- ports/espressif/bindings/espnow/ESPNow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index d2f5d2268f25..513966e1e8ee 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -118,7 +118,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow___exit___obj, 4, 4, espnow_obj //| def send( //| self, //| message: ReadableBuffer, -//| peer: Peer, +//| peer: Optional[Peer] = None, //| ) -> None: //| """Send a message to the peer's mac address. //|