diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index ac7b040936..560dffe6a4 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -144,7 +144,7 @@ msgstr "" msgid "%q init failed" msgstr "" -#: shared-bindings/dualbank/__init__.c +#: ports/espressif/bindings/espnow/Peer.c shared-bindings/dualbank/__init__.c msgid "%q is %q" msgstr "" @@ -242,7 +242,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 "" @@ -510,6 +510,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 @@ -681,7 +682,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 "" @@ -1072,7 +1073,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 "" @@ -1255,8 +1256,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 "" @@ -1282,7 +1282,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 "" @@ -1291,7 +1291,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 "" @@ -1323,7 +1323,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 "" @@ -1679,11 +1679,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 "" @@ -1691,7 +1691,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 "" @@ -1867,7 +1867,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 "" @@ -1887,7 +1887,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 "" @@ -2338,7 +2338,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 "" diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index ea70f320cb..2151c8e03c 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 @@ -214,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 \ @@ -248,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) \ @@ -259,6 +260,20 @@ 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) \ + $(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 new file mode 100644 index 0000000000..513966e1e8 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -0,0 +1,369 @@ +/* + * 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/objproperty.h" +#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: +//| """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. 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 = 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 = MP_STATE_PORT(espnow_singleton); + + if (self != NULL) { + mp_raise_RuntimeError(translate("Already running")); + } + + // Allocate a new object + self = m_new_obj(espnow_obj_t); + self->base.type = &espnow_type; + + // 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; + return MP_OBJ_FROM_PTR(self); +} + +//| 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); + espnow_check_for_deinit(self); + 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__); + +// --- Send and Read messages --- + +//| def send( +//| self, +//| message: ReadableBuffer, +//| peer: Optional[Peer] = None, +//| ) -> 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. +//| +//| :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) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + 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. (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_int_from_uint(self->recv_buffer_size); +} +MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_buffer_size_obj, espnow_get_buffer_size); + +MP_PROPERTY_GETTER(espnow_buffer_size_obj, + (mp_obj_t)&espnow_get_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); + espnow_check_for_deinit(self); + 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); + +MP_PROPERTY_GETSET(espnow_phy_rate_obj, + (mp_obj_t)&espnow_get_phy_rate_obj, + (mp_obj_t)&espnow_set_phy_rate_obj); + +// --- Peer Related Properties --- + +//| 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); + 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); + +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) }, + + // 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) }, + + // Peer related properties + { MP_ROM_QSTR(MP_QSTR_peers), MP_ROM_PTR(&espnow_peers_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(espnow_locals_dict, espnow_locals_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) { + espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); + espnow_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, +}; + +//| 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); + espnow_check_for_deinit(self); + 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, + .make_new = espnow_make_new, + .locals_dict = (mp_obj_t)&espnow_locals_dict, + .flags = MP_TYPE_FLAG_EXTENDED, + MP_TYPE_EXTENDED_FIELDS( + .protocol = &espnow_stream_p, + .unary_op = &espnow_unary_op + ), +}; diff --git a/ports/espressif/bindings/espnow/ESPNow.h b/ports/espressif/bindings/espnow/ESPNow.h new file mode 100644 index 0000000000..6aa011c501 --- /dev/null +++ b/ports/espressif/bindings/espnow/ESPNow.h @@ -0,0 +1,31 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * 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 + * 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 mp_obj_type_t espnow_type; diff --git a/ports/espressif/bindings/espnow/ESPNowPacket.c b/ports/espressif/bindings/espnow/ESPNowPacket.c new file mode 100644 index 0000000000..f8bc8e8418 --- /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 retrieved from ESP-NOW communication protocol. A namedtuple.""" +//| +//| mac: ReadableBuffer +//| """The sender's mac address (length = 6 bytes).""" +//| +//| msg: ReadableBuffer +//| """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 time in milliseconds since the device last booted when the packet was received.""" +//| + +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 0000000000..87fc51ee92 --- /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/Peer.c b/ports/espressif/bindings/espnow/Peer.c new file mode 100644 index 0000000000..7b6cc5f668 --- /dev/null +++ b/ports/espressif/bindings/espnow/Peer.c @@ -0,0 +1,240 @@ +/* + * 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" +#include "common-hal/espnow/__init__.h" + +// TODO: check peer already exist +// TODO: check peer dosen't exist + +//| 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, 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) { + 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, 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); + } + + 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, 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; +} +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, 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; +} +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 0000000000..e4cb828472 --- /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 0000000000..15d07c09d0 --- /dev/null +++ b/ports/espressif/bindings/espnow/Peers.c @@ -0,0 +1,140 @@ +/* + * 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/espidf/__init__.h" + +#include "bindings/espnow/Peer.h" +#include "bindings/espnow/Peers.h" + +#include "esp_now.h" + +// TODO: Check for deinit + +//| class Peers: +//| """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`.""" +//| ... + +//| 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_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); +} +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_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); +} +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); + return list->base.type->print(print, self->list, kind); +} + +/******************************************************************************/ +/* 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); + return list->base.type->ext->unary_op(op, self->list); +} + +/******************************************************************************/ +/* 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); + return list->base.type->ext->subscr(self->list, index, value); +} + +/******************************************************************************/ +/* peers iterator */ + +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) { + 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, + .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, + ), +}; diff --git a/ports/espressif/bindings/espnow/Peers.h b/ports/espressif/bindings/espnow/Peers.h new file mode 100644 index 0000000000..e871ae86c0 --- /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 new file mode 100644 index 0000000000..da15be49ea --- /dev/null +++ b/ports/espressif/bindings/espnow/__init__.c @@ -0,0 +1,95 @@ +/* + * 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 "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 +//| +//| The `espnow` module provides an interface to the +//| `ESP-NOW `_ +//| protocol provided by Espressif on its SoCs +//| (`API docs `_). +//| +//| **Sender** +//| +//| .. code-block:: python +//| +//| import espnow +//| +//| e = espnow.ESPNow() +//| peer = espnow.Peer(mac=b'\xaa\xaa\xaa\xaa\xaa\xaa') +//| e.peers.append(peer) +//| +//| e.send("Starting...") +//| for i in range(10): +//| e.send(str(i)*20) +//| e.send(b'end') +//| +//| **Receiver** +//| +//| .. code-block:: python +//| +//| import espnow +//| +//| e = espnow.ESPNow() +//| packets = [] +//| +//| while True: +//| if e: +//| packet = e.read() +//| packets.append(packet) +//| if packet.msg == b'end': +//| break +//| +//| print("packets:", f"length={len(packets)}") +//| for packet in packets: +//| print(packet) +//| """ +//| ... +//| + +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_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); + +const mp_obj_module_t espnow_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&espnow_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_espnow, espnow_module, CIRCUITPY_ESPNOW); diff --git a/ports/espressif/bindings/espnow/__init__.h b/ports/espressif/bindings/espnow/__init__.h new file mode 100644 index 0000000000..fb814a434f --- /dev/null +++ b/ports/espressif/bindings/espnow/__init__.h @@ -0,0 +1,29 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 MicroDev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +void espnow_reset(void); diff --git a/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c b/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c index 96de70bf84..cfc6018708 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 63c85099e6..75def8ad76 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 3b3e51df61..d1d8c24123 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/common-hal/espidf/__init__.c b/ports/espressif/common-hal/espidf/__init__.c index 183ebb3817..d2d00ebc56 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 new file mode 100644 index 0000000000..a048c7f9f1 --- /dev/null +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -0,0 +1,240 @@ +/* + * 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/espidf/__init__.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 + +// TODO: deinit wifi? + +// 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). +#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. +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 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 = MP_STATE_PORT(espnow_singleton); + if (status == ESP_NOW_SEND_SUCCESS) { + self->send_success++; + } else { + self->send_failure++; + } +} + +// 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->read_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. + // 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 + + espnow_header_t header; + header.magic = ESPNOW_MAGIC; + header.msg_len = msg_len; + 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->read_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_phy_rate(self, phy_rate); + 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); +} + +// 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_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_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, +// disable callbacks and deallocate the recv data buffer. +void common_hal_espnow_deinit(espnow_obj_t *self) { + if (common_hal_espnow_deinited(self)) { + return; + } + + 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; +} + +void espnow_reset(void) { + common_hal_espnow_deinit(MP_STATE_PORT(espnow_singleton)); + MP_STATE_PORT(espnow_singleton) = NULL; +} + +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_RESULT(esp_now_set_pmk(key)); +} + +// --- Send and Receive ESP-NOW data --- + + +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(); + + 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_RESULT(err); + + return mp_const_none; +} + +mp_obj_t common_hal_espnow_read(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); + } + + 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 0000000000..6240788603 --- /dev/null +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -0,0 +1,59 @@ +/* + * 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 "py/ringbuf.h" + +#include "bindings/espnow/Peers.h" + +#include "esp_wifi.h" + +typedef struct _espnow_obj_t { + mp_obj_base_t base; + 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); + +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); + +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_read(espnow_obj_t *self); diff --git a/ports/espressif/common-hal/espnow/__init__.c b/ports/espressif/common-hal/espnow/__init__.c new file mode 100644 index 0000000000..effc752f79 --- /dev/null +++ b/ports/espressif/common-hal/espnow/__init__.c @@ -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. + */ + +#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 0000000000..bb1950d98c --- /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); diff --git a/ports/espressif/esp_error.c b/ports/espressif/esp_error.c deleted file mode 100644 index 4bc44674b7..0000000000 --- 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); -} diff --git a/ports/espressif/mpconfigport.h b/ports/espressif/mpconfigport.h index c296be2024..e4733356bb 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 _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 79515619ea..4637d971a4 100644 --- a/ports/espressif/mpconfigport.mk +++ b/ports/espressif/mpconfigport.mk @@ -82,6 +82,7 @@ CIRCUITPY_DUALBANK = 0 endif # Modules dependent on other modules +CIRCUITPY_ESPNOW ?= $(CIRCUITPY_WIFI) CIRCUITPY_GIFIO ?= $(CIRCUITPY_ESPCAMERA) CIRCUITPY_QRIO ?= $(CIRCUITPY_ESPCAMERA) diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index a71825afa5..db4a436b13 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/__init__.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 43f4fb186a..c849a12851 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -226,6 +226,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/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 8a4cb33cbc..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,13 +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; -}