diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index d0fb9e5da4..cca9e1b067 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 935a740164..0bcd7d39ed 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,43 +110,57 @@ 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; + + _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; } - 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); - - // 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. -//| """ -//| ... -//| -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)); +// 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; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_active_obj, 1, 2, espnow_active); -//| 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) -//| """ -//| ... +//| def set_pmk(self, pmk: bytes) -> None: +//| """Set the ESP-NOW Primary Master Key (pmk) for encrypted communications. //| -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} }, - }; - - 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) { - _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)); - } - - 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 - +//| :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_KW(espnow_config_obj, 1, espnow_config); +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(!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); + 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.""" +//| +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); + +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); + +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; +} +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]; +//| :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 } }, + }; + + 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); - // 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); + 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); + +MP_PROPERTY_GETTER(espnow_any_obj, + (mp_obj_t)&espnow_get_any_obj); // --- Peer Management Functions --- -//| 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. -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); + mp_arg_parse_all(n_args - 1, pos_args + 1, 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); + 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; } - if (args[ARG_channel].u_obj != mp_const_none) { - peer->channel = mp_obj_get_int(args[ARG_channel].u_obj); + 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_ifidx].u_obj != mp_const_none) { - peer->ifidx = mp_obj_get_int(args[ARG_ifidx].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_encrypt].u_obj != mp_const_none) { - peer->encrypt = mp_obj_is_true(args[ARG_encrypt].u_obj); + 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->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_num_peers_obj, - (mp_obj_t)&espnow_get_num_peers_obj); +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_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 52325dad58..5661885911 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 7bfb447fa4..ded265b47e 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 f90805d6f3..5936b64230 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 5d54abc6b5..2725bedcca 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