diff --git a/.gitmodules b/.gitmodules index f02565e6915..1e13cbc8bc4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -424,3 +424,30 @@ [submodule "ports/espressif/esp-hosted"] path = ports/espressif/esp-hosted url = https://github.com/espressif/esp-hosted-mcu.git +[submodule "frozen/Adafruit_CircuitPython_IL0373"] + path = frozen/Adafruit_CircuitPython_IL0373 + url = https://github.com/adafruit/Adafruit_CircuitPython_IL0373.git +[submodule "frozen/Adafruit_CircuitPython_JD79667"] + path = frozen/Adafruit_CircuitPython_JD79667 + url = https://github.com/adafruit/Adafruit_CircuitPython_JD79667.git +[submodule "frozen/Adafruit_CircuitPython_MAX1704x"] + path = frozen/Adafruit_CircuitPython_MAX1704x + url = https://github.com/adafruit/Adafruit_CircuitPython_MAX1704x.git +[submodule "frozen/Adafruit_CircuitPython_NTP"] + path = frozen/Adafruit_CircuitPython_NTP + url = https://github.com/adafruit/Adafruit_CircuitPython_NTP.git +[submodule "frozen/Adafruit_CircuitPython_SCD4X"] + path = frozen/Adafruit_CircuitPython_SCD4X + url = https://github.com/adafruit/Adafruit_CircuitPython_SCD4X.git +[submodule "frozen/Adafruit_CircuitPython_SCD30"] + path = frozen/Adafruit_CircuitPython_SCD30 + url = https://github.com/adafruit/Adafruit_CircuitPython_SCD30.git +[submodule "frozen/Adafruit_CircuitPython_SEN6x"] + path = frozen/Adafruit_CircuitPython_SEN6x + url = https://github.com/adafruit/Adafruit_CircuitPython_SEN6x.git +[submodule "frozen/Adafruit_CircuitPython_PCF8523"] + path = frozen/Adafruit_CircuitPython_PCF8523 + url = https://github.com/adafruit/Adafruit_CircuitPython_PCF8523.git +[submodule "frozen/Adafruit_CircuitPython_DS1307"] + path = frozen/Adafruit_CircuitPython_DS1307 + url = https://github.com/adafruit/Adafruit_CircuitPython_DS1307.git diff --git a/frozen/Adafruit_CircuitPython_DS1307 b/frozen/Adafruit_CircuitPython_DS1307 new file mode 160000 index 00000000000..d9ccc8cbc90 --- /dev/null +++ b/frozen/Adafruit_CircuitPython_DS1307 @@ -0,0 +1 @@ +Subproject commit d9ccc8cbc90af7f5daf380fca9492d3a337430d6 diff --git a/frozen/Adafruit_CircuitPython_IL0373 b/frozen/Adafruit_CircuitPython_IL0373 new file mode 160000 index 00000000000..645f0bf78cd --- /dev/null +++ b/frozen/Adafruit_CircuitPython_IL0373 @@ -0,0 +1 @@ +Subproject commit 645f0bf78cd9e09ef07eaca37871ee310fa36445 diff --git a/frozen/Adafruit_CircuitPython_JD79667 b/frozen/Adafruit_CircuitPython_JD79667 new file mode 160000 index 00000000000..4a5ce533021 --- /dev/null +++ b/frozen/Adafruit_CircuitPython_JD79667 @@ -0,0 +1 @@ +Subproject commit 4a5ce53302152058cac151cef6d5c528304d3437 diff --git a/frozen/Adafruit_CircuitPython_MAX1704x b/frozen/Adafruit_CircuitPython_MAX1704x new file mode 160000 index 00000000000..ad881ac7f2a --- /dev/null +++ b/frozen/Adafruit_CircuitPython_MAX1704x @@ -0,0 +1 @@ +Subproject commit ad881ac7f2adc0ba06f1a153e91658ebcbbacf18 diff --git a/frozen/Adafruit_CircuitPython_NTP b/frozen/Adafruit_CircuitPython_NTP new file mode 160000 index 00000000000..20cb912e92a --- /dev/null +++ b/frozen/Adafruit_CircuitPython_NTP @@ -0,0 +1 @@ +Subproject commit 20cb912e92a8edcb7a69e64f938b33937f55d475 diff --git a/frozen/Adafruit_CircuitPython_PCF8523 b/frozen/Adafruit_CircuitPython_PCF8523 new file mode 160000 index 00000000000..28187e912a1 --- /dev/null +++ b/frozen/Adafruit_CircuitPython_PCF8523 @@ -0,0 +1 @@ +Subproject commit 28187e912a1ddd5ae544857fb449198ea1914197 diff --git a/frozen/Adafruit_CircuitPython_SCD30 b/frozen/Adafruit_CircuitPython_SCD30 new file mode 160000 index 00000000000..a13f91cb423 --- /dev/null +++ b/frozen/Adafruit_CircuitPython_SCD30 @@ -0,0 +1 @@ +Subproject commit a13f91cb423e985e03f9b67ec77bb86df8e24616 diff --git a/frozen/Adafruit_CircuitPython_SCD4X b/frozen/Adafruit_CircuitPython_SCD4X new file mode 160000 index 00000000000..2c625693826 --- /dev/null +++ b/frozen/Adafruit_CircuitPython_SCD4X @@ -0,0 +1 @@ +Subproject commit 2c62569382663b42d79f1780ee647895c58dbc6e diff --git a/frozen/Adafruit_CircuitPython_SEN6x b/frozen/Adafruit_CircuitPython_SEN6x new file mode 160000 index 00000000000..b8dacfc9552 --- /dev/null +++ b/frozen/Adafruit_CircuitPython_SEN6x @@ -0,0 +1 @@ +Subproject commit b8dacfc955208db3127efdf48b52a8e4398bb361 diff --git a/ports/espressif/bindings/espnow/ESPNow.c b/ports/espressif/bindings/espnow/ESPNow.c index 345d51b6dff..f302cf7810a 100644 --- a/ports/espressif/bindings/espnow/ESPNow.c +++ b/ports/espressif/bindings/espnow/ESPNow.c @@ -109,7 +109,10 @@ static MP_DEFINE_CONST_FUN_OBJ_1(espnow_deinit_obj, espnow_deinit); //| 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. +//| :param Peer peer: Send message to this peer. If `None`, send to registered +//| unicast peers only. To broadcast or multicast, pass the corresponding +//| registered `Peer` explicitly. If no unicast peers are registered, +//| sending with `None` raises `OSError`. //| """ //| ... //| @@ -308,7 +311,7 @@ static mp_uint_t espnow_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintpt 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) { + if ((flags & MP_STREAM_POLL_RD) && common_hal_espnow_get_bytes_available(self) > 0) { ret |= MP_STREAM_POLL_RD; } return ret; @@ -337,7 +340,7 @@ static const mp_stream_p_t espnow_stream_p = { static mp_obj_t espnow_unary_op(mp_unary_op_t op, mp_obj_t self_in) { espnow_obj_t *self = MP_OBJ_TO_PTR(self_in); espnow_check_for_deinit(self); - size_t len = ringbuf_num_filled(self->recv_buffer); + size_t len = common_hal_espnow_get_bytes_available(self); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(len != 0); diff --git a/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.h b/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.h index 38b014fd59f..722598eaaf5 100644 --- a/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.h @@ -34,3 +34,8 @@ // UART pins attached to the USB-serial converter chip #define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO1) #define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO3) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.mk index 2569b1cabb1..b93c59e5f3a 100644 --- a/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32_v2/mpconfigboard.mk @@ -10,3 +10,40 @@ CIRCUITPY_ESP_FLASH_SIZE = 8MB CIRCUITPY_ESP_PSRAM_SIZE = 2MB CIRCUITPY_ESP_PSRAM_MODE = qio CIRCUITPY_ESP_PSRAM_FREQ = 40m + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_feather_esp32_v2/partitions.csv b/ports/espressif/boards/adafruit_feather_esp32_v2/partitions.csv new file mode 100644 index 00000000000..bb0a866752c --- /dev/null +++ b/ports/espressif/boards/adafruit_feather_esp32_v2/partitions.csv @@ -0,0 +1,7 @@ +# Env Hub build: 3 MiB OTA slots and 1984 KiB filesystem on the 8 MiB Feather V2. +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 20K +otadata, data, ota, 0xe000, 8K +ota_0, app, ota_0, 0x10000, 3072K +ota_1, app, ota_1, 0x310000, 3072K +user_fs, data, fat, 0x610000, 1984K diff --git a/ports/espressif/boards/adafruit_feather_esp32_v2/sdkconfig b/ports/espressif/boards/adafruit_feather_esp32_v2/sdkconfig index e69de29bb2d..1940c5a74e2 100644 --- a/ports/espressif/boards/adafruit_feather_esp32_v2/sdkconfig +++ b/ports/espressif/boards/adafruit_feather_esp32_v2/sdkconfig @@ -0,0 +1,9 @@ +# Frozen Env Hub libraries exceed the original 2 MiB application slot. +# Updating this layout moves user_fs; back up and restore files when flashing. +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="boards/adafruit_feather_esp32_v2/partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="boards/adafruit_feather_esp32_v2/partitions.csv" + +# Allow the collector's certificate chain to fit in an outbound TLS record. +# Keep the upstream 16 KiB inbound record limit. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 diff --git a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.h b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.h index 969bf468574..363182ecf80 100644 --- a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.h @@ -32,3 +32,8 @@ // Explanation of how a user got into safe mode #define BOARD_USER_SAFE_MODE_ACTION MP_ERROR_TEXT("You pressed the BOOT button at start up.") + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk index 28821ed7b48..28ee74d2e95 100644 --- a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/mpconfigboard.mk @@ -7,5 +7,43 @@ CIRCUITPY_ESP_FLASH_MODE = qio CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_FLASH_SIZE = 4MB -# Board was originally defined with a 2MB firmware, almost 2MB user filesystem. Leave it that way. -CIRCUITPY_4MB_FLASH_LARGE_USER_FS_LAYOUT = 1 +# Frozen Env Hub libraries exceed 2 MB. Use the standard larger app layout; +# back up and restore the filesystem when upgrading from the old layout. +CIRCUITPY_4MB_FLASH_LARGE_USER_FS_LAYOUT = 0 + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/sdkconfig b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/sdkconfig index e9628662160..a3c8f48f955 100644 --- a/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/sdkconfig +++ b/ports/espressif/boards/adafruit_feather_esp32c6_4mbflash_nopsram/sdkconfig @@ -12,3 +12,6 @@ # end of Component config # end of Espressif IoT Development Framework Configuration +# Fit the Env Hub server certificate chain in an outbound TLS record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.h b/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.h index 7abd61a7d42..22c17bdb966 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.h @@ -27,3 +27,8 @@ #define DEFAULT_UART_BUS_TX (&pin_GPIO39) #define DOUBLE_TAP_PIN (&pin_GPIO34) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk index 6fafa82485a..deeaa2e728d 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk @@ -12,3 +12,47 @@ CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_PSRAM_SIZE = 2MB CIRCUITPY_ESP_PSRAM_MODE = qio CIRCUITPY_ESP_PSRAM_FREQ = 80m + +# This project needs _bleio on this board: the hub serves its portal over +# a Nordic UART service and the nodes take their config the same way. The +# port already defaults CIRCUITPY_BLEIO_NATIVE to 1 and only forces it off +# for 2MB flash, the S2 and the P4 -- stated here so a future flash-size +# trim cannot take it away silently. +CIRCUITPY_BLEIO_NATIVE = 1 + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/sdkconfig b/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/sdkconfig index e9628662160..a3c8f48f955 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/sdkconfig +++ b/ports/espressif/boards/adafruit_feather_esp32s3_4mbflash_2mbpsram/sdkconfig @@ -12,3 +12,6 @@ # end of Component config # end of Espressif IoT Development Framework Configuration +# Fit the Env Hub server certificate chain in an outbound TLS record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.h b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.h index a47bc8d859e..2c3af372b45 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.h @@ -27,3 +27,8 @@ #define DEFAULT_UART_BUS_TX (&pin_GPIO39) #define DOUBLE_TAP_PIN (&pin_GPIO34) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk index cdf1657d8be..140d2150891 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk @@ -8,3 +8,40 @@ IDF_TARGET = esp32s3 CIRCUITPY_ESP_FLASH_MODE = qio CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_FLASH_SIZE = 8MB + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/sdkconfig b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/sdkconfig index e9628662160..a3c8f48f955 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/sdkconfig +++ b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/sdkconfig @@ -12,3 +12,6 @@ # end of Component config # end of Espressif IoT Development Framework Configuration +# Fit the Env Hub server certificate chain in an outbound TLS record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.h b/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.h index 5053a1b2b58..29b2239e88c 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.h @@ -27,3 +27,8 @@ #define DEFAULT_UART_BUS_TX (&pin_GPIO39) #define DOUBLE_TAP_PIN (&pin_GPIO34) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.mk index a82ae6d4f04..c51218faf2b 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32s3_reverse_tft/mpconfigboard.mk @@ -16,3 +16,47 @@ CIRCUITPY_ESP_PSRAM_FREQ = 80m # Already have a display. CIRCUITPY_PARALLELDISPLAYBUS = 0 + +# This project needs _bleio on this board: the hub serves its portal over +# a Nordic UART service and the nodes take their config the same way. The +# port already defaults CIRCUITPY_BLEIO_NATIVE to 1 and only forces it off +# for 2MB flash, the S2 and the P4 -- stated here so a future flash-size +# trim cannot take it away silently. +CIRCUITPY_BLEIO_NATIVE = 1 + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.h b/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.h index 243777fd8df..3def649a3aa 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.h @@ -27,3 +27,8 @@ #define DEFAULT_UART_BUS_TX (&pin_GPIO1) #define DOUBLE_TAP_PIN (&pin_GPIO38) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.mk index 71ef6d222a2..81021c36f4f 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32s3_tft/mpconfigboard.mk @@ -16,3 +16,47 @@ CIRCUITPY_ESP_PSRAM_FREQ = 80m # Already have a display. CIRCUITPY_PARALLELDISPLAYBUS = 0 + +# This project needs _bleio on this board: the hub serves its portal over +# a Nordic UART service and the nodes take their config the same way. The +# port already defaults CIRCUITPY_BLEIO_NATIVE to 1 and only forces it off +# for 2MB flash, the S2 and the P4 -- stated here so a future flash-size +# trim cannot take it away silently. +CIRCUITPY_BLEIO_NATIVE = 1 + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.h b/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.h index 9bd51044f4e..c6ace45d75a 100644 --- a/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.h @@ -25,3 +25,8 @@ // UART pins attached to the USB-serial converter chip #define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO1) #define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO3) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.mk index f948521eb6d..fb434010d84 100644 --- a/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_huzzah32/mpconfigboard.mk @@ -6,3 +6,40 @@ IDF_TARGET = esp32 CIRCUITPY_ESP_FLASH_MODE = qio CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_FLASH_SIZE = 4MB + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.h b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.h index 2442d28e712..3cd07dc2a68 100644 --- a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.h @@ -26,3 +26,8 @@ #define DEFAULT_UART_BUS_TX (&pin_GPIO40) #define DOUBLE_TAP_PIN (&pin_GPIO38) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk index 2fd5a40f259..7b9d8f60723 100644 --- a/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_metro_esp32s3/mpconfigboard.mk @@ -12,3 +12,40 @@ CIRCUITPY_ESP_FLASH_SIZE = 16MB CIRCUITPY_ESP_PSRAM_MODE = opi CIRCUITPY_ESP_PSRAM_FREQ = 80m CIRCUITPY_ESP_PSRAM_SIZE = 8MB + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_metro_esp32s3/partitions.csv b/ports/espressif/boards/adafruit_metro_esp32s3/partitions.csv new file mode 100644 index 00000000000..648d43e07b8 --- /dev/null +++ b/ports/espressif/boards/adafruit_metro_esp32s3/partitions.csv @@ -0,0 +1,11 @@ +# Env Hub build: 3 MB OTA slots on the 16 MB Metro ESP32-S3. +# Keep the existing TinyUF2 factory app at 0x410000. The gap before it +# preserves that address while allowing ota_0 to grow. user_fs moves: +# back up its files and reformat/restore when installing this layout. +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 20K +otadata, data, ota, 0xe000, 8K +ota_0, app, ota_0, 0x10000, 3072K +uf2, app, factory, 0x410000, 256K +ota_1, app, ota_1, 0x450000, 3072K +user_fs, data, fat, 0x750000, 8896K diff --git a/ports/espressif/boards/adafruit_metro_esp32s3/sdkconfig b/ports/espressif/boards/adafruit_metro_esp32s3/sdkconfig index e9628662160..51f3f883e07 100644 --- a/ports/espressif/boards/adafruit_metro_esp32s3/sdkconfig +++ b/ports/espressif/boards/adafruit_metro_esp32s3/sdkconfig @@ -12,3 +12,10 @@ # end of Component config # end of Espressif IoT Development Framework Configuration +# Env Hub frozen libraries exceed the default 2 MB application slot. +# Preserve TinyUF2's factory offset; back up user_fs before upgrading. +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="boards/adafruit_metro_esp32s3/partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="boards/adafruit_metro_esp32s3/partitions.csv" +# Fit the Env Hub server certificate chain in an outbound TLS record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 diff --git a/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.h b/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.h index a0bc2b9f374..9864578e595 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.h @@ -29,3 +29,8 @@ // Reduce wifi.radio.tx_power due to the antenna design of this board #define CIRCUITPY_WIFI_DEFAULT_TX_POWER (15) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.mk b/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.mk index e27e48cf51e..07aaaaa91a4 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_qtpy_esp32_pico/mpconfigboard.mk @@ -13,3 +13,40 @@ CIRCUITPY_ESP_PSRAM_FREQ = 80m # Not enough pins. CIRCUITPY_PARALLELDISPLAYBUS = 0 + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.h b/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.h index 9760bbfeb44..09430af6327 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.h @@ -28,3 +28,8 @@ // Reduce wifi.radio.tx_power due to the antenna design of this board #define CIRCUITPY_WIFI_DEFAULT_TX_POWER (15) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk index 09571a63a59..6898b808757 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_qtpy_esp32s3_4mbflash_2mbpsram/mpconfigboard.mk @@ -16,3 +16,47 @@ CIRCUITPY_ESP_PSRAM_FREQ = 80m # Not enough pins. CIRCUITPY_ESPCAMERA = 0 CIRCUITPY_PARALLELDISPLAYBUS = 0 + +# This project needs _bleio on this board: the hub serves its portal over +# a Nordic UART service and the nodes take their config the same way. The +# port already defaults CIRCUITPY_BLEIO_NATIVE to 1 and only forces it off +# for 2MB flash, the S2 and the P4 -- stated here so a future flash-size +# trim cannot take it away silently. +CIRCUITPY_BLEIO_NATIVE = 1 + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.h b/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.h index 6073bd47d0e..f6409a25ed1 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.h +++ b/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.h @@ -28,3 +28,8 @@ // Reduce wifi.radio.tx_power due to the antenna design of this board #define CIRCUITPY_WIFI_DEFAULT_TX_POWER (15) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.mk index 053b15647c1..b0955f310b6 100644 --- a/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_qtpy_esp32s3_nopsram/mpconfigboard.mk @@ -12,3 +12,40 @@ CIRCUITPY_ESP_FLASH_SIZE = 8MB # Few pins. CIRCUITPY_PARALLELDISPLAYBUS = 0 + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.h b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.h index ca05bf78b6a..a13e0c67948 100644 --- a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.h +++ b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.h @@ -15,3 +15,11 @@ #define DEFAULT_UART_BUS_RX (&pin_GPIO12) #define DEFAULT_UART_BUS_TX (&pin_GPIO11) + +// Optional bench console through the onboard CP2102N. This uses fewer USB/IP +// host channels than the native composite USB-JTAG interface on cascaded hubs. +// Build with CIRCUITPY_ESP_USB_SERIAL_JTAG=0; the normal USB console stays default. +#if !CIRCUITPY_ESP_USB_SERIAL_JTAG +#define CIRCUITPY_CONSOLE_UART_RX DEFAULT_UART_BUS_RX +#define CIRCUITPY_CONSOLE_UART_TX DEFAULT_UART_BUS_TX +#endif diff --git a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk index 0da008596e3..b77640089cc 100644 --- a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk +++ b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/mpconfigboard.mk @@ -3,6 +3,30 @@ CIRCUITPY_CREATION_ID = 0x00C50001 IDF_TARGET = esp32c5 +# Freeze the collector dependencies to match the other Env Hub builds. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 + CIRCUITPY_ESP_FLASH_MODE = dio CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_FLASH_SIZE = 8MB diff --git a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/partitions.csv b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/partitions.csv new file mode 100644 index 00000000000..91c771c6b8c --- /dev/null +++ b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/partitions.csv @@ -0,0 +1,7 @@ +# Collector build only: 3 MB OTA slots, 1984 KB filesystem on an 8 MB board. +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 20K +otadata, data, ota, 0xe000, 8K +ota_0, app, ota_0, 0x10000, 3072K +ota_1, app, ota_1, 0x310000, 3072K +user_fs, data, fat, 0x610000, 1984K diff --git a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/sdkconfig b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/sdkconfig index e9628662160..dc35b010ca2 100644 --- a/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/sdkconfig +++ b/ports/espressif/boards/espressif_esp32c5_devkitc_1_n8r8/sdkconfig @@ -12,3 +12,10 @@ # end of Component config # end of Espressif IoT Development Framework Configuration +# Allow the collector's TLS certificate chain to fit in a record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +# Frozen libraries need a larger application slot; back up user_fs before flashing. +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="boards/espressif_esp32c5_devkitc_1_n8r8/partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="boards/espressif_esp32c5_devkitc_1_n8r8/partitions.csv" diff --git a/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.h b/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.h index d901bfb37ba..887342b4c1a 100644 --- a/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.h +++ b/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.h @@ -13,3 +13,11 @@ #define DEFAULT_UART_BUS_RX (&pin_GPIO10) #define DEFAULT_UART_BUS_TX (&pin_GPIO11) + +// Optional bench console through the onboard CP2102N. This uses fewer USB/IP +// host channels than the native composite USB-JTAG interface on cascaded hubs. +// Build with CIRCUITPY_ESP_USB_SERIAL_JTAG=0; the normal USB console stays default. +#if !CIRCUITPY_ESP_USB_SERIAL_JTAG +#define CIRCUITPY_CONSOLE_UART_RX DEFAULT_UART_BUS_RX +#define CIRCUITPY_CONSOLE_UART_TX DEFAULT_UART_BUS_TX +#endif diff --git a/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.mk b/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.mk index 1092a874a68..7713af5e293 100644 --- a/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.mk +++ b/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/mpconfigboard.mk @@ -2,6 +2,30 @@ CIRCUITPY_CREATOR_ID = 0x000C303A CIRCUITPY_CREATION_ID = 0x00C61001 IDF_TARGET = esp32c61 + +# Freeze the collector dependencies to match the other Env Hub builds. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 IDF_TARGET_ARCH = riscv CIRCUITPY_ESP_FLASH_MODE = qio diff --git a/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/sdkconfig b/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/sdkconfig index e9628662160..c19c2594eac 100644 --- a/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/sdkconfig +++ b/ports/espressif/boards/espressif_esp32c61_devkitc_1_n8r2/sdkconfig @@ -12,3 +12,7 @@ # end of Component config # end of Espressif IoT Development Framework Configuration +# Allow the collector's TLS certificate chain to fit in a record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 diff --git a/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.h b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.h index e5f2606cf49..96c91c98d9b 100644 --- a/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.h +++ b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.h @@ -15,3 +15,8 @@ #define DEFAULT_UART_BUS_RX (&pin_GPIO17) #define DEFAULT_UART_BUS_TX (&pin_GPIO16) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.mk b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.mk index e15d2bf41e8..b4507b34995 100644 --- a/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.mk +++ b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/mpconfigboard.mk @@ -6,3 +6,40 @@ IDF_TARGET = esp32c6 CIRCUITPY_ESP_FLASH_MODE = qio CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_FLASH_SIZE = 8MB + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/partitions.csv b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/partitions.csv new file mode 100644 index 00000000000..91c771c6b8c --- /dev/null +++ b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/partitions.csv @@ -0,0 +1,7 @@ +# Collector build only: 3 MB OTA slots, 1984 KB filesystem on an 8 MB board. +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 20K +otadata, data, ota, 0xe000, 8K +ota_0, app, ota_0, 0x10000, 3072K +ota_1, app, ota_1, 0x310000, 3072K +user_fs, data, fat, 0x610000, 1984K diff --git a/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/sdkconfig b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/sdkconfig index e9628662160..b372712b4ca 100644 --- a/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/sdkconfig +++ b/ports/espressif/boards/espressif_esp32c6_devkitc_1_n8/sdkconfig @@ -12,3 +12,13 @@ # end of Component config # end of Espressif IoT Development Framework Configuration + +# The collector serves a certificate chain larger than the upstream 2 KB record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 + +# Frozen collector libraries exceed the upstream 2 MB application slot on C6. +# This layout moves user_fs: back up CIRCUITPY before installing or reverting. +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="boards/espressif_esp32c6_devkitc_1_n8/partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="boards/espressif_esp32c6_devkitc_1_n8/partitions.csv" diff --git a/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.h b/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.h index ab942513937..01da6c40bdc 100644 --- a/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.h +++ b/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.h @@ -15,3 +15,8 @@ #define DEFAULT_UART_BUS_RX (&pin_GPIO44) #define DEFAULT_UART_BUS_TX (&pin_GPIO43) + +// Bench finding: 32 KB of IDF headroom is not enough for a softAP that also +// serves TLS -- handshakes hit MemoryError and DHCP replies fail while the +// Python heap still reports free space. 40 KB is what the C6 hub needed. +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (40 * 1024) diff --git a/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.mk b/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.mk index ea5597d3702..b67fa3c80c6 100644 --- a/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.mk +++ b/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/mpconfigboard.mk @@ -8,3 +8,40 @@ IDF_TARGET = esp32s3 CIRCUITPY_ESP_FLASH_MODE = qio CIRCUITPY_ESP_FLASH_FREQ = 80m CIRCUITPY_ESP_FLASH_SIZE = 8MB + +# --- environmental-collector frozen libraries ------------------------------- +# Frozen bytecode executes in place from flash; the same library imported +# from lib/ is read into RAM and stays there. On a board without PSRAM that +# is the difference between running and not -- a Feather ESP32-S3 No PSRAM +# running this project, with its own code already cross-compiled to .mpy, +# had 6 KB of heap left once BLE + ESP-NOW + softAP were up and died on the +# next import. +# +# Listed literally rather than behind an include, even though the same 22 +# lines repeat across every board this project targets: tools/ +# ci_fetch_deps.py scans THIS file for "FROZEN_MPY_DIRS += $(TOP)/" to +# decide which submodules to check out, and it does not follow includes. An +# include builds locally and then fails CI at MKMANIFEST with empty frozen +# directories. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Requests +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ConnectionManager +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NTP +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BusDevice +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Ticks +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SEN6x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD4X +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SCD30 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_MAX1704x +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LC709203F +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_JD79667 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IL0373 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Display_Text +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Bitmap_Font +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8563 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS3231 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_PCF8523 +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DS1307 diff --git a/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/sdkconfig b/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/sdkconfig index e9628662160..095c89509ee 100644 --- a/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/sdkconfig +++ b/ports/espressif/boards/espressif_esp32s3_devkitc_1_n8/sdkconfig @@ -12,3 +12,7 @@ # end of Component config # end of Espressif IoT Development Framework Configuration +# The collector serves a certificate chain larger than the upstream 2 KB record. +# The current three-certificate chain needs 4159 bytes including TLS framing. +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=8192 +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 diff --git a/ports/espressif/common-hal/espnow/ESPNow.c b/ports/espressif/common-hal/espnow/ESPNow.c index eb3b57174a3..1dbd42d0afa 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.c +++ b/ports/espressif/common-hal/espnow/ESPNow.c @@ -20,6 +20,8 @@ #include "esp_now.h" #include "mphalport.h" +#include "freertos/FreeRTOS.h" + #include "esp_now.h" #define ESPNOW_MAGIC 0x99 @@ -52,6 +54,10 @@ typedef struct { uint8_t msg[0]; // Message is up to 250 bytes } __attribute__((packed)) espnow_packet_t; +// The Wi-Fi task may run on a different core from the VM. Protect complete +// packets and the ring buffer's shared counters, not just individual copies. +static portMUX_TYPE recv_buffer_mutex = portMUX_INITIALIZER_UNLOCKED; + // --- The ESP-NOW send and recv callback routines --- // Callback triggered when a sent packet is acknowledged by the peer (or not). @@ -73,8 +79,11 @@ static void recv_cb(const esp_now_recv_info_t *esp_now_info, const uint8_t *msg, 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)) { + portENTER_CRITICAL(&recv_buffer_mutex); + if (msg_len < 0 || msg_len > ESP_NOW_MAX_DATA_LEN || + sizeof(espnow_packet_t) + msg_len > ringbuf_num_empty(buf)) { self->read_failure++; + portEXIT_CRITICAL(&recv_buffer_mutex); return; } @@ -89,6 +98,7 @@ static void recv_cb(const esp_now_recv_info_t *esp_now_info, const uint8_t *msg, ringbuf_put_n(buf, msg, msg_len); self->read_success++; + portEXIT_CRITICAL(&recv_buffer_mutex); } bool common_hal_espnow_deinited(espnow_obj_t *self) { @@ -183,33 +193,40 @@ mp_obj_t common_hal_espnow_send(espnow_obj_t *self, const mp_buffer_info_t *mess return mp_const_none; } +size_t common_hal_espnow_get_bytes_available(espnow_obj_t *self) { + portENTER_CRITICAL(&recv_buffer_mutex); + size_t available = ringbuf_num_filled(self->recv_buffer); + portEXIT_CRITICAL(&recv_buffer_mutex); + return available; +} + mp_obj_t common_hal_espnow_read(espnow_obj_t *self) { + // Copy the entire packet while locked. Allocating Python objects or raising + // an exception must happen after releasing the cross-core critical section. + espnow_header_t header; + uint8_t mac_buf[ESP_NOW_ETH_ALEN]; + uint8_t msg_buf[ESP_NOW_MAX_DATA_LEN]; + + portENTER_CRITICAL(&recv_buffer_mutex); if (!ringbuf_num_filled(self->recv_buffer)) { + portEXIT_CRITICAL(&recv_buffer_mutex); 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]; + bool valid = ringbuf_get_n(self->recv_buffer, (uint8_t *)&header, sizeof(header)) == sizeof(header) && + header.magic == ESPNOW_MAGIC && + header.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, header.msg_len) == header.msg_len; + portEXIT_CRITICAL(&recv_buffer_mutex); - // 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) { + if (!valid) { 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_bytes(msg_buf, header.msg_len), MP_OBJ_NEW_SMALL_INT(header.rssi), mp_obj_new_int(header.time_ms), }; diff --git a/ports/espressif/common-hal/espnow/ESPNow.h b/ports/espressif/common-hal/espnow/ESPNow.h index b68e1544fb8..c842eb2db2d 100644 --- a/ports/espressif/common-hal/espnow/ESPNow.h +++ b/ports/espressif/common-hal/espnow/ESPNow.h @@ -37,3 +37,5 @@ 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); + +extern size_t common_hal_espnow_get_bytes_available(espnow_obj_t *self); diff --git a/ports/espressif/mpconfigport.h b/ports/espressif/mpconfigport.h index 7de44130971..92f95f7f627 100644 --- a/ports/espressif/mpconfigport.h +++ b/ports/espressif/mpconfigport.h @@ -73,6 +73,14 @@ extern portMUX_TYPE background_task_mutex; #define CIRCUITPY_WIFI_DEFAULT_TX_POWER (20) #endif +// IDF heap kept free while the wifi radio or BLE adapter is enabled. The GC heap +// grows on demand (MICROPY_GC_SPLIT_HEAP_AUTO) and would otherwise consume the +// whole system heap, leaving the wifi driver, lwIP and NimBLE unable to allocate +// (ESP-NOW sends fail, softAP stations get no DHCP lease). Boards can override. +#ifndef CIRCUITPY_ESP_RADIO_HEAP_RESERVE +#define CIRCUITPY_ESP_RADIO_HEAP_RESERVE (32 * 1024) +#endif + #ifndef CIRCUITPY_ESP32P4_SWAP_LSFS #define CIRCUITPY_ESP32P4_SWAP_LSFS (0) #endif diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 91fe8cf5a2e..ad6294a4df6 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -39,6 +39,8 @@ #include "common-hal/watchdog/WatchDogTimer.h" #include "common-hal/socketpool/Socket.h" #include "common-hal/wifi/__init__.h" +#include "shared-bindings/wifi/__init__.h" +#include "shared-bindings/wifi/Radio.h" #include "supervisor/background_callback.h" #include "supervisor/shared/tick.h" #include "shared-bindings/microcontroller/__init__.h" @@ -57,6 +59,7 @@ #if CIRCUITPY_BLEIO_NATIVE #include "shared-bindings/_bleio/__init__.h" +#include "shared-bindings/_bleio/Adapter.h" #endif #if CIRCUITPY_ESPCAMERA @@ -381,9 +384,27 @@ void *port_realloc(void *ptr, size_t size, bool dma_capable) { return heap_caps_realloc(ptr, size, caps); } +// Cap what the GC may take so CIRCUITPY_ESP_RADIO_HEAP_RESERVE of the IDF heap +// stays available to the radio stacks while one is enabled (see mpconfigport.h). size_t port_heap_get_largest_free_size(void) { - size_t free_size = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); - return free_size; + size_t largest = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); + size_t reserve = 0; + #if CIRCUITPY_WIFI + if (common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { + reserve = CIRCUITPY_ESP_RADIO_HEAP_RESERVE; + } + #endif + #if CIRCUITPY_BLEIO_NATIVE + if (common_hal_bleio_adapter_get_enabled(&common_hal_bleio_adapter_obj)) { + reserve = CIRCUITPY_ESP_RADIO_HEAP_RESERVE; + } + #endif + if (reserve == 0) { + return largest; + } + size_t total = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t allowed = total > reserve ? total - reserve : 0; + return MIN(largest, allowed); } #if MICROPY_PERSISTENT_CODE_LOAD_NATIVE diff --git a/py/version.py b/py/version.py index 7ef2a587628..76ebe247e1a 100755 --- a/py/version.py +++ b/py/version.py @@ -3,7 +3,11 @@ def get_version_info_from_git(repo_path, extra_args=[]): - if "CP_VERSION" in os.environ: + # CIRCUITPY-CHANGE: treat an empty CP_VERSION as unset. A workflow that routes an + # unpopulated step output through `env:` (build-board-custom.yml -> + # .github/actions/mpy_cross does this for boards with frozen modules) exports + # CP_VERSION="" and must fall back to git describe instead of failing. + if os.environ.get("CP_VERSION"): git_tag = os.environ["CP_VERSION"] else: # Note: git describe doesn't work if no tag is available diff --git a/shared-module/ssl/SSLSocket.c b/shared-module/ssl/SSLSocket.c index 6d306c2f071..0d18b0ec5bb 100644 --- a/shared-module/ssl/SSLSocket.c +++ b/shared-module/ssl/SSLSocket.c @@ -22,6 +22,8 @@ #include "shared-bindings/socketpool/enum.h" +#include "mbedtls/ssl_ticket.h" + #define MP_STREAM_POLL_RDWR (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR) #if defined(MBEDTLS_ERROR_C) @@ -325,7 +327,16 @@ ssl_sslsocket_obj_t *common_hal_ssl_sslcontext_wrap_socket(ssl_sslcontext_obj_t goto cleanup; } - if (self->crt_bundle_attach != NULL) { + if (server_side && !(self->cacert_buf && self->cacert_bytes)) { + // On a server the CA store is about authenticating *clients*. A + // context that only had load_cert_chain() called on it -- the normal + // HTTPS-server setup -- must not demand a client certificate, which is + // what the default root bundle turned into: every handshake failed + // with MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE. Match CPython, where a + // server-side context defaults to CERT_NONE; a program that loads its + // own CA with load_verify_locations() still gets client verification. + mbedtls_ssl_conf_authmode(&o->conf, MBEDTLS_SSL_VERIFY_NONE); + } else if (self->crt_bundle_attach != NULL) { mbedtls_ssl_conf_authmode(&o->conf, MBEDTLS_SSL_VERIFY_REQUIRED); self->crt_bundle_attach(&o->conf); } else if (self->cacert_buf && self->cacert_bytes) { @@ -372,6 +383,23 @@ ssl_sslsocket_obj_t *common_hal_ssl_sslcontext_wrap_socket(ssl_sslcontext_obj_t goto cleanup; } } + #if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_TICKET_C) + if (server_side) { + // Session tickets let a returning client resume without the (expensive) public-key + // handshake. One process-wide ticket key, rotated by lifetime; nothing to persist. + static mbedtls_ssl_ticket_context ticket_ctx; + static bool ticket_ready = false; + if (!ticket_ready) { + mbedtls_ssl_ticket_init(&ticket_ctx); + if (mbedtls_ssl_ticket_setup(&ticket_ctx, PSA_ALG_GCM, PSA_KEY_TYPE_AES, 256, 86400) == 0) { + ticket_ready = true; + } + } + if (ticket_ready) { + mbedtls_ssl_conf_session_tickets_cb(&o->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse, &ticket_ctx); + } + } + #endif return o; cleanup: mbedtls_pk_free(&o->pkey); diff --git a/tools/check_espnow_rx_lock.py b/tools/check_espnow_rx_lock.py new file mode 100755 index 00000000000..e3ab0a78f73 --- /dev/null +++ b/tools/check_espnow_rx_lock.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +"""Exercise actual ESP-NOW packet functions with deterministic two-thread scheduling. + +Requires a POSIX C compiler. Kernel critical sections are mapped to pthread +mutexes; packet and ring-buffer algorithms are extracted unchanged from source. +""" +import argparse +import hashlib +import json +from pathlib import Path +import re +import subprocess +import tempfile + + +def function(text, name): + start = re.search(r'^[\w *]+\b' + re.escape(name) + r'\([^;]*?\)\s*\{', text, re.M).start() + opening = text.index('{', start) + depth, end = 1, opening + 1 + while depth: + depth += (text[end] == '{') - (text[end] == '}') + end += 1 + return text[start:end] + + +PREFIX = r''' +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define ESPNOW_MAGIC 0x99 +#define ESP_NOW_ETH_ALEN 6 +#define ESP_NOW_MAX_DATA_LEN 250 +#define m_malloc malloc +#define portMUX_TYPE pthread_mutex_t +#define portMUX_INITIALIZER_UNLOCKED PTHREAD_MUTEX_INITIALIZER +static _Thread_local int locked, is_worker; +static atomic_int worker_attempted, worker_go, worker_done; +static void enter(pthread_mutex_t *m) { + if (is_worker) atomic_store(&worker_attempted,1); + assert(pthread_mutex_lock(m)==0); locked++; +} +static void leave(pthread_mutex_t *m) { + assert(locked==1); locked--; assert(pthread_mutex_unlock(m)==0); +} +#define portENTER_CRITICAL(m) enter(m) +#define portEXIT_CRITICAL(m) leave(m) +typedef uintptr_t mp_obj_t; +typedef int mp_obj_type_t; +static int espnow_packet_type_obj; +#define mp_const_none ((mp_obj_t)0) +#define MP_QSTR_buffer 1 +#define MP_OBJ_NEW_SMALL_INT(v) ((mp_obj_t)(v)) +static _Thread_local jmp_buf error_target; +static _Thread_local uint8_t got_mac[6],got_payload[250]; +static _Thread_local size_t got_length; +static _Thread_local int bytes_index; +static void mp_arg_error_invalid(int q) { (void)q; assert(!locked); longjmp(error_target,1); } +static mp_obj_t mp_obj_new_bytes(const uint8_t *p,size_t n) { + assert(!locked); + if (bytes_index++==0) { assert(n==6); memcpy(got_mac,p,n); } + else { assert(n<=250);memcpy(got_payload,p,n);got_length=n; } + return 1; +} +static mp_obj_t mp_obj_new_int(uint32_t n) { assert(!locked);return n; } +static mp_obj_t namedtuple_make_new(const mp_obj_type_t *t,size_t n,size_t k,const mp_obj_t *e) { + (void)t;(void)n;(void)k;(void)e;assert(!locked);return 1; +} +static uint32_t mp_hal_ticks_ms(void) { return 123; } +typedef struct { int8_t rssi; } rx_ctrl_t; +typedef struct { const uint8_t *src_addr; rx_ctrl_t *rx_ctrl; } esp_now_recv_info_t; +''' + +MIDDLE = r''' +typedef struct { ringbuf_t *recv_buffer;unsigned read_failure,read_success; } espnow_obj_t; +static espnow_obj_t obj; +#define MP_STATE_PORT(x) (&obj) +static int inject_put,inject_get; +static void scheduling_point(void) { + atomic_store(&worker_go,1); + // On the fixed source the worker reaches the lock but cannot enter. On the + // baseline it finishes reading the partial packet before the writer resumes. + while (!atomic_load(&worker_attempted) && !atomic_load(&worker_done)) sched_yield(); +} +static size_t scheduling_put(ringbuf_t *r,const uint8_t *b,size_t n) { + size_t result=ringbuf_put_n(r,b,n); + if (inject_put && !is_worker) { inject_put=0;scheduling_point(); } + return result; +} +static size_t scheduling_get(ringbuf_t *r,uint8_t *b,size_t n) { + size_t result=ringbuf_get_n(r,b,n); + if (inject_get && !is_worker) { inject_get=0;scheduling_point(); } + return result; +} +#define ringbuf_put_n scheduling_put +#define ringbuf_get_n scheduling_get +''' + +MAIN = r''' +#undef ringbuf_put_n +#undef ringbuf_get_n +static uint8_t storage[1024],mac[6]={2,3,4,5,6,7},payload[250]; +static ringbuf_t rb; +static rx_ctrl_t rx={-30}; +static esp_now_recv_info_t info={mac,&rx}; +static int worker_action,worker_result; +static size_t worker_available; +static void reset_case(size_t capacity) { + assert(!locked);ringbuf_init(&rb,storage,capacity);obj=(espnow_obj_t){.recv_buffer=&rb}; + inject_put=inject_get=0;atomic_store(&worker_go,0);atomic_store(&worker_done,0); + atomic_store(&worker_attempted,0);worker_result=0;worker_available=0; +} +static int safe_read(void) { + bytes_index=0; + if (setjmp(error_target)) return -1; + return common_hal_espnow_read(&obj) ? 1 : 0; +} +static void check_payload(size_t length) { + assert(got_length==length && memcmp(got_mac,mac,6)==0); + assert(memcmp(got_payload,payload,length)==0); +} +static void *worker(void *unused) { + (void)unused;is_worker=1; + while(!atomic_load(&worker_go)) sched_yield(); + if(worker_action==0) { worker_result=safe_read();if(worker_result==1) check_payload(20); } + else if(worker_action==1) recv_cb(&info,payload,20); + else worker_available=common_hal_espnow_get_bytes_available(&obj); + assert(!locked);atomic_store(&worker_done,1);return NULL; +} +static void start_worker(pthread_t *t,int action) { + worker_action=action;assert(pthread_create(t,NULL,worker,NULL)==0); +} +static atomic_int producer_done; +static unsigned delivered; +static void *producer(void *unused) { + (void)unused; + uint8_t p[250]; + for(uint32_t seq=1;seq<=20000;seq++) { + memcpy(p,&seq,4);for(size_t k=4;k=0); + if(result) { + assert(got_length==250 && memcmp(got_mac,mac,6)==0); + uint32_t seq;memcpy(&seq,got_payload,4);assert(seq>previous && seq<=20000);previous=seq; + for(size_t k=4;k<250;k++)assert(got_payload[k]==(uint8_t)(seq+k)); + delivered++; + } else sched_yield(); + } + return NULL; +} +int main(void) { + memset(payload,0x42,sizeof(payload));pthread_t t; + reset_case(sizeof(storage));assert(safe_read()==0);assert(!locked); + puts("PASS empty_read_unlocks"); + for(size_t n=0;n<=250;n+=(n==0?1:249)) { + reset_case(sizeof(espnow_packet_t)+n);recv_cb(&info,payload,n); + assert(safe_read()==1);check_payload(n);assert(rb.used==0 && obj.read_success==1); + } + puts("PASS zero_one_maximum_payload_exact_capacity"); + reset_case(sizeof(espnow_packet_t)+19);recv_cb(&info,payload,20); + assert(obj.read_failure==1 && rb.used==0 && !locked); + puts("PASS insufficient_capacity_drops_whole_packet_and_unlocks"); + reset_case(sizeof(storage));start_worker(&t,0);inject_put=1; + recv_cb(&info,payload,20);assert(pthread_join(t,NULL)==0); + if(EXPECT_RACE) { + assert(worker_result==-1 && safe_read()==-1); + puts("REPRODUCED partial_header_read_and_corrupt_followup_on_baseline");return 0; + } + assert(worker_result==1 && rb.used==0 && obj.read_success==1); + puts("PASS reader_waits_for_complete_packet"); + reset_case(sizeof(storage));start_worker(&t,2);inject_put=1; + recv_cb(&info,payload,20);assert(pthread_join(t,NULL)==0); + assert(worker_available==sizeof(espnow_packet_t)+20 && safe_read()==1); + puts("PASS readiness_waits_for_complete_packet"); + reset_case(sizeof(storage));recv_cb(&info,payload,20);start_worker(&t,1);inject_get=1; + assert(safe_read()==1);check_payload(20);assert(pthread_join(t,NULL)==0); + assert(safe_read()==1);check_payload(20);assert(rb.used==0); + puts("PASS producer_waits_for_complete_dequeue"); + reset_case(sizeof(espnow_packet_t)+250); + for(int i=0;i<400;i++) { + size_t n=(size_t)(i%251);recv_cb(&info,payload,n);assert(safe_read()==1);check_payload(n); + } + puts("PASS wraparound_variable_lengths"); + reset_case(sizeof(storage));recv_cb(&info,payload,-1);recv_cb(&info,payload,251); + assert(obj.read_failure==2 && rb.used==0 && !locked); + puts("PASS_invalid_callback_lengths_drop_and_unlock"); + for(int corrupt=0;corrupt<4;corrupt++) { + reset_case(sizeof(storage));recv_cb(&info,payload,20); + if(corrupt==0)storage[0]=0; + if(corrupt==1)storage[1]=251; + if(corrupt==2)rb.used=2; + if(corrupt==3)rb.used=sizeof(espnow_packet_t)+19; + assert(safe_read()==-1 && !locked); + } + puts("PASS malformed_header_and_truncation_raise_after_unlock"); + reset_case(sizeof(storage));atomic_store(&producer_done,0);delivered=0; + pthread_t p,c;assert(pthread_create(&c,NULL,consumer,NULL)==0); + assert(pthread_create(&p,NULL,producer,NULL)==0); + assert(pthread_join(p,NULL)==0 && pthread_join(c,NULL)==0); + assert(delivered>0 && delivered==obj.read_success && delivered+obj.read_failure==20000 && rb.used==0); + printf("PASS concurrent_20000_packets_integrity_order_accounting delivered=%u dropped=%u\n",delivered,obj.read_failure); + return 0; +} +''' + + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument('circuitpython',type=Path) + parser.add_argument('--revision') + parser.add_argument('--expect-race',action='store_true') + parser.add_argument('--output',type=Path,required=True) + args=parser.parse_args();root=args.circuitpython.resolve() + files=['ports/espressif/common-hal/espnow/ESPNow.c','py/ringbuf.c','py/ringbuf.h'] + def read(p): + if args.revision: + return subprocess.check_output(['git','-C',str(root),'show',f'{args.revision}:{p}'],text=True) + return (root/p).read_text() + sources={p:read(p) for p in files};esp,ring,header=(sources[p] for p in files) + header=header.replace('#include "py/gc.h"','').replace('#pragma once','') + ring=ring.replace('#include "ringbuf.h"','') + types=esp[esp.index('typedef struct {'):esp.index('// --- The ESP-NOW send')] + available='common_hal_espnow_get_bytes_available' + getter=function(esp,available) if available in esp else f'size_t {available}(espnow_obj_t *self) {{return ringbuf_num_filled(self->recv_buffer);}}' + code=PREFIX+header+types+ring+MIDDLE+getter+function(esp,'common_hal_espnow_read')+function(esp,'recv_cb')+MAIN + code=f'#define EXPECT_RACE {int(args.expect_race)}\n'+code + with tempfile.TemporaryDirectory(prefix='espnow-rx-lock-') as temp: + src=Path(temp)/'check.c';exe=Path(temp)/'check';src.write_text(code) + subprocess.run(['cc','-std=c11','-O2','-Wall','-Wextra','-Werror','-Wno-unused-function','-pthread',str(src),'-o',str(exe)],check=True) + result=subprocess.run([str(exe)],capture_output=True,text=True,timeout=30) + data=dict(revision=args.revision or 'working-tree',source_sha256={p:hashlib.sha256(v.encode()).hexdigest() for p,v in sources.items()},generated_c_sha256=hashlib.sha256(code.encode()).hexdigest(),expected_race=args.expect_race,returncode=result.returncode,checks=result.stdout.splitlines(),stderr=result.stderr,scope='Actual packet/ring-buffer source with pthread critical sections, deterministic scheduling and concurrent integrity tests; not physical incidence data') + args.output.write_text(json.dumps(data,indent=2)+'\n');print(json.dumps(data,indent=2)) + raise SystemExit(result.returncode) + + +if __name__=='__main__':main()