From 73aa39f23c87a9061422b02bf7177d0829df67d9 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 11 Feb 2026 10:18:28 -0800 Subject: [PATCH 1/2] Add networking support to the Zephyr native_sim It uses the host's sockets directly to test our zsock integration. This doesn't test network management. It does also test the web workflow. --- .gitignore | 4 + locale/circuitpython.pot | 5 +- ports/zephyr-cp/Makefile | 13 +- ports/zephyr-cp/README.md | 14 + .../bindings/hostnetwork/HostNetwork.c | 37 ++ .../bindings/hostnetwork/HostNetwork.h | 19 + .../zephyr-cp/bindings/hostnetwork/__init__.c | 26 + .../zephyr-cp/bindings/hostnetwork/__init__.h | 11 + ports/zephyr-cp/boards/frdm_rw612.conf | 1 + .../native/native_sim/autogen_board_info.toml | 5 +- .../nrf5340bsim/autogen_board_info.toml | 1 + ports/zephyr-cp/boards/native_sim.conf | 16 + .../nordic/nrf5340dk/autogen_board_info.toml | 1 + .../nordic/nrf54h20dk/autogen_board_info.toml | 1 + .../nordic/nrf54l15dk/autogen_board_info.toml | 1 + .../nordic/nrf7002dk/autogen_board_info.toml | 3 +- .../nxp/frdm_mcxn947/autogen_board_info.toml | 1 + .../nxp/frdm_rw612/autogen_board_info.toml | 3 +- .../mimxrt1170_evk/autogen_board_info.toml | 1 + .../da14695_dk_usb/autogen_board_info.toml | 1 + .../renesas/ek_ra6m5/autogen_board_info.toml | 1 + .../renesas/ek_ra8d1/autogen_board_info.toml | 1 + .../nucleo_n657x0_q/autogen_board_info.toml | 1 + .../nucleo_u575zi_q/autogen_board_info.toml | 1 + .../st/stm32h7b3i_dk/autogen_board_info.toml | 1 + .../stm32wba65i_dk1/autogen_board_info.toml | 1 + .../common-hal/hostnetwork/HostNetwork.c | 15 + .../common-hal/hostnetwork/HostNetwork.h | 11 + .../zephyr-cp/common-hal/socketpool/Socket.c | 518 +++++++++--------- .../common-hal/socketpool/SocketPool.c | 178 +++--- .../common-hal/socketpool/SocketPool.h | 3 + .../zephyr-cp/cptools/build_circuitpython.py | 57 +- ports/zephyr-cp/cptools/zephyr2cp.py | 16 + ports/zephyr-cp/prj.conf | 16 +- ports/zephyr-cp/tests/conftest.py | 9 +- ports/zephyr-cp/tests/docs/web_workflow.md | 37 ++ ports/zephyr-cp/tests/test_web_workflow.py | 142 +++++ py/circuitpy_mpconfig.mk | 3 + shared-bindings/socketpool/SocketPool.c | 7 +- supervisor/shared/web_workflow/web_workflow.c | 137 +++-- supervisor/shared/web_workflow/web_workflow.h | 5 + supervisor/shared/workflow.c | 12 +- 42 files changed, 911 insertions(+), 425 deletions(-) create mode 100644 ports/zephyr-cp/bindings/hostnetwork/HostNetwork.c create mode 100644 ports/zephyr-cp/bindings/hostnetwork/HostNetwork.h create mode 100644 ports/zephyr-cp/bindings/hostnetwork/__init__.c create mode 100644 ports/zephyr-cp/bindings/hostnetwork/__init__.h create mode 100644 ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.c create mode 100644 ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.h create mode 100644 ports/zephyr-cp/tests/docs/web_workflow.md create mode 100644 ports/zephyr-cp/tests/test_web_workflow.py diff --git a/.gitignore b/.gitignore index 5b3b1db2d33be..ca6872387dd27 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,7 @@ TAGS # git-review-web outputs .review + +# Zephyr trace files +**/channel0_0 +**/*.perfetto-trace diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 48e0ab6fbfd85..e2eebdea0fe04 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2067,10 +2067,13 @@ msgstr "" #: ports/espressif/common-hal/socketpool/SocketPool.c #: ports/raspberrypi/common-hal/socketpool/SocketPool.c -#: ports/zephyr-cp/common-hal/socketpool/SocketPool.c msgid "SocketPool can only be used with wifi.radio" msgstr "" +#: ports/zephyr-cp/common-hal/socketpool/SocketPool.c +msgid "SocketPool can only be used with wifi.radio or hostnetwork.HostNetwork" +msgstr "" + #: shared-bindings/aesio/aes.c msgid "Source and destination buffers must be the same length" msgstr "" diff --git a/ports/zephyr-cp/Makefile b/ports/zephyr-cp/Makefile index c4feead23b9b5..5e320a328818c 100644 --- a/ports/zephyr-cp/Makefile +++ b/ports/zephyr-cp/Makefile @@ -8,8 +8,7 @@ BUILD ?= build-$(BOARD) TRANSLATION ?= en_US - -.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug run clean menuconfig all clean-all test fetch-port-submodules +.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug run run-sim clean menuconfig all clean-all test fetch-port-submodules $(BUILD)/zephyr-cp/zephyr/zephyr.elf: python cptools/pre_zephyr_build_prep.py $(BOARD) @@ -36,6 +35,16 @@ debug: $(BUILD)/zephyr-cp/zephyr/zephyr.elf run: $(BUILD)/firmware.exe $^ +run-sim: + $(MAKE) BOARD=native_native_sim BUILD=build-native_native_sim build-native_native_sim/firmware.exe + truncate -s 2M build-native_native_sim/flash.bin + mformat -i build-native_native_sim/flash.bin :: + @if [ -d CIRCUITPY ] && [ -n "$$(find CIRCUITPY -mindepth 1 -print -quit)" ]; then \ + echo "Populating build-native_native_sim/flash.bin from ./CIRCUITPY"; \ + mcopy -s -i build-native_native_sim/flash.bin CIRCUITPY/* ::; \ + fi + build-native_native_sim/firmware.exe --flash=build-native_native_sim/flash.bin --flash_rm -wait_uart -rt + menuconfig: west build --sysbuild -d $(BUILD) -t menuconfig diff --git a/ports/zephyr-cp/README.md b/ports/zephyr-cp/README.md index 1fba1d7d3f7f4..f4391fc4cb635 100644 --- a/ports/zephyr-cp/README.md +++ b/ports/zephyr-cp/README.md @@ -28,6 +28,20 @@ make BOARD=nordic_nrf7002dk This uses Zephyr's cmake to generate Makefiles that then delegate to `tools/cpbuild/build_circuitpython.py` to build the CircuitPython bits in parallel. +## Running the native simulator + +From `ports/zephyr-cp`, run: + +```sh +make run-sim +``` + +`run-sim` starts the native simulator in realtime. +It prints the PTY path to connect to the simulator REPL. +If a local `./CIRCUITPY/` folder exists, its files are used as the simulator's CIRCUITPY drive. + +Edit files in `./CIRCUITPY` (for example `code.py`) and rerun `make run-sim` to test changes. + ## Testing other boards [Any Zephyr board](https://docs.zephyrproject.org/latest/boards/index.html#) can diff --git a/ports/zephyr-cp/bindings/hostnetwork/HostNetwork.c b/ports/zephyr-cp/bindings/hostnetwork/HostNetwork.c new file mode 100644 index 0000000000000..fc0fb9ecc4f8f --- /dev/null +++ b/ports/zephyr-cp/bindings/hostnetwork/HostNetwork.c @@ -0,0 +1,37 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "bindings/hostnetwork/HostNetwork.h" + +#include "py/runtime.h" + +//| class HostNetwork: +//| """Native networking for the host simulator.""" +//| +//| def __init__(self) -> None: +//| """Create a HostNetwork instance.""" +//| ... +//| +static mp_obj_t hostnetwork_hostnetwork_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + hostnetwork_hostnetwork_obj_t *self = mp_obj_malloc(hostnetwork_hostnetwork_obj_t, &hostnetwork_hostnetwork_type); + common_hal_hostnetwork_hostnetwork_construct(self); + return MP_OBJ_FROM_PTR(self); +} + +static const mp_rom_map_elem_t hostnetwork_hostnetwork_locals_dict_table[] = { +}; +static MP_DEFINE_CONST_DICT(hostnetwork_hostnetwork_locals_dict, hostnetwork_hostnetwork_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + hostnetwork_hostnetwork_type, + MP_QSTR_HostNetwork, + MP_TYPE_FLAG_NONE, + make_new, hostnetwork_hostnetwork_make_new, + locals_dict, &hostnetwork_hostnetwork_locals_dict + ); diff --git a/ports/zephyr-cp/bindings/hostnetwork/HostNetwork.h b/ports/zephyr-cp/bindings/hostnetwork/HostNetwork.h new file mode 100644 index 0000000000000..009d8c0608743 --- /dev/null +++ b/ports/zephyr-cp/bindings/hostnetwork/HostNetwork.h @@ -0,0 +1,19 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; +} hostnetwork_hostnetwork_obj_t; + +extern const mp_obj_type_t hostnetwork_hostnetwork_type; + +void common_hal_hostnetwork_hostnetwork_construct(hostnetwork_hostnetwork_obj_t *self); diff --git a/ports/zephyr-cp/bindings/hostnetwork/__init__.c b/ports/zephyr-cp/bindings/hostnetwork/__init__.c new file mode 100644 index 0000000000000..92302b67d0e97 --- /dev/null +++ b/ports/zephyr-cp/bindings/hostnetwork/__init__.c @@ -0,0 +1,26 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "py/obj.h" + +#include "bindings/hostnetwork/__init__.h" +#include "bindings/hostnetwork/HostNetwork.h" + +//| """Host networking support for the native simulator.""" +//| + +static const mp_rom_map_elem_t hostnetwork_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_hostnetwork) }, + { MP_ROM_QSTR(MP_QSTR_HostNetwork), MP_ROM_PTR(&hostnetwork_hostnetwork_type) }, +}; +static MP_DEFINE_CONST_DICT(hostnetwork_module_globals, hostnetwork_module_globals_table); + +const mp_obj_module_t hostnetwork_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&hostnetwork_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_hostnetwork, hostnetwork_module); diff --git a/ports/zephyr-cp/bindings/hostnetwork/__init__.h b/ports/zephyr-cp/bindings/hostnetwork/__init__.h new file mode 100644 index 0000000000000..a6731546bdef1 --- /dev/null +++ b/ports/zephyr-cp/bindings/hostnetwork/__init__.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "bindings/hostnetwork/HostNetwork.h" + +extern hostnetwork_hostnetwork_obj_t common_hal_hostnetwork_obj; diff --git a/ports/zephyr-cp/boards/frdm_rw612.conf b/ports/zephyr-cp/boards/frdm_rw612.conf index 7f063218153e5..7d56dfed29cc9 100644 --- a/ports/zephyr-cp/boards/frdm_rw612.conf +++ b/ports/zephyr-cp/boards/frdm_rw612.conf @@ -20,6 +20,7 @@ CONFIG_MBEDTLS_PKCS1_V15=y CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_ENABLED=y CONFIG_MBEDTLS_ENTROPY_C=y CONFIG_MBEDTLS_CTR_DRBG_ENABLED=y +CONFIG_MBEDTLS_SHA1=y CONFIG_MBEDTLS_USE_PSA_CRYPTO=n CONFIG_BT=y diff --git a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml index 73897f7162082..3738e6494ec8b 100644 --- a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml @@ -49,7 +49,8 @@ frequencyio = false getpass = false gifio = false gnss = false -hashlib = false +hashlib = true # Zephyr networking enabled +hostnetwork = true # Zephyr board has hostnetwork i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false @@ -87,7 +88,7 @@ rtc = false sdcardio = true # Zephyr board has busio sdioio = false sharpdisplay = true # Zephyr board has busio -socketpool = false +socketpool = true # Zephyr networking enabled spitarget = false ssl = false storage = true # Zephyr board has flash diff --git a/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml index 9ddbb2153fb63..aa2deb2b41ce2 100644 --- a/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/native_sim.conf b/ports/zephyr-cp/boards/native_sim.conf index cc295949d0351..ddbfef11266d8 100644 --- a/ports/zephyr-cp/boards/native_sim.conf +++ b/ports/zephyr-cp/boards/native_sim.conf @@ -18,3 +18,19 @@ CONFIG_I2C_EMUL=y CONFIG_EEPROM=y CONFIG_EEPROM_AT24=y CONFIG_EEPROM_AT2X_EMUL=y + +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_ETH_NATIVE_TAP=n +CONFIG_NET_DRIVERS=y +CONFIG_NET_SOCKETS_OFFLOAD=y +CONFIG_NET_NATIVE_OFFLOADED_SOCKETS=y +CONFIG_HEAP_MEM_POOL_SIZE=1024 + +CONFIG_NET_LOG=y + +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_SHA1=y +CONFIG_MBEDTLS_SHA256=y diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index 52beeda076fc3..8490d71d8f6b5 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml index 2759dfb89c1c7..333733d6b307e 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index 6df832f607b2c..10d03bf857840 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index d713552d87be7..7d2bceb969112 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -49,7 +49,8 @@ frequencyio = false getpass = false gifio = false gnss = false -hashlib = false +hashlib = true # Zephyr networking enabled +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml index 233796fc6f443..37091a344cc0e 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml index f7ad0289cc75e..3030dff3a6ecf 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml @@ -49,7 +49,8 @@ frequencyio = false getpass = false gifio = false gnss = false -hashlib = false +hashlib = true # Zephyr networking enabled +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml index 4926b5c9a6cce..9a5c4c541447d 100644 --- a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml index 77d93aa10f095..883bf6be4665e 100644 --- a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index 2ea2cea90b775..b2097d011fc5e 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 3be48a8b72c3f..1686169fffbce 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml index 4b9c1053f2289..2e20a0db1b876 100644 --- a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index ec78a62f066e2..a542e756c62e3 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index 24e44662e4016..0088d60f018e9 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml index e26084cc43ee3..f030ee7319ec4 100644 --- a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml @@ -50,6 +50,7 @@ getpass = false gifio = false gnss = false hashlib = false +hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false diff --git a/ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.c b/ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.c new file mode 100644 index 0000000000000..494b21cb02db9 --- /dev/null +++ b/ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.c @@ -0,0 +1,15 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "bindings/hostnetwork/HostNetwork.h" + +hostnetwork_hostnetwork_obj_t common_hal_hostnetwork_obj = { + .base = { &hostnetwork_hostnetwork_type }, +}; + +void common_hal_hostnetwork_hostnetwork_construct(hostnetwork_hostnetwork_obj_t *self) { + (void)self; +} diff --git a/ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.h b/ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.h new file mode 100644 index 0000000000000..a6731546bdef1 --- /dev/null +++ b/ports/zephyr-cp/common-hal/hostnetwork/HostNetwork.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "bindings/hostnetwork/HostNetwork.h" + +extern hostnetwork_hostnetwork_obj_t common_hal_hostnetwork_obj; diff --git a/ports/zephyr-cp/common-hal/socketpool/Socket.c b/ports/zephyr-cp/common-hal/socketpool/Socket.c index 857526a12debd..ca8ba4198394a 100644 --- a/ports/zephyr-cp/common-hal/socketpool/Socket.c +++ b/ports/zephyr-cp/common-hal/socketpool/Socket.c @@ -20,165 +20,104 @@ #include "supervisor/shared/tick.h" #include "supervisor/workflow.h" -#include - -// void socketpool_resolve_host_or_throw(int family, int type, const char *hostname, struct sockaddr_storage *addr, int port) { -// // struct addrinfo *result_i; -// // const struct addrinfo hints = { -// // .ai_family = family, -// // .ai_socktype = type, -// // }; -// // int error = socketpool_getaddrinfo_common(hostname, port, &hints, &result_i); -// if (true) { -// common_hal_socketpool_socketpool_raise_gaierror_noname(); -// } -// // memcpy(addr, result_i->ai_addr, sizeof(struct sockaddr_storage)); -// // lwip_freeaddrinfo(result_i); -// } - -// static void resolve_host_or_throw(socketpool_socket_obj_t *self, const char *hostname, struct sockaddr_storage *addr, int port) { -// socketpool_resolve_host_or_throw(self->family, self->type, hostname, addr, port); -// } - -// StackType_t socket_select_stack[2 * configMINIMAL_STACK_SIZE]; - -// /* Socket state table: -// * 0 := Closed (unused) -// * 1 := Open -// * 2 := Closing (remove from rfds) -// * Index into socket_fd_state is calculated from actual lwip fd. idx := fd - LWIP_SOCKET_OFFSET -// */ -// #define FDSTATE_CLOSED 0 -// #define FDSTATE_OPEN 1 -// #define FDSTATE_CLOSING 2 -// static uint8_t socket_fd_state[CONFIG_LWIP_MAX_SOCKETS]; +#include +#include +#include +#include +#include -// How long to wait between checks for a socket to connect. -#define SOCKET_CONNECT_POLL_INTERVAL_MS 100 +#include +#include -// static socketpool_socket_obj_t *user_socket[CONFIG_LWIP_MAX_SOCKETS]; -// StaticTask_t socket_select_task_buffer; -// TaskHandle_t socket_select_task_handle; -// static int socket_change_fd = -1; - -// static void socket_select_task(void *arg) { -// uint64_t signal; -// fd_set readfds; -// fd_set excptfds; - -// while (true) { -// FD_ZERO(&readfds); -// FD_ZERO(&excptfds); -// FD_SET(socket_change_fd, &readfds); -// int max_fd = socket_change_fd; -// for (size_t i = 0; i < MP_ARRAY_SIZE(socket_fd_state); i++) { -// if ((socket_fd_state[i] == FDSTATE_OPEN) && (user_socket[i] == NULL)) { -// int sockfd = i + LWIP_SOCKET_OFFSET; -// max_fd = MAX(max_fd, sockfd); -// FD_SET(sockfd, &readfds); -// FD_SET(sockfd, &excptfds); -// } -// } - -// int num_triggered = select(max_fd + 1, &readfds, NULL, &excptfds, NULL); -// // Hard error (or someone closed a socket on another thread) -// if (num_triggered == -1) { -// assert(errno == EBADF); -// continue; -// } - -// assert(num_triggered > 0); - -// // Notice event trigger -// if (FD_ISSET(socket_change_fd, &readfds)) { -// read(socket_change_fd, &signal, sizeof(signal)); -// num_triggered--; -// } - -// // Handle active FDs, close the dead ones -// for (size_t i = 0; i < MP_ARRAY_SIZE(socket_fd_state); i++) { -// int sockfd = i + LWIP_SOCKET_OFFSET; -// if (socket_fd_state[i] != FDSTATE_CLOSED) { -// if (FD_ISSET(sockfd, &readfds) || FD_ISSET(sockfd, &excptfds)) { -// if (socket_fd_state[i] == FDSTATE_CLOSING) { -// socket_fd_state[i] = FDSTATE_CLOSED; -// num_triggered--; -// } -// } -// } -// } - -// if (num_triggered > 0) { -// // Wake up CircuitPython by queuing request -// supervisor_workflow_request_background(); -// ulTaskNotifyTake(pdTRUE, portMAX_DELAY); -// } -// } - -// close(socket_change_fd); -// socket_change_fd = -1; -// vTaskDelete(NULL); -// } +#define SOCKETPOOL_IP_STR_LEN 48 -void socket_user_reset(void) { - // if (socket_change_fd < 0) { - // esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); - // ESP_ERROR_CHECK(esp_vfs_eventfd_register(&config)); - - // // Clear initial socket states - // for (size_t i = 0; i < MP_ARRAY_SIZE(socket_fd_state); i++) { - // socket_fd_state[i] = FDSTATE_CLOSED; - // user_socket[i] = NULL; - // } - // socket_change_fd = eventfd(0, 0); - // // Run this at the same priority as CP so that the web workflow background task can be - // // queued while CP is running. Both tasks can still sleep and, therefore, sleep overall. - // socket_select_task_handle = xTaskCreateStaticPinnedToCore(socket_select_task, - // "socket_select", - // 2 * configMINIMAL_STACK_SIZE, - // NULL, - // uxTaskPriorityGet(NULL), - // socket_select_stack, - // &socket_select_task_buffer, - // xPortGetCoreID()); - // } else { - // // Not init - close open user sockets - // for (size_t i = 0; i < MP_ARRAY_SIZE(socket_fd_state); i++) { - // if ((socket_fd_state[i] == FDSTATE_OPEN) && user_socket[i]) { - // common_hal_socketpool_socket_close(user_socket[i]); - // } - // } - // } +static mp_obj_t _format_address(const struct sockaddr *addr, int family) { + char ip_str[SOCKETPOOL_IP_STR_LEN]; + const struct sockaddr_in *a = (void *)addr; + + switch (family) { + #if CIRCUITPY_SOCKETPOOL_IPV6 + case AF_INET6: + zsock_inet_ntop(family, &((const struct sockaddr_in6 *)a)->sin6_addr, ip_str, sizeof(ip_str)); + break; + #endif + default: + case AF_INET: + zsock_inet_ntop(family, &((const struct sockaddr_in *)a)->sin_addr, ip_str, sizeof(ip_str)); + break; + } + return mp_obj_new_str(ip_str, strlen(ip_str)); } -// Unblock select task (ok if not blocked yet) -void socketpool_socket_poll_resume(void) { - // if (socket_select_task_handle) { - // xTaskNotifyGive(socket_select_task_handle); - // } +static mp_obj_t _sockaddr_to_tuple(const struct sockaddr_storage *sockaddr) { + mp_obj_t args[4] = { + _format_address((const struct sockaddr *)sockaddr, sockaddr->ss_family), + }; + int n = 2; + #if CIRCUITPY_SOCKETPOOL_IPV6 + if (sockaddr->ss_family == AF_INET6) { + const struct sockaddr_in6 *addr6 = (const void *)sockaddr; + args[1] = MP_OBJ_NEW_SMALL_INT(ntohs(addr6->sin6_port)); + args[2] = MP_OBJ_NEW_SMALL_INT(addr6->sin6_flowinfo); + args[3] = MP_OBJ_NEW_SMALL_INT(addr6->sin6_scope_id); + n = 4; + } else + #endif + { + const struct sockaddr_in *addr = (const void *)sockaddr; + args[1] = MP_OBJ_NEW_SMALL_INT(ntohs(addr->sin_port)); + } + return mp_obj_new_tuple(n, args); +} + +static void socketpool_resolve_host_or_throw(int family, int type, const char *hostname, struct sockaddr_storage *addr, int port) { + const struct zsock_addrinfo hints = { + .ai_family = family, + .ai_socktype = type, + }; + struct zsock_addrinfo *result_i = NULL; + char service_buf[6]; + + snprintf(service_buf, sizeof(service_buf), "%d", port); + + int error = zsock_getaddrinfo(hostname, service_buf, &hints, &result_i); + if (error != 0 || result_i == NULL) { + common_hal_socketpool_socketpool_raise_gaierror_noname(); + } + + memcpy(addr, result_i->ai_addr, sizeof(struct sockaddr_storage)); + zsock_freeaddrinfo(result_i); } -// The writes below send an event to the socket select task so that it redoes the -// select with the new open socket set. +static void resolve_host_or_throw(socketpool_socket_obj_t *self, const char *hostname, struct sockaddr_storage *addr, int port) { + socketpool_resolve_host_or_throw(self->family, self->type, hostname, addr, port); +} -// static bool register_open_socket(int fd) { -// if (fd < FD_SETSIZE) { -// socket_fd_state[fd - LWIP_SOCKET_OFFSET] = FDSTATE_OPEN; -// user_socket[fd - LWIP_SOCKET_OFFSET] = NULL; +// How long to wait between checks for a socket to connect. +#define SOCKET_CONNECT_POLL_INTERVAL_MS 100 -// uint64_t signal = 1; -// write(socket_change_fd, &signal, sizeof(signal)); -// socketpool_socket_poll_resume(); -// return true; -// } -// return false; -// } +void socket_user_reset(void) { + // User sockets are heap objects with __del__ bound to close(). + // During VM shutdown/reset, gc_sweep_all() runs finalizers, so sockets + // are closed there rather than being tracked and closed explicitly here. +} + +static struct k_work_delayable socketpool_poll_work; +static bool socketpool_poll_work_initialized; -// static void mark_user_socket(int fd, socketpool_socket_obj_t *obj) { -// socket_fd_state[fd - LWIP_SOCKET_OFFSET] = FDSTATE_OPEN; -// user_socket[fd - LWIP_SOCKET_OFFSET] = obj; -// // No need to wakeup select task -// } +static void socketpool_poll_work_handler(struct k_work *work) { + (void)work; + supervisor_workflow_request_background(); +} + +// Unblock select task (ok if not blocked yet) +void socketpool_socket_poll_resume(void) { + if (!socketpool_poll_work_initialized) { + k_work_init_delayable(&socketpool_poll_work, socketpool_poll_work_handler); + socketpool_poll_work_initialized = true; + } + k_work_schedule(&socketpool_poll_work, K_MSEC(10)); +} static bool _socketpool_socket(socketpool_socketpool_obj_t *self, socketpool_socketpool_addressfamily_t family, socketpool_socketpool_sock_t type, @@ -212,16 +151,23 @@ static bool _socketpool_socket(socketpool_socketpool_obj_t *self, sock->pool = self; sock->timeout_ms = (uint)-1; - // Create LWIP socket - // int socknum = -1; - // socknum = lwip_socket(sock->family, sock->type, sock->ipproto); - // if (socknum < 0) { - // return false; - // } + int socknum = zsock_socket(sock->family, sock->type, sock->ipproto); + if (socknum < 0) { + return false; + } - // sock->num = socknum; - // // Sockets should be nonblocking in most cases - // lwip_fcntl(socknum, F_SETFL, O_NONBLOCK); + sock->num = socknum; + + // Enable address reuse by default to avoid bind failures on recently-used ports. + int reuseaddr = 1; + if (zsock_setsockopt(socknum, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) { + // Ignore if SO_REUSEADDR is unsupported. + } + + // Sockets should be nonblocking in most cases. + if (zsock_fcntl(socknum, F_SETFL, O_NONBLOCK) < 0) { + // Ignore if non-blocking is unsupported. + } return true; } @@ -235,11 +181,6 @@ bool socketpool_socket(socketpool_socketpool_obj_t *self, return false; } - // This shouldn't happen since we have room for the same number of sockets as LWIP. - // if (!register_open_socket(sock->num)) { - // lwip_close(sock->num); - // return false; - // } return true; } @@ -260,13 +201,12 @@ socketpool_socket_obj_t *common_hal_socketpool_socket(socketpool_socketpool_obj_ if (!_socketpool_socket(self, family, type, proto, sock)) { mp_raise_RuntimeError(MP_ERROR_TEXT("Out of sockets")); } - // mark_user_socket(sock->num, sock); return sock; } int socketpool_socket_accept(socketpool_socket_obj_t *self, mp_obj_t *peer_out, socketpool_socket_obj_t *accepted) { struct sockaddr_storage peer_addr; - // socklen_t socklen = sizeof(peer_addr); + socklen_t socklen = sizeof(peer_addr); int newsoc = -1; bool timed_out = false; uint64_t start_ticks = supervisor_ticks_ms64(); @@ -277,11 +217,23 @@ int socketpool_socket_accept(socketpool_socket_obj_t *self, mp_obj_t *peer_out, timed_out = supervisor_ticks_ms64() - start_ticks >= self->timeout_ms; } RUN_BACKGROUND_TASKS; - // newsoc = lwip_accept(self->num, (struct sockaddr *)&peer_addr, &socklen); + #if CIRCUITPY_HOSTNETWORK + if (self->timeout_ms == 0) { + struct zsock_timeval tv = { + .tv_sec = 0, + .tv_usec = 1000, + }; + zsock_setsockopt(self->num, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + } + #endif + newsoc = zsock_accept(self->num, (struct sockaddr *)&peer_addr, &socklen); // In non-blocking mode, fail instead of timing out if (newsoc == -1 && (self->timeout_ms == 0 || mp_hal_is_interrupted())) { return -MP_EAGAIN; } + if (newsoc == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { + return -errno; + } } if (timed_out) { @@ -293,18 +245,14 @@ int socketpool_socket_accept(socketpool_socket_obj_t *self, mp_obj_t *peer_out, } // We got a socket. New client socket will not be non-blocking by default, so make it non-blocking. - // lwip_fcntl(newsoc, F_SETFL, O_NONBLOCK); + if (zsock_fcntl(newsoc, F_SETFL, O_NONBLOCK) < 0) { + // Ignore if non-blocking is unsupported. + } if (accepted != NULL) { // Error if called with open socket object. assert(common_hal_socketpool_socket_get_closed(accepted)); - // Register if system socket - // if (!register_open_socket(newsoc)) { - // lwip_close(newsoc); - // return -MP_EBADF; - // } - // Replace the old accepted socket with the new one. accepted->num = newsoc; accepted->pool = self->pool; @@ -313,7 +261,7 @@ int socketpool_socket_accept(socketpool_socket_obj_t *self, mp_obj_t *peer_out, } if (peer_out) { - *peer_out = sockaddr_to_tuple(&peer_addr); + *peer_out = _sockaddr_to_tuple(&peer_addr); } return newsoc; @@ -327,7 +275,6 @@ socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_o if (newsoc > 0) { // Create the socket - // mark_user_socket(newsoc, sock); sock->base.type = &socketpool_socket_type; sock->num = newsoc; sock->pool = self->pool; @@ -343,40 +290,42 @@ socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_o size_t common_hal_socketpool_socket_bind(socketpool_socket_obj_t *self, const char *host, size_t hostlen, uint32_t port) { - // struct sockaddr_storage bind_addr; + struct sockaddr_storage bind_addr; const char *broadcast = ""; + uint32_t local_port = port; - // bind_addr.ss_family = self->family; + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.ss_family = self->family; #if CIRCUITPY_SOCKETPOOL_IPV6 if (self->family == AF_INET6) { struct sockaddr_in6 *addr6 = (void *)&bind_addr; - addr6->sin6_port = htons(port); + addr6->sin6_port = htons(local_port); // no ipv6 broadcast if (hostlen == 0) { memset(&addr6->sin6_addr, 0, sizeof(addr6->sin6_addr)); } else { - socketpool_resolve_host_or_throw(self->family, self->type, host, &bind_addr, port); + socketpool_resolve_host_or_throw(self->family, self->type, host, &bind_addr, local_port); } } else #endif { - // struct sockaddr_in *addr4 = (void *)&bind_addr; - // addr4->sin_port = htons(port); + struct sockaddr_in *addr4 = (void *)&bind_addr; + addr4->sin_port = htons(local_port); if (hostlen == 0) { - // addr4->sin_addr.s_addr = IPADDR_ANY; + addr4->sin_addr.s_addr = htonl(INADDR_ANY); } else if (hostlen == strlen(broadcast) && memcmp(host, broadcast, strlen(broadcast)) == 0) { - // addr4->sin_addr.s_addr = IPADDR_BROADCAST; + addr4->sin_addr.s_addr = htonl(INADDR_BROADCAST); } else { - // socketpool_resolve_host_or_throw(self->family, self->type, host, &bind_addr, port); + socketpool_resolve_host_or_throw(self->family, self->type, host, &bind_addr, local_port); } } - // int result = lwip_bind(self->num, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); - // if (result == 0) { - // return 0; - // } + int result = zsock_bind(self->num, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); + if (result == 0) { + return 0; + } return errno; } @@ -390,20 +339,11 @@ void socketpool_socket_close(socketpool_socket_obj_t *self) { } #endif self->connected = false; - // int fd = self->num; - // Ignore bogus/closed sockets - // if (fd >= LWIP_SOCKET_OFFSET) { - // if (user_socket[fd - LWIP_SOCKET_OFFSET] == NULL) { - // socket_fd_state[fd - LWIP_SOCKET_OFFSET] = FDSTATE_CLOSING; - // lwip_shutdown(fd, SHUT_RDWR); - // lwip_close(fd); - // } else { - // lwip_shutdown(fd, SHUT_RDWR); - // lwip_close(fd); - // socket_fd_state[fd - LWIP_SOCKET_OFFSET] = FDSTATE_CLOSED; - // user_socket[fd - LWIP_SOCKET_OFFSET] = NULL; - // } - // } + int fd = self->num; + if (fd >= 0) { + zsock_shutdown(fd, ZSOCK_SHUT_RDWR); + zsock_close(fd); + } self->num = -1; } @@ -413,16 +353,11 @@ void common_hal_socketpool_socket_close(socketpool_socket_obj_t *self) { void common_hal_socketpool_socket_connect(socketpool_socket_obj_t *self, const char *host, size_t hostlen, uint32_t port) { - // struct sockaddr_storage addr; - // resolve_host_or_throw(self, host, &addr, port); - - // Replace above with function call ----- - - // Emulate SO_CONTIMEO, which is not implemented by lwip. - // All our sockets are non-blocking, so we check the timeout ourselves. + (void)hostlen; + struct sockaddr_storage addr; + resolve_host_or_throw(self, host, &addr, port); - int result = -1; - // result = lwip_connect(self->num, (struct sockaddr *)&addr, addr.s2_len); + int result = zsock_connect(self->num, (struct sockaddr *)&addr, sizeof(addr)); if (result == 0) { // Connected immediately. @@ -436,12 +371,7 @@ void common_hal_socketpool_socket_connect(socketpool_socket_obj_t *self, return; } - // struct timeval timeout = { - // .tv_sec = 0, - // .tv_usec = SOCKET_CONNECT_POLL_INTERVAL_MS * 1000, - // }; - - // Keep checking, using select(), until timeout expires, at short intervals. + // Keep checking, using poll(), until timeout expires, at short intervals. // This allows ctrl-C interrupts to be detected and background tasks to run. mp_uint_t timeout_left = self->timeout_ms; @@ -452,14 +382,22 @@ void common_hal_socketpool_socket_connect(socketpool_socket_obj_t *self, return; } - // fd_set fds; - // FD_ZERO(&fds); - // FD_SET(self->num, &fds); + struct zsock_pollfd fd = { + .fd = self->num, + .events = ZSOCK_POLLOUT, + }; + int poll_timeout = SOCKET_CONNECT_POLL_INTERVAL_MS; + if (self->timeout_ms == (uint)-1) { + poll_timeout = -1; + } else if (timeout_left < SOCKET_CONNECT_POLL_INTERVAL_MS) { + poll_timeout = timeout_left; + } - // result = select(self->num + 1, NULL, &fds, NULL, &timeout); + result = zsock_poll(&fd, 1, poll_timeout); if (result == 0) { - // No change to fd's after waiting for timeout, so try again if some time is still left. - // Don't wrap below 0, because we're using a uint. + if (self->timeout_ms == (uint)-1) { + continue; + } if (timeout_left < SOCKET_CONNECT_POLL_INTERVAL_MS) { timeout_left = 0; } else { @@ -469,16 +407,14 @@ void common_hal_socketpool_socket_connect(socketpool_socket_obj_t *self, } if (result < 0) { - // Some error happened when doing select(); error is in errno. mp_raise_OSError(errno); } - // select() indicated the socket is writable. Check if any connection error occurred. int error_code = 0; - // socklen_t socklen = sizeof(error_code); - // result = getsockopt(self->num, SOL_SOCKET, SO_ERROR, &error_code, &socklen); + socklen_t socklen = sizeof(error_code); + result = zsock_getsockopt(self->num, SOL_SOCKET, SO_ERROR, &error_code, &socklen); if (result < 0 || error_code != 0) { - mp_raise_OSError(errno); + mp_raise_OSError(error_code != 0 ? error_code : errno); } self->connected = true; return; @@ -498,17 +434,15 @@ bool common_hal_socketpool_socket_get_connected(socketpool_socket_obj_t *self) { } bool common_hal_socketpool_socket_listen(socketpool_socket_obj_t *self, int backlog) { - // return lwip_listen(self->num, backlog) == 0; - return false; + return zsock_listen(self->num, backlog) == 0; } mp_uint_t common_hal_socketpool_socket_recvfrom_into(socketpool_socket_obj_t *self, uint8_t *buf, uint32_t len, mp_obj_t *source_out) { - // struct sockaddr_storage source_addr; - // socklen_t socklen = sizeof(source_addr); + struct sockaddr_storage source_addr; + socklen_t socklen = sizeof(source_addr); - // LWIP Socket uint64_t start_ticks = supervisor_ticks_ms64(); int received = -1; bool timed_out = false; @@ -519,11 +453,18 @@ mp_uint_t common_hal_socketpool_socket_recvfrom_into(socketpool_socket_obj_t *se timed_out = supervisor_ticks_ms64() - start_ticks >= self->timeout_ms; } RUN_BACKGROUND_TASKS; - // received = lwip_recvfrom(self->num, buf, len, 0, (struct sockaddr *)&source_addr, &socklen); + received = zsock_recvfrom(self->num, buf, len, ZSOCK_MSG_DONTWAIT, (struct sockaddr *)&source_addr, &socklen); - // In non-blocking mode, fail instead of looping - if (received == -1 && self->timeout_ms == 0) { - mp_raise_OSError(MP_EAGAIN); + if (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + mp_raise_OSError(errno); + } + + if (received == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + // In non-blocking mode, fail instead of looping + if (self->timeout_ms == 0) { + mp_raise_OSError(MP_EAGAIN); + } + continue; } } @@ -537,7 +478,7 @@ mp_uint_t common_hal_socketpool_socket_recvfrom_into(socketpool_socket_obj_t *se } if (source_out) { - // *source_out = sockaddr_to_tuple(&source_addr); + *source_out = _sockaddr_to_tuple(&source_addr); } return received; @@ -549,7 +490,6 @@ int socketpool_socket_recv_into(socketpool_socket_obj_t *self, bool timed_out = false; if (self->num != -1) { - // LWIP Socket uint64_t start_ticks = supervisor_ticks_ms64(); received = -1; while (received == -1 && @@ -558,7 +498,16 @@ int socketpool_socket_recv_into(socketpool_socket_obj_t *self, timed_out = supervisor_ticks_ms64() - start_ticks >= self->timeout_ms; } RUN_BACKGROUND_TASKS; - // received = lwip_recv(self->num, (void *)buf, len, 0); + received = zsock_recv(self->num, (void *)buf, len, ZSOCK_MSG_DONTWAIT); + if (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + return -errno; + } + if (received < 1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + if (self->timeout_ms == 0) { + return -MP_EAGAIN; + } + continue; + } // In non-blocking mode, fail instead of looping if (received < 1 && self->timeout_ms == 0) { if ((received == 0) || (errno == ENOTCONN)) { @@ -597,9 +546,7 @@ mp_uint_t common_hal_socketpool_socket_recv_into(socketpool_socket_obj_t *self, int socketpool_socket_send(socketpool_socket_obj_t *self, const uint8_t *buf, uint32_t len) { int sent = -1; if (self->num != -1) { - // LWIP Socket - // TODO: deal with potential failure/add timeout? - // sent = lwip_send(self->num, buf, len, 0); + sent = zsock_send(self->num, buf, len, 0); } else { sent = -MP_EBADF; } @@ -626,15 +573,15 @@ mp_uint_t common_hal_socketpool_socket_send(socketpool_socket_obj_t *self, const mp_uint_t common_hal_socketpool_socket_sendto(socketpool_socket_obj_t *self, const char *host, size_t hostlen, uint32_t port, const uint8_t *buf, uint32_t len) { - // struct sockaddr_storage addr; - // resolve_host_or_throw(self, host, &addr, port); + (void)hostlen; + struct sockaddr_storage addr; + resolve_host_or_throw(self, host, &addr, port); - // int bytes_sent = lwip_sendto(self->num, buf, len, 0, (struct sockaddr *)&addr, addr.s2_len); - // if (bytes_sent < 0) { - // mp_raise_BrokenPipeError(); - // return 0; - // } - int bytes_sent = 0; + int bytes_sent = zsock_sendto(self->num, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)); + if (bytes_sent < 0) { + mp_raise_BrokenPipeError(); + return 0; + } return bytes_sent; } @@ -648,7 +595,44 @@ mp_int_t common_hal_socketpool_socket_get_type(socketpool_socket_obj_t *self) { int common_hal_socketpool_socket_setsockopt(socketpool_socket_obj_t *self, int level, int optname, const void *value, size_t optlen) { - int err = 0; // lwip_setsockopt(self->num, level, optname, value, optlen); + int zephyr_level = level; + int zephyr_optname = optname; + + switch (level) { + case SOCKETPOOL_SOL_SOCKET: + zephyr_level = SOL_SOCKET; + break; + case SOCKETPOOL_IPPROTO_IP: + zephyr_level = IPPROTO_IP; + break; + case SOCKETPOOL_IPPROTO_TCP: + zephyr_level = IPPROTO_TCP; + break; + case SOCKETPOOL_IPPROTO_UDP: + zephyr_level = IPPROTO_UDP; + break; + #if CIRCUITPY_SOCKETPOOL_IPV6 + case SOCKETPOOL_IPPROTO_IPV6: + zephyr_level = IPPROTO_IPV6; + break; + #endif + } + + if (zephyr_level == SOL_SOCKET) { + switch (optname) { + case SOCKETPOOL_SO_REUSEADDR: + zephyr_optname = SO_REUSEADDR; + break; + } + } else if (zephyr_level == IPPROTO_TCP) { + switch (optname) { + case SOCKETPOOL_TCP_NODELAY: + zephyr_optname = TCP_NODELAY; + break; + } + } + + int err = zsock_setsockopt(self->num, zephyr_level, zephyr_optname, value, optlen); if (err != 0) { return -errno; } @@ -656,29 +640,23 @@ int common_hal_socketpool_socket_setsockopt(socketpool_socket_obj_t *self, int l } bool common_hal_socketpool_readable(socketpool_socket_obj_t *self) { - // struct timeval immediate = {0, 0}; + struct zsock_pollfd fd = { + .fd = self->num, + .events = ZSOCK_POLLIN, + }; - // fd_set fds; - // FD_ZERO(&fds); - // FD_SET(self->num, &fds); - // int num_triggered = select(self->num + 1, &fds, NULL, &fds, &immediate); - - // including returning true in the error case - // return num_triggered != 0; - return false; + int num_triggered = zsock_poll(&fd, 1, 0); + return num_triggered > 0; } bool common_hal_socketpool_writable(socketpool_socket_obj_t *self) { - // struct timeval immediate = {0, 0}; - - // fd_set fds; - // FD_ZERO(&fds); - // FD_SET(self->num, &fds); - // int num_triggered = select(self->num + 1, NULL, &fds, &fds, &immediate); + struct zsock_pollfd fd = { + .fd = self->num, + .events = ZSOCK_POLLOUT, + }; - // including returning true in the error case - // return num_triggered != 0; - return false; + int num_triggered = zsock_poll(&fd, 1, 0); + return num_triggered > 0; } void socketpool_socket_move(socketpool_socket_obj_t *self, socketpool_socket_obj_t *sock) { diff --git a/ports/zephyr-cp/common-hal/socketpool/SocketPool.c b/ports/zephyr-cp/common-hal/socketpool/SocketPool.c index 4531c5bf1b7fa..c9a3f008321a1 100644 --- a/ports/zephyr-cp/common-hal/socketpool/SocketPool.c +++ b/ports/zephyr-cp/common-hal/socketpool/SocketPool.c @@ -8,114 +8,116 @@ #include "common-hal/socketpool/Socket.h" #include "py/runtime.h" +#if CIRCUITPY_HOSTNETWORK +#include "bindings/hostnetwork/__init__.h" +#endif +#if CIRCUITPY_WIFI #include "shared-bindings/wifi/__init__.h" +#endif #include "common-hal/socketpool/__init__.h" +#include +#include + +#include + void common_hal_socketpool_socketpool_construct(socketpool_socketpool_obj_t *self, mp_obj_t radio) { - if (radio != MP_OBJ_FROM_PTR(&common_hal_wifi_radio_obj)) { - mp_raise_ValueError(MP_ERROR_TEXT("SocketPool can only be used with wifi.radio")); + bool is_wifi = false; + #if CIRCUITPY_WIFI + is_wifi = radio == MP_OBJ_FROM_PTR(&common_hal_wifi_radio_obj); + #endif + bool is_hostnetwork = false; + #if CIRCUITPY_HOSTNETWORK + is_hostnetwork = mp_obj_is_type(radio, &hostnetwork_hostnetwork_type); + #endif + if (!(is_wifi || is_hostnetwork)) { + mp_raise_ValueError(MP_ERROR_TEXT("SocketPool can only be used with wifi.radio or hostnetwork.HostNetwork")); } } // common_hal_socketpool_socket is in socketpool/Socket.c to centralize open socket tracking. -// int socketpool_getaddrinfo_common(const char *host, int service, const struct addrinfo *hints, struct addrinfo **res) { -// // As of 2022, the version of lwip in esp-idf does not handle the -// // trailing-dot syntax of domain names, so emulate it. -// // Remove this once https://github.com/espressif/esp-idf/issues/10013 has -// // been implemented -// if (host) { -// size_t strlen_host = strlen(host); -// if (strlen_host && host[strlen_host - 1] == '.') { -// mp_obj_t nodot = mp_obj_new_str(host, strlen_host - 1); -// host = mp_obj_str_get_str(nodot); -// } -// } - -// // char service_buf[6]; -// // snprintf(service_buf, sizeof(service_buf), "%d", service); - -// // return lwip_getaddrinfo(host, service_buf, hints, res); -// return 0; -// } - -// static mp_obj_t format_address(const struct sockaddr *addr, int family) { -// char ip_str[IPADDR_STRLEN_MAX]; // big enough for any supported address type -// const struct sockaddr_in *a = (void *)addr; - -// switch (family) { -// #if CIRCUITPY_SOCKETPOOL_IPV6 -// case AF_INET6: -// inet_ntop(family, &((const struct sockaddr_in6 *)a)->sin6_addr, ip_str, sizeof(ip_str)); -// break; -// #endif -// default: -// case AF_INET: -// inet_ntop(family, &((const struct sockaddr_in *)a)->sin_addr, ip_str, sizeof(ip_str)); -// break; -// } -// return mp_obj_new_str(ip_str, strlen(ip_str)); -// } - -// static mp_obj_t convert_sockaddr(const struct addrinfo *ai, int port) { -// #if CIRCUITPY_SOCKETPOOL_IPV6 -// mp_int_t n_tuple = ai->ai_family == AF_INET6 ? 4 : 2; -// #else -// mp_int_t n_tuple = 2; -// #endif -// mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(n_tuple, NULL)); -// result->items[0] = format_address(ai->ai_addr, ai->ai_family); -// result->items[1] = MP_OBJ_NEW_SMALL_INT(port); -// #if CIRCUITPY_SOCKETPOOL_IPV6 -// if (ai->ai_family == AF_INET6) { -// const struct sockaddr_in6 *ai6 = (void *)ai->ai_addr; -// result->items[2] = MP_OBJ_NEW_SMALL_INT(ai6->sin6_flowinfo); -// result->items[3] = MP_OBJ_NEW_SMALL_INT(ai6->sin6_scope_id); -// } -// #endif -// return result; -// } - -// static mp_obj_t convert_addrinfo(const struct addrinfo *ai, int port) { -// MP_STATIC_ASSERT(AF_INET == SOCKETPOOL_AF_INET); -// #if CIRCUITPY_SOCKETPOOL_IPV6 -// MP_STATIC_ASSERT(AF_INET6 == SOCKETPOOL_AF_INET6); -// #endif -// // MP_STATIC_ASSERT(AF_UNSPEC == SOCKETPOOL_AF_UNSPEC); -// mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL)); -// result->items[0] = MP_OBJ_NEW_SMALL_INT(ai->ai_family); -// result->items[1] = MP_OBJ_NEW_SMALL_INT(ai->ai_socktype); -// result->items[2] = MP_OBJ_NEW_SMALL_INT(ai->ai_protocol); -// result->items[3] = ai->ai_canonname ? mp_obj_new_str(ai->ai_canonname, strlen(ai->ai_canonname)) : MP_OBJ_NEW_QSTR(MP_QSTR_); -// result->items[4] = convert_sockaddr(ai, port); -// return result; -// } +static int socketpool_getaddrinfo_common(const char *host, int service, const struct zsock_addrinfo *hints, struct zsock_addrinfo **res) { + char service_buf[6]; + snprintf(service_buf, sizeof(service_buf), "%d", service); + + return zsock_getaddrinfo(host, service_buf, hints, res); +} + +#define SOCKETPOOL_IP_STR_LEN 48 + +static mp_obj_t format_address(const struct sockaddr *addr, int family) { + char ip_str[SOCKETPOOL_IP_STR_LEN]; + const struct sockaddr_in *a = (void *)addr; + + switch (family) { + #if CIRCUITPY_SOCKETPOOL_IPV6 + case AF_INET6: + zsock_inet_ntop(family, &((const struct sockaddr_in6 *)a)->sin6_addr, ip_str, sizeof(ip_str)); + break; + #endif + default: + case AF_INET: + zsock_inet_ntop(family, &((const struct sockaddr_in *)a)->sin_addr, ip_str, sizeof(ip_str)); + break; + } + return mp_obj_new_str(ip_str, strlen(ip_str)); +} + +static mp_obj_t convert_sockaddr(const struct zsock_addrinfo *ai, int port) { + #if CIRCUITPY_SOCKETPOOL_IPV6 + mp_int_t n_tuple = ai->ai_family == AF_INET6 ? 4 : 2; + #else + mp_int_t n_tuple = 2; + #endif + mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(n_tuple, NULL)); + result->items[0] = format_address(ai->ai_addr, ai->ai_family); + result->items[1] = MP_OBJ_NEW_SMALL_INT(port); + #if CIRCUITPY_SOCKETPOOL_IPV6 + if (ai->ai_family == AF_INET6) { + const struct sockaddr_in6 *ai6 = (void *)ai->ai_addr; + result->items[2] = MP_OBJ_NEW_SMALL_INT(ai6->sin6_flowinfo); + result->items[3] = MP_OBJ_NEW_SMALL_INT(ai6->sin6_scope_id); + } + #endif + return result; +} + +static mp_obj_t convert_addrinfo(const struct zsock_addrinfo *ai, int port) { + mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL)); + result->items[0] = MP_OBJ_NEW_SMALL_INT(ai->ai_family); + result->items[1] = MP_OBJ_NEW_SMALL_INT(ai->ai_socktype); + result->items[2] = MP_OBJ_NEW_SMALL_INT(ai->ai_protocol); + result->items[3] = ai->ai_canonname ? mp_obj_new_str(ai->ai_canonname, strlen(ai->ai_canonname)) : MP_OBJ_NEW_QSTR(MP_QSTR_); + result->items[4] = convert_sockaddr(ai, port); + return result; +} mp_obj_t common_hal_socketpool_getaddrinfo_raise(socketpool_socketpool_obj_t *self, const char *host, int port, int family, int type, int proto, int flags) { - // const struct addrinfo hints = { - // .ai_flags = flags, - // .ai_family = family, - // .ai_protocol = proto, - // .ai_socktype = type, - // }; - - // struct addrinfo *res = NULL; - // int err = socketpool_getaddrinfo_common(host, port, &hints, &res); - if (true) { + const struct zsock_addrinfo hints = { + .ai_flags = flags, + .ai_family = family, + .ai_protocol = proto, + .ai_socktype = type, + }; + + struct zsock_addrinfo *res = NULL; + int err = socketpool_getaddrinfo_common(host, port, &hints, &res); + if (err != 0 || res == NULL) { common_hal_socketpool_socketpool_raise_gaierror_noname(); } nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t result = mp_obj_new_list(0, NULL); - // for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { - // mp_obj_list_append(result, convert_addrinfo(ai, port)); - // } + for (struct zsock_addrinfo *ai = res; ai; ai = ai->ai_next) { + mp_obj_list_append(result, convert_addrinfo(ai, port)); + } nlr_pop(); - // lwip_freeaddrinfo(res); + zsock_freeaddrinfo(res); return result; } else { - // lwip_freeaddrinfo(res); + zsock_freeaddrinfo(res); nlr_raise(MP_OBJ_FROM_PTR(nlr.ret_val)); } } diff --git a/ports/zephyr-cp/common-hal/socketpool/SocketPool.h b/ports/zephyr-cp/common-hal/socketpool/SocketPool.h index 64f91e01e1cf1..7891a8b8e4820 100644 --- a/ports/zephyr-cp/common-hal/socketpool/SocketPool.h +++ b/ports/zephyr-cp/common-hal/socketpool/SocketPool.h @@ -6,6 +6,9 @@ #pragma once +#include +#include + #include "py/obj.h" typedef struct { diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 3a007661c77d9..51521e913ab5d 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -224,6 +224,20 @@ async def generate_display_resources(output_path, translation, font, extra_chara ) +async def generate_web_workflow_static(output_path, static_files): + await cpbuild.run_command( + [ + "python", + srcdir / "tools" / "gen_web_workflow_static.py", + "--output_c_file", + output_path, + *static_files, + ], + srcdir, + check_hash=[output_path], + ) + + def determine_enabled_modules(board_info, portdir, srcdir): """Determine which CircuitPython modules should be enabled based on board capabilities. @@ -246,10 +260,16 @@ def determine_enabled_modules(board_info, portdir, srcdir): enabled_modules.add("storage") module_reasons["storage"] = "Zephyr board has flash" - if "wifi" in enabled_modules: + network_enabled = board_info.get("wifi", False) or board_info.get("hostnetwork", False) + + if network_enabled: enabled_modules.add("socketpool") - enabled_modules.add("ssl") module_reasons["socketpool"] = "Zephyr networking enabled" + enabled_modules.add("hashlib") + module_reasons["hashlib"] = "Zephyr networking enabled" + + if board_info.get("wifi", False) or board_info.get("ethernet", False): + enabled_modules.add("ssl") module_reasons["ssl"] = "Zephyr networking enabled" for port_module in (portdir / "bindings").iterdir(): @@ -296,6 +316,9 @@ async def build_circuitpython(): lto = cmake_args.get("LTO", "n") == "y" circuitpython_flags.append(f"-DCIRCUITPY_ENABLE_MPY_NATIVE={1 if enable_mpy_native else 0}") circuitpython_flags.append(f"-DCIRCUITPY_FULL_BUILD={1 if full_build else 0}") + circuitpython_flags.append(f"-DCIRCUITPY_SETTINGS_TOML={1 if full_build else 0}") + circuitpython_flags.append(f"-DCIRCUITPY_OS_GETENV={1 if full_build else 0}") + circuitpython_flags.append("-DCIRCUITPY_STATUS_BAR=1") circuitpython_flags.append(f"-DCIRCUITPY_USB_HOST={1 if usb_host else 0}") circuitpython_flags.append(f"-DCIRCUITPY_BOARD_ID='\"{board}\"'") circuitpython_flags.append(f"-DCIRCUITPY_TRANSLATE_OBJECT={1 if lto else 0}") @@ -352,7 +375,10 @@ async def build_circuitpython(): enabled_modules, module_reasons = determine_enabled_modules(board_info, portdir, srcdir) + web_workflow_enabled = board_info.get("wifi", False) or board_info.get("hostnetwork", False) + circuitpython_flags.extend(board_info["cflags"]) + circuitpython_flags.append(f"-DCIRCUITPY_WEB_WORKFLOW={1 if web_workflow_enabled else 0}") supervisor_source = [ "main.c", "extmod/modjson.c", @@ -387,8 +413,8 @@ async def build_circuitpython(): supervisor_source.extend(top.glob("supervisor/shared/*.c")) supervisor_source.append(top / "supervisor/shared/bluetooth/bluetooth.c") supervisor_source.append(top / "supervisor/shared/translate/translate.c") - # if web_workflow: - # supervisor_source.extend(top.glob("supervisor/shared/web_workflow/*.c")) + if web_workflow_enabled: + supervisor_source.extend(top.glob("supervisor/shared/web_workflow/*.c")) usb_ok = board_info.get("usb_device", False) circuitpython_flags.append(f"-DCIRCUITPY_USB_DEVICE={1 if usb_ok else 0}") @@ -416,10 +442,22 @@ async def build_circuitpython(): (portdir / "supervisor/usb.c", srcdir / "supervisor/shared/usb.c") ) + creator_id = mpconfigboard.get("CIRCUITPY_CREATOR_ID", mpconfigboard.get("USB_VID")) + creation_id = mpconfigboard.get("CIRCUITPY_CREATION_ID", mpconfigboard.get("USB_PID")) + if creator_id is not None: + circuitpython_flags.append(f"-DCIRCUITPY_CREATOR_ID=0x{creator_id:08x}") + if creation_id is not None: + circuitpython_flags.append(f"-DCIRCUITPY_CREATION_ID=0x{creation_id:08x}") + # Always use port serial. It'll switch between USB and UART automatically. circuitpython_flags.append("-DCIRCUITPY_PORT_SERIAL=1") - if "ssl" in enabled_modules: + if "hashlib" in enabled_modules: + circuitpython_flags.append("-DCIRCUITPY_HASHLIB_MBEDTLS=1") + if "ssl" not in enabled_modules: + circuitpython_flags.append("-DCIRCUITPY_HASHLIB_MBEDTLS_ONLY=1") + + if "ssl" in enabled_modules or "hashlib" in enabled_modules: # TODO: Figure out how to get these paths from zephyr circuitpython_flags.append("-DMBEDTLS_CONFIG_FILE='\"config-mbedtls.h\"'") circuitpython_flags.extend( @@ -432,7 +470,8 @@ async def build_circuitpython(): ("-isystem", portdir / "modules" / "crypto" / "mbedtls" / "include") ) circuitpython_flags.extend(("-isystem", zephyrdir / "modules" / "mbedtls" / "configs")) - supervisor_source.append(top / "lib" / "mbedtls_config" / "crt_bundle.c") + if "ssl" in enabled_modules: + supervisor_source.append(top / "lib" / "mbedtls_config" / "crt_bundle.c") # Make sure all modules have a setting by filling in defaults. hal_source = [] @@ -549,6 +588,12 @@ async def build_circuitpython(): ) source_files.append(output_path) + if web_workflow_enabled: + output_path = board_build / "autogen_web_workflow_static.c" + static_files = sorted((srcdir / "supervisor/shared/web_workflow/static").glob("*")) + tg.create_task(generate_web_workflow_static(output_path, static_files)) + source_files.append(output_path) + # This file is generated by the QSTR/translation process. source_files.append(builddir / f"translations-{translation}.c") # These files don't include unique QSTRs. They just need to be compiled. diff --git a/ports/zephyr-cp/cptools/zephyr2cp.py b/ports/zephyr-cp/cptools/zephyr2cp.py index 86b0b0fe40386..a891069b3156d 100644 --- a/ports/zephyr-cp/cptools/zephyr2cp.py +++ b/ports/zephyr-cp/cptools/zephyr2cp.py @@ -395,6 +395,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir): # noqa "wifi": False, "usb_device": False, "_bleio": False, + "hostnetwork": board_id in ["native_sim"], } config_bt_enabled = False @@ -746,11 +747,25 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir): # noqa pins.write_text(pin_declarations) board_c = board_dir / "board.c" + hostnetwork_include = "" + hostnetwork_entry = "" + if board_info.get("hostnetwork", False): + hostnetwork_include = ( + '#if CIRCUITPY_HOSTNETWORK\n#include "bindings/hostnetwork/__init__.h"\n#endif\n' + ) + hostnetwork_entry = ( + "#if CIRCUITPY_HOSTNETWORK\n" + " { MP_ROM_QSTR(MP_QSTR_NETWORK), MP_ROM_PTR(&common_hal_hostnetwork_obj) },\n" + "#endif\n" + ) + new_board_c_content = f""" // This file is autogenerated by build_circuitpython.py #include "shared-bindings/board/__init__.h" +{hostnetwork_include} + #include #include "py/obj.h" @@ -779,6 +794,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir): # noqa static const mp_rom_map_elem_t board_module_globals_table[] = {{ CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS +{hostnetwork_entry} {board_pin_mapping} {zephyr_binding_labels} diff --git a/ports/zephyr-cp/prj.conf b/ports/zephyr-cp/prj.conf index 121de2c4ae52d..d0f4d911c123b 100644 --- a/ports/zephyr-cp/prj.conf +++ b/ports/zephyr-cp/prj.conf @@ -1,4 +1,4 @@ -CONFIG_SYS_HEAP_RUNTIME_STATS=y +CONFIG_SYS_HEAP_RUNTIME_STATS=n CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_STD_C23=y @@ -12,10 +12,10 @@ CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 CONFIG_THREAD_STACK_INFO=y -CONFIG_STACK_SENTINEL=y -CONFIG_DEBUG_THREAD_INFO=y -CONFIG_DEBUG_INFO=y -CONFIG_EXCEPTION_STACK_TRACE=y +CONFIG_STACK_SENTINEL=n +CONFIG_DEBUG_THREAD_INFO=n +CONFIG_DEBUG_INFO=n +CONFIG_EXCEPTION_STACK_TRACE=n CONFIG_USB_DEVICE_STACK_NEXT=y CONFIG_USBD_CDC_ACM_CLASS=y @@ -30,8 +30,8 @@ CONFIG_HWINFO=y CONFIG_REBOOT=y CONFIG_ENTROPY_GENERATOR=y -CONFIG_ASSERT=y -CONFIG_LOG_BLOCK_IN_THREAD=y +CONFIG_ASSERT=n +CONFIG_LOG_BLOCK_IN_THREAD=n CONFIG_EVENTS=y @@ -45,7 +45,7 @@ CONFIG_SPI_ASYNC=y CONFIG_LOG=y CONFIG_LOG_MAX_LEVEL=2 CONFIG_HW_STACK_PROTECTION=y -CONFIG_FRAME_POINTER=y +CONFIG_FRAME_POINTER=n CONFIG_BT_BUF_ACL_TX_COUNT=7 CONFIG_BT_HCI_ERR_TO_STR=y diff --git a/ports/zephyr-cp/tests/conftest.py b/ports/zephyr-cp/tests/conftest.py index 0bc296cf43feb..7c7eb6ae34994 100644 --- a/ports/zephyr-cp/tests/conftest.py +++ b/ports/zephyr-cp/tests/conftest.py @@ -42,6 +42,10 @@ def pytest_configure(config): "markers", "code_py_runs(count): stop native_sim after count code.py runs (default: 1)", ) + config.addinivalue_line( + "markers", + "native_sim_rt: run native_sim in realtime mode (-rt instead of -no-rt)", + ) ZEPHYR_CP = Path(__file__).parent.parent @@ -190,6 +194,8 @@ def circuitpython(request, board, sim_id, native_sim_binary, native_sim_env, tmp else: code_py_runs = int(runs_marker.args[0]) + use_realtime = request.node.get_closest_marker("native_sim_rt") is not None + if "bsim" in board: cmd = [str(native_sim_binary), f"--flash_app={flash}"] if instance_count > 1: @@ -207,7 +213,8 @@ def circuitpython(request, board, sim_id, native_sim_binary, native_sim_env, tmp else: cmd = [str(native_sim_binary), f"--flash={flash}"] # native_sim vm-runs includes the boot VM setup run. - cmd.extend(("-no-rt", "-wait_uart", f"--vm-runs={code_py_runs + 1}")) + realtime_flag = "-rt" if use_realtime else "-no-rt" + cmd.extend((realtime_flag, "-wait_uart", f"--vm-runs={code_py_runs + 1}")) marker = request.node.get_closest_marker("disable_i2c_devices") if marker and len(marker.args) > 0: diff --git a/ports/zephyr-cp/tests/docs/web_workflow.md b/ports/zephyr-cp/tests/docs/web_workflow.md new file mode 100644 index 0000000000000..eb6742b8a3ec6 --- /dev/null +++ b/ports/zephyr-cp/tests/docs/web_workflow.md @@ -0,0 +1,37 @@ +# Web Workflow native_sim Tests + +These tests validate CircuitPython's web workflow support in the Zephyr native_sim port, including filesystem write behavior with and without USB-style write protection. + +## Coverage + +- `test_web_workflow_hostnetwork`: Verifies the web workflow HTTP server responds and enforces authentication (`/edit/` returns `401 Unauthorized`). +- `test_web_workflow_write_code_py_conflict`: Exercises a write attempt while the filesystem is protected (no `boot.py` remount). The DELETE request should return `409 Conflict`. +- `test_web_workflow_write_code_py_remount`: Uses a `boot.py` remount to allow CircuitPython to write. A PUT request updates `code.py`, and a subsequent GET verifies the contents. + +## Filesystem Setup + +The tests create a flash image with: + +- `settings.toml` containing `CIRCUITPY_WEB_API_PASSWORD="testpass"` so the web workflow starts using the on-device settings file. +- `boot.py` (for the remount test only) with: + ```python + import storage + storage.remount("/", readonly=False) + ``` + This disables concurrent write protection so the web workflow can write to CIRCUITPY. + +## Running the Tests + +Build native_sim (if needed): + +```bash +make BOARD=native_native_sim +``` + +Run the tests: + +```bash +pytest -q ports/zephyr-cp/tests/test_web_workflow.py::test_web_workflow_hostnetwork -vv +pytest -q ports/zephyr-cp/tests/test_web_workflow.py::test_web_workflow_write_code_py_conflict -vv +pytest -q ports/zephyr-cp/tests/test_web_workflow.py::test_web_workflow_write_code_py_remount -vv +``` diff --git a/ports/zephyr-cp/tests/test_web_workflow.py b/ports/zephyr-cp/tests/test_web_workflow.py new file mode 100644 index 0000000000000..d3a00782a333e --- /dev/null +++ b/ports/zephyr-cp/tests/test_web_workflow.py @@ -0,0 +1,142 @@ +# SPDX-FileCopyrightText: 2026 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""Tests for web workflow on native_sim.""" + +from __future__ import annotations + +import json +import re + +import pytest +import requests + + +pytestmark = pytest.mark.native_sim_rt + +WEB_WORKFLOW_PORT = 8090 +WEB_WORKFLOW_PASSWORD = "testpass" + +WEB_WORKFLOW_CODE = """\ +import time + +# Keep the VM alive while the web workflow starts. +time.sleep(3) +""" + +WEB_WORKFLOW_UPDATED_CODE = """\ +print("updated") +""" + +WEB_WORKFLOW_SETTINGS = f"""\ +CIRCUITPY_WEB_API_PASSWORD="{WEB_WORKFLOW_PASSWORD}" +CIRCUITPY_WEB_API_PORT={WEB_WORKFLOW_PORT} +""" + +WEB_WORKFLOW_SETTINGS_PORT_80 = f"""\ +CIRCUITPY_WEB_API_PASSWORD="{WEB_WORKFLOW_PASSWORD}" +CIRCUITPY_WEB_API_PORT=80 +""" + +WEB_WORKFLOW_BOOT = """\ +import storage + +storage.remount("/", readonly=False) +""" + + +@pytest.mark.circuitpy_drive( + { + "code.py": WEB_WORKFLOW_CODE, + "settings.toml": WEB_WORKFLOW_SETTINGS, + } +) +def test_web_workflow_hostnetwork(circuitpython): + """Ensure web workflow responds over hostnetwork.""" + circuitpython.serial.wait_for(f"127.0.0.1:{WEB_WORKFLOW_PORT}") + response = requests.get(f"http://127.0.0.1:{WEB_WORKFLOW_PORT}/edit/", timeout=1.0) + + assert response.status_code == 401 + + +@pytest.mark.circuitpy_drive( + { + "code.py": WEB_WORKFLOW_CODE, + "settings.toml": WEB_WORKFLOW_SETTINGS, + } +) +def test_web_workflow_version_json_hostnetwork_ip_and_port(circuitpython): + """Ensure /cp/version.json reports hostnetwork endpoint with configured port.""" + circuitpython.serial.wait_for(f"127.0.0.1:{WEB_WORKFLOW_PORT}") + response = requests.get( + f"http://127.0.0.1:{WEB_WORKFLOW_PORT}/cp/version.json", + auth=("", WEB_WORKFLOW_PASSWORD), + timeout=1.0, + ) + + assert response.status_code == 200 + + payload = json.loads(response.text) + assert payload["ip"] == "127.0.0.1" + assert payload["port"] == WEB_WORKFLOW_PORT + + +@pytest.mark.circuitpy_drive( + { + "code.py": WEB_WORKFLOW_CODE, + "settings.toml": WEB_WORKFLOW_SETTINGS, + } +) +def test_web_workflow_status_line_hostnetwork_non_default_port(circuitpython): + """Status line should include hostnetwork IP and non-default port.""" + circuitpython.wait_until_done() + output = circuitpython.serial.all_output + + # Remove ANSI control sequences before matching. + output = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", output) + assert "127.0.0.1:8090" in output + + +@pytest.mark.circuitpy_drive( + { + "code.py": WEB_WORKFLOW_CODE, + "settings.toml": WEB_WORKFLOW_SETTINGS_PORT_80, + } +) +def test_web_workflow_status_line_hostnetwork_default_port(circuitpython): + """Status line should show IP without :80 for default HTTP port.""" + circuitpython.wait_until_done() + output = circuitpython.serial.all_output + + output = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", output) + assert "127.0.0.1" in output + assert "127.0.0.1:80" not in output + + +@pytest.mark.circuitpy_drive( + { + "boot.py": WEB_WORKFLOW_BOOT, + "code.py": WEB_WORKFLOW_CODE, + "settings.toml": WEB_WORKFLOW_SETTINGS, + } +) +def test_web_workflow_write_code_py_remount(circuitpython): + """Ensure web workflow can update code.py after remounting.""" + circuitpython.serial.wait_for(f"127.0.0.1:{WEB_WORKFLOW_PORT}") + body = WEB_WORKFLOW_UPDATED_CODE.encode("utf-8") + + response = requests.put( + f"http://127.0.0.1:{WEB_WORKFLOW_PORT}/fs/code.py", + auth=("", WEB_WORKFLOW_PASSWORD), + data=body, + timeout=1.0, + ) + assert response.status_code in (201, 204) + + response = requests.get( + f"http://127.0.0.1:{WEB_WORKFLOW_PORT}/fs/code.py", + auth=("", WEB_WORKFLOW_PASSWORD), + timeout=1.0, + ) + assert response.status_code == 200 + assert WEB_WORKFLOW_UPDATED_CODE in response.text diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 56fc695742133..a1297a4754435 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -346,6 +346,9 @@ CFLAGS += -DCIRCUITPY_HASHLIB_MBEDTLS=$(CIRCUITPY_HASHLIB_MBEDTLS) CIRCUITPY_HASHLIB_MBEDTLS_ONLY ?= $(call enable-if-all,$(CIRCUITPY_HASHLIB_MBEDTLS) $(call enable-if-not,$(CIRCUITPY_SSL))) CFLAGS += -DCIRCUITPY_HASHLIB_MBEDTLS_ONLY=$(CIRCUITPY_HASHLIB_MBEDTLS_ONLY) +# Always zero because it is for Zephyr only +CFLAGS += -DCIRCUITPY_HOSTNETWORK=0 + CIRCUITPY_I2CTARGET ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_I2CTARGET=$(CIRCUITPY_I2CTARGET) diff --git a/shared-bindings/socketpool/SocketPool.c b/shared-bindings/socketpool/SocketPool.c index e139e3a077ab2..06b63b80ea74d 100644 --- a/shared-bindings/socketpool/SocketPool.c +++ b/shared-bindings/socketpool/SocketPool.c @@ -24,12 +24,11 @@ //| a pool of sockets provided by the underlying OS. //| """ //| -//| def __init__(self, radio: wifi.Radio) -> None: +//| def __init__(self, radio: Union[wifi.Radio, hostnetwork.HostNetwork]) -> None: //| """Create a new SocketPool object for the provided radio //| -//| :param wifi.Radio radio: The (connected) network hardware to associate -//| with this SocketPool; currently, this will always be the object -//| returned by :py:attr:`wifi.radio` +//| :param radio: The (connected) network interface to associate with this +//| SocketPool, such as :py:attr:`wifi.radio` or :py:attr:`board.NETWORK`. //| """ //| ... //| diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index 74e4fed57592c..55b7de8df6340 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -8,6 +8,8 @@ #define _GNU_SOURCE #include +#include +#include #include #include "extmod/vfs.h" @@ -42,6 +44,10 @@ #include "shared-bindings/socketpool/Socket.h" #include "shared-bindings/socketpool/SocketPool.h" +#if CIRCUITPY_HOSTNETWORK +#include "bindings/hostnetwork/__init__.h" +#endif + #if CIRCUITPY_WIFI #include "shared-bindings/wifi/__init__.h" #endif @@ -84,12 +90,18 @@ typedef struct { char websocket_key[24 + 1]; } _request; +#if CIRCUITPY_WIFI static wifi_radio_error_t _wifi_status = WIFI_RADIO_ERROR_NONE; +#endif -#if CIRCUITPY_STATUS_BAR +#if CIRCUITPY_STATUS_BAR && (CIRCUITPY_WIFI || CIRCUITPY_HOSTNETWORK) // Store various last states to compute if status bar needs an update. static bool _last_enabled = false; static uint32_t _last_ip = 0; +static mp_int_t _last_web_api_port = 80; +#endif + +#if CIRCUITPY_STATUS_BAR && CIRCUITPY_WIFI static wifi_radio_error_t _last_wifi_status = WIFI_RADIO_ERROR_NONE; #endif @@ -171,11 +183,7 @@ static bool _base64_in_place(char *buf, size_t in_len, size_t out_len) { return true; } -static void _update_encoded_ip(void) { - uint32_t ipv4_address = 0; - if (common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { - ipv4_address = wifi_radio_get_ipv4_address(&common_hal_wifi_radio_obj); - } +static void _update_encoded_ip(uint32_t ipv4_address) { if (_encoded_ip != ipv4_address) { uint8_t *octets = (uint8_t *)&ipv4_address; snprintf(_our_ip_encoded, sizeof(_our_ip_encoded), "%d.%d.%d.%d", octets[0], octets[1], octets[2], octets[3]); @@ -183,66 +191,118 @@ static void _update_encoded_ip(void) { } } +static bool _get_web_workflow_ip(uint32_t *ipv4_address) { + *ipv4_address = 0; + #if CIRCUITPY_WIFI + if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) { + return false; + } + *ipv4_address = wifi_radio_get_ipv4_address(&common_hal_wifi_radio_obj); + return true; + #elif CIRCUITPY_HOSTNETWORK + // hostnetwork uses the host network namespace and is reachable via localhost. + *ipv4_address = 0x0100007f; // 127.0.0.1 + return true; + #else + return false; + #endif +} + +#if CIRCUITPY_STATUS_BAR +static void _print_web_workflow_endpoint(void) { + mp_printf(&mp_plat_print, "%s", _our_ip_encoded); + if (web_api_port != 80) { + mp_printf(&mp_plat_print, ":%d", web_api_port); + } +} +#endif + mdns_server_obj_t *supervisor_web_workflow_mdns(mp_obj_t network_interface) { - #if CIRCUITPY_MDNS + #if CIRCUITPY_MDNS && CIRCUITPY_WIFI if (network_interface == &common_hal_wifi_radio_obj && mdns.base.type == &mdns_server_type) { return &mdns; } #endif + (void)network_interface; return NULL; } #if CIRCUITPY_STATUS_BAR bool supervisor_web_workflow_status_dirty(void) { - return common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj) != _last_enabled || - _encoded_ip != _last_ip || - _last_wifi_status != _wifi_status; + #if CIRCUITPY_WIFI || CIRCUITPY_HOSTNETWORK + uint32_t ipv4_address = 0; + bool enabled = _get_web_workflow_ip(&ipv4_address); + if (enabled != _last_enabled || ipv4_address != _last_ip || web_api_port != _last_web_api_port) { + return true; + } + #if CIRCUITPY_WIFI + if (_last_wifi_status != _wifi_status) { + return true; + } + #endif + return false; + #else + return false; + #endif } #endif #if CIRCUITPY_STATUS_BAR void supervisor_web_workflow_status(void) { - _last_enabled = common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj); + #if CIRCUITPY_WIFI || CIRCUITPY_HOSTNETWORK + uint32_t ipv4_address = 0; + _last_enabled = _get_web_workflow_ip(&ipv4_address); + _last_web_api_port = web_api_port; + if (_last_enabled) { - uint32_t ipv4_address = wifi_radio_get_ipv4_address(&common_hal_wifi_radio_obj); + _update_encoded_ip(ipv4_address); + _last_ip = _encoded_ip; + if (ipv4_address != 0) { - _update_encoded_ip(); - _last_ip = _encoded_ip; - mp_printf(&mp_plat_print, "%s", _our_ip_encoded); - if (web_api_port != 80) { - mp_printf(&mp_plat_print, ":%d", web_api_port); - } + _print_web_workflow_endpoint(); // TODO: Use these unicode to show signal strength: ▂▄▆█ return; } - serial_write_compressed(MP_ERROR_TEXT("Wi-Fi: ")); - _last_wifi_status = _wifi_status; - if (_wifi_status == WIFI_RADIO_ERROR_AUTH_EXPIRE || - _wifi_status == WIFI_RADIO_ERROR_AUTH_FAIL) { - serial_write_compressed(MP_ERROR_TEXT("Authentication failure")); - } else if (_wifi_status != WIFI_RADIO_ERROR_NONE) { - mp_printf(&mp_plat_print, "%d", _wifi_status); - } else if (ipv4_address == 0) { - _last_ip = 0; - serial_write_compressed(MP_ERROR_TEXT("No IP")); - } else { - } - } else { - // Keep Wi-Fi print separate so its data can be matched with the one above. - serial_write_compressed(MP_ERROR_TEXT("Wi-Fi: ")); + } + + #if CIRCUITPY_WIFI + serial_write_compressed(MP_ERROR_TEXT("Wi-Fi: ")); + _last_wifi_status = _wifi_status; + if (!_last_enabled) { serial_write_compressed(MP_ERROR_TEXT("off")); + } else if (_wifi_status == WIFI_RADIO_ERROR_AUTH_EXPIRE || + _wifi_status == WIFI_RADIO_ERROR_AUTH_FAIL) { + serial_write_compressed(MP_ERROR_TEXT("Authentication failure")); + } else if (_wifi_status != WIFI_RADIO_ERROR_NONE) { + mp_printf(&mp_plat_print, "%d", _wifi_status); + } else { + _last_ip = 0; + serial_write_compressed(MP_ERROR_TEXT("No IP")); } + #endif + #else + return; + #endif } #endif bool supervisor_start_web_workflow(void) { - #if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI && CIRCUITPY_SETTINGS_TOML + #if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_SETTINGS_TOML && (CIRCUITPY_WIFI || CIRCUITPY_HOSTNETWORK) + + #if CIRCUITPY_WIFI + mp_obj_t socketpool_radio = MP_OBJ_FROM_PTR(&common_hal_wifi_radio_obj); + #else + mp_obj_t socketpool_radio = MP_OBJ_FROM_PTR(&common_hal_hostnetwork_obj); + #endif + settings_err_t result; + + #if CIRCUITPY_WIFI char ssid[33]; char password[64]; - settings_err_t result = settings_get_str("CIRCUITPY_WIFI_SSID", ssid, sizeof(ssid)); + result = settings_get_str("CIRCUITPY_WIFI_SSID", ssid, sizeof(ssid)); if (result != SETTINGS_OK || strlen(ssid) < 1) { return false; } @@ -275,6 +335,7 @@ bool supervisor_start_web_workflow(void) { common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, false); return false; } + #endif // Skip starting the workflow if we're not starting from power on or reset. const mcu_reset_reason_t reset_reason = common_hal_mcu_processor_get_reset_reason(); @@ -307,7 +368,7 @@ bool supervisor_start_web_workflow(void) { } pool.base.type = &socketpool_socketpool_type; - common_hal_socketpool_socketpool_construct(&pool, &common_hal_wifi_radio_obj); + common_hal_socketpool_socketpool_construct(&pool, socketpool_radio); socketpool_socket_reset(&listening); socketpool_socket_reset(&active); @@ -847,7 +908,9 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request * instance_name = common_hal_mdns_server_get_instance_name(&mdns); } #endif - _update_encoded_ip(); + uint32_t ipv4_address = 0; + (void)_get_web_workflow_ip(&ipv4_address); + _update_encoded_ip(ipv4_address); // Note: this leverages the fact that C concats consecutive string literals together. mp_printf(&_socket_print, "{\"web_api_version\": 4, " diff --git a/supervisor/shared/web_workflow/web_workflow.h b/supervisor/shared/web_workflow/web_workflow.h index 439964dd333ac..9ec15a14ddc48 100644 --- a/supervisor/shared/web_workflow/web_workflow.h +++ b/supervisor/shared/web_workflow/web_workflow.h @@ -8,7 +8,12 @@ #include +#if CIRCUITPY_MDNS #include "shared-bindings/mdns/Server.h" +#else +typedef struct mdns_server_obj mdns_server_obj_t; +#endif + #include "shared-bindings/socketpool/Socket.h" // This background function should be called repeatedly. It cannot be done based diff --git a/supervisor/shared/workflow.c b/supervisor/shared/workflow.c index c1999b4a7b68a..7370e34e86007 100644 --- a/supervisor/shared/workflow.c +++ b/supervisor/shared/workflow.c @@ -43,10 +43,12 @@ void supervisor_workflow_reset(void) { #if CIRCUITPY_WEB_WORKFLOW bool result = supervisor_start_web_workflow(); - if (workflow_background_cb.fun) { - if (result) { - supervisor_workflow_request_background(); + if (result) { + if (!workflow_background_cb.fun) { + memset(&workflow_background_cb, 0, sizeof(workflow_background_cb)); + workflow_background_cb.fun = supervisor_web_workflow_background; } + supervisor_workflow_request_background(); } #endif } @@ -105,9 +107,11 @@ void supervisor_workflow_start(void) { #if CIRCUITPY_WEB_WORKFLOW if (supervisor_start_web_workflow()) { - // Enable background callbacks if web_workflow startup successful + // Enable background callbacks if web_workflow startup successful. memset(&workflow_background_cb, 0, sizeof(workflow_background_cb)); workflow_background_cb.fun = supervisor_web_workflow_background; + // Kick the first background run now that the callback is installed. + supervisor_workflow_request_background(); } #endif From 20d9e1812ce050a8ae1e3331f3e6cd833a14cd89 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 25 Feb 2026 13:06:22 -0800 Subject: [PATCH 2/2] Remove outdated OS_GETENV flag Co-authored-by: Dan Halbert --- ports/zephyr-cp/cptools/build_circuitpython.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 51521e913ab5d..4bae0cc73bf86 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -317,7 +317,6 @@ async def build_circuitpython(): circuitpython_flags.append(f"-DCIRCUITPY_ENABLE_MPY_NATIVE={1 if enable_mpy_native else 0}") circuitpython_flags.append(f"-DCIRCUITPY_FULL_BUILD={1 if full_build else 0}") circuitpython_flags.append(f"-DCIRCUITPY_SETTINGS_TOML={1 if full_build else 0}") - circuitpython_flags.append(f"-DCIRCUITPY_OS_GETENV={1 if full_build else 0}") circuitpython_flags.append("-DCIRCUITPY_STATUS_BAR=1") circuitpython_flags.append(f"-DCIRCUITPY_USB_HOST={1 if usb_host else 0}") circuitpython_flags.append(f"-DCIRCUITPY_BOARD_ID='\"{board}\"'")