From 8f8697e3b7ca0fef4eadc929418ec50bc83d3f65 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Fri, 4 Sep 2026 18:36:00 -0700 Subject: [PATCH 1/3] wifi: let connect() take a scan result Passing a Network from start_scanning_networks() uses its bssid and channel, so reaching a specific AP needs no second scan. Gated behind CIRCUITPY_WIFI_CONNECT_NETWORK, on by default. The nRF7002 DK runs from 1MB of internal flash with under 800 bytes free and overflowed by 32, so it opts out in its circuitpython.toml the way it already does for ulab and radio.ping(). Costs 64 bytes, measured on the ESP32-C5. --- .../nordic/nrf7002dk/circuitpython.toml | 3 ++ .../zephyr-cp/cptools/build_circuitpython.py | 6 ++++ py/circuitpy_mpconfig.h | 6 ++++ shared-bindings/wifi/Radio.c | 31 +++++++++++++++---- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml index badaffb8e1b..91e479488b6 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml @@ -10,3 +10,6 @@ CIRCUITPY_ULAB = false # The same 3 KB is why radio.ping() is off here too. CIRCUITPY_WIFI_PING = false + +# And why connect() does not take a scan result here. +CIRCUITPY_WIFI_CONNECT_NETWORK = false diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 8594c245c81..3209447d872 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -668,6 +668,12 @@ async def build_circuitpython(): # noqa: C901 circuitpython_flags.append( f"-DCIRCUITPY_WIFI_PING={1 if mpconfigboard.get('CIRCUITPY_WIFI_PING', True) else 0}" ) + # wifi.Radio.connect() accepts a Network from a scan. On by default; boards + # that cannot spare the flash set CIRCUITPY_WIFI_CONNECT_NETWORK = false in + # their circuitpython.toml and connect() then takes only an ssid. + circuitpython_flags.append( + f"-DCIRCUITPY_WIFI_CONNECT_NETWORK={1 if mpconfigboard.get('CIRCUITPY_WIFI_CONNECT_NETWORK', True) else 0}" + ) source_files = supervisor_source + hal_source + ["extmod/vfs.c"] if ulab_enabled: diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index 9dd5abc8a3a..42e5ef8245c 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -695,6 +695,12 @@ void background_callback_run_all(void); #error "CIRCUITPY_BOOT_BUTTON and CIRCUITPY_BOOT_BUTTON_NO_GPIO are mutually exclusive" #endif +// wifi.Radio.connect() accepts a Network from a scan. Boards short of flash can +// set this to 0; connect() then takes only an ssid. +#ifndef CIRCUITPY_WIFI_CONNECT_NETWORK +#define CIRCUITPY_WIFI_CONNECT_NETWORK (1) +#endif + #if defined(__GNUC__) && !defined(__ZEPHYR__) #if __GNUC__ < CIRCUITPY_MIN_GCC_VERSION // (the 3 level scheme here is required to get expansion & stringization diff --git a/shared-bindings/wifi/Radio.c b/shared-bindings/wifi/Radio.c index 76c0fa69c90..b9b9444a82f 100644 --- a/shared-bindings/wifi/Radio.c +++ b/shared-bindings/wifi/Radio.c @@ -6,6 +6,9 @@ #include "shared-bindings/wifi/__init__.h" #include "shared-bindings/wifi/AuthMode.h" +#if CIRCUITPY_WIFI_CONNECT_NETWORK +#include "shared-bindings/wifi/Network.h" +#endif #include "shared-bindings/wifi/PowerManagement.h" #include @@ -443,7 +446,7 @@ MP_PROPERTY_GETTER(wifi_radio_ap_active_obj, //| def connect( //| self, -//| ssid: Union[str, ReadableBuffer], +//| ssid: Union[str, ReadableBuffer, Network], //| password: Union[str, ReadableBuffer] = b"", //| *, //| channel: int = 0, @@ -464,7 +467,10 @@ MP_PROPERTY_GETTER(wifi_radio_ap_active_obj, //| significantly because a full scan doesn't occur. //| //| If ``bssid`` is given and not None, the scan will start at the first channel or the one given and -//| connect to the AP with the given ``bssid`` and ``ssid``.""" +//| connect to the AP with the given ``bssid`` and ``ssid``. +//| +//| A `Network` from `start_scanning_networks` may be given in place of ``ssid``. Its +//| ``bssid`` and ``channel`` are used, which avoids the scan entirely.""" //| ... //| static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -486,9 +492,22 @@ static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_m timeout = mp_obj_get_float(args[ARG_timeout].u_obj); } + mp_obj_t ssid_obj = args[ARG_ssid].u_obj; + mp_obj_t bssid_obj = args[ARG_bssid].u_obj; + mp_int_t channel = args[ARG_channel].u_int; + + #if CIRCUITPY_WIFI_CONNECT_NETWORK + if (mp_obj_is_type(ssid_obj, &wifi_network_type)) { + wifi_network_obj_t *network = MP_OBJ_TO_PTR(ssid_obj); + ssid_obj = common_hal_wifi_network_get_ssid(network); + bssid_obj = common_hal_wifi_network_get_bssid(network); + channel = mp_obj_get_int(common_hal_wifi_network_get_channel(network)); + } + #endif + mp_buffer_info_t ssid; ssid.len = 0; - mp_get_buffer_raise(args[ARG_ssid].u_obj, &ssid, MP_BUFFER_READ); + mp_get_buffer_raise(ssid_obj, &ssid, MP_BUFFER_READ); mp_arg_validate_length_range(ssid.len, 1, 32, MP_QSTR_ssid); mp_buffer_info_t password; @@ -508,14 +527,14 @@ static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_m mp_buffer_info_t bssid; bssid.len = 0; // Should probably make sure bssid is just bytes and not something else too - if (args[ARG_bssid].u_obj != mp_const_none) { - mp_get_buffer_raise(args[ARG_bssid].u_obj, &bssid, MP_BUFFER_READ); + if (bssid_obj != mp_const_none) { + mp_get_buffer_raise(bssid_obj, &bssid, MP_BUFFER_READ); if (bssid.len != MAC_ADDRESS_LENGTH) { mp_raise_ValueError(MP_ERROR_TEXT("Invalid BSSID")); } } - wifi_radio_error_t error = common_hal_wifi_radio_connect(self, ssid.buf, ssid.len, password.buf, password.len, args[ARG_channel].u_int, timeout, bssid.buf, bssid.len); + wifi_radio_error_t error = common_hal_wifi_radio_connect(self, ssid.buf, ssid.len, password.buf, password.len, channel, timeout, bssid.buf, bssid.len); if (error == WIFI_RADIO_ERROR_AUTH_FAIL) { mp_raise_ConnectionError(MP_ERROR_TEXT("Authentication failure")); } else if (error == WIFI_RADIO_ERROR_NO_AP_FOUND) { From 5235eb518f0f43b21322098761eceebef8049974 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Wed, 9 Sep 2026 18:48:21 -0700 Subject: [PATCH 2/3] wifi: make the scan result a network= kwarg connect() takes a network= keyword instead of accepting a Network in the ssid slot. ssid is now optional, network is keyword only, and passing both raises TypeError. Co-Authored-By: Claude Opus 5 (1M context) --- locale/circuitpython.pot | 6 +++++- shared-bindings/wifi/Radio.c | 30 +++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index c55efbcf8c1..a2df58a82c3 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2307,7 +2307,7 @@ msgstr "" msgid "function expected at most %d arguments, got %d" msgstr "" -#: py/argcheck.c +#: py/argcheck.c shared-bindings/wifi/Radio.c msgid "'%q' argument required" msgstr "" @@ -4258,6 +4258,10 @@ msgstr "" msgid "AuthMode.OPEN is not used with password" msgstr "" +#: shared-bindings/wifi/Radio.c +msgid "Supply either %q or %q, not both" +msgstr "" + #: shared-bindings/wifi/Radio.c msgid "Invalid BSSID" msgstr "" diff --git a/shared-bindings/wifi/Radio.c b/shared-bindings/wifi/Radio.c index b9b9444a82f..ba431470872 100644 --- a/shared-bindings/wifi/Radio.c +++ b/shared-bindings/wifi/Radio.c @@ -446,12 +446,13 @@ MP_PROPERTY_GETTER(wifi_radio_ap_active_obj, //| def connect( //| self, -//| ssid: Union[str, ReadableBuffer, Network], +//| ssid: Optional[Union[str, ReadableBuffer]] = None, //| password: Union[str, ReadableBuffer] = b"", //| *, //| channel: int = 0, //| bssid: Optional[Union[str, ReadableBuffer]] = None, //| timeout: Optional[float] = None, +//| network: Optional[Network] = None, //| ) -> None: //| """Connects to the given ssid and waits for an ip address. Reconnections are handled //| automatically once one connection succeeds. @@ -469,18 +470,26 @@ MP_PROPERTY_GETTER(wifi_radio_ap_active_obj, //| If ``bssid`` is given and not None, the scan will start at the first channel or the one given and //| connect to the AP with the given ``bssid`` and ``ssid``. //| -//| A `Network` from `start_scanning_networks` may be given in place of ``ssid``. Its -//| ``bssid`` and ``channel`` are used, which avoids the scan entirely.""" +//| If ``network`` is given, it must be a `Network` returned by +//| `start_scanning_networks`. Its ``ssid``, ``bssid`` and ``channel`` are used, so no +//| scan happens. Give either ``ssid`` or ``network``, not both.""" //| ... //| static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_ssid, ARG_password, ARG_channel, ARG_bssid, ARG_timeout }; + enum { ARG_ssid, ARG_password, ARG_channel, ARG_bssid, ARG_timeout, + #if CIRCUITPY_WIFI_CONNECT_NETWORK + ARG_network, + #endif + }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_ssid, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_ssid, MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_password, MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #if CIRCUITPY_WIFI_CONNECT_NETWORK + { MP_QSTR_network, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #endif }; wifi_radio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); @@ -497,14 +506,21 @@ static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_m mp_int_t channel = args[ARG_channel].u_int; #if CIRCUITPY_WIFI_CONNECT_NETWORK - if (mp_obj_is_type(ssid_obj, &wifi_network_type)) { - wifi_network_obj_t *network = MP_OBJ_TO_PTR(ssid_obj); + if (args[ARG_network].u_obj != mp_const_none) { + if (ssid_obj != mp_const_none) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("Supply either %q or %q, not both"), MP_QSTR_ssid, MP_QSTR_network); + } + wifi_network_obj_t *network = MP_OBJ_TO_PTR(mp_arg_validate_type(args[ARG_network].u_obj, &wifi_network_type, MP_QSTR_network)); ssid_obj = common_hal_wifi_network_get_ssid(network); bssid_obj = common_hal_wifi_network_get_bssid(network); channel = mp_obj_get_int(common_hal_wifi_network_get_channel(network)); } #endif + if (ssid_obj == mp_const_none) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' argument required"), MP_QSTR_ssid); + } + mp_buffer_info_t ssid; ssid.len = 0; mp_get_buffer_raise(ssid_obj, &ssid, MP_BUFFER_READ); From 222dc38ef2ce6c8f895468801d9d8b8b0195c53b Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Thu, 10 Sep 2026 13:03:56 -0700 Subject: [PATCH 3/3] wifi: drop the connect() network gate #11335 gave the nRF7002 DK the flash it needed, so every board gets connect(network=). Removes CIRCUITPY_WIFI_CONNECT_NETWORK. Co-Authored-By: Claude Opus 5 (1M context) --- .../boards/nordic/nrf7002dk/circuitpython.toml | 3 --- ports/zephyr-cp/cptools/build_circuitpython.py | 6 ------ py/circuitpy_mpconfig.h | 6 ------ shared-bindings/wifi/Radio.c | 12 +----------- 4 files changed, 1 insertion(+), 26 deletions(-) diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml index 91e479488b6..badaffb8e1b 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml @@ -10,6 +10,3 @@ CIRCUITPY_ULAB = false # The same 3 KB is why radio.ping() is off here too. CIRCUITPY_WIFI_PING = false - -# And why connect() does not take a scan result here. -CIRCUITPY_WIFI_CONNECT_NETWORK = false diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 3209447d872..8594c245c81 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -668,12 +668,6 @@ async def build_circuitpython(): # noqa: C901 circuitpython_flags.append( f"-DCIRCUITPY_WIFI_PING={1 if mpconfigboard.get('CIRCUITPY_WIFI_PING', True) else 0}" ) - # wifi.Radio.connect() accepts a Network from a scan. On by default; boards - # that cannot spare the flash set CIRCUITPY_WIFI_CONNECT_NETWORK = false in - # their circuitpython.toml and connect() then takes only an ssid. - circuitpython_flags.append( - f"-DCIRCUITPY_WIFI_CONNECT_NETWORK={1 if mpconfigboard.get('CIRCUITPY_WIFI_CONNECT_NETWORK', True) else 0}" - ) source_files = supervisor_source + hal_source + ["extmod/vfs.c"] if ulab_enabled: diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index 42e5ef8245c..9dd5abc8a3a 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -695,12 +695,6 @@ void background_callback_run_all(void); #error "CIRCUITPY_BOOT_BUTTON and CIRCUITPY_BOOT_BUTTON_NO_GPIO are mutually exclusive" #endif -// wifi.Radio.connect() accepts a Network from a scan. Boards short of flash can -// set this to 0; connect() then takes only an ssid. -#ifndef CIRCUITPY_WIFI_CONNECT_NETWORK -#define CIRCUITPY_WIFI_CONNECT_NETWORK (1) -#endif - #if defined(__GNUC__) && !defined(__ZEPHYR__) #if __GNUC__ < CIRCUITPY_MIN_GCC_VERSION // (the 3 level scheme here is required to get expansion & stringization diff --git a/shared-bindings/wifi/Radio.c b/shared-bindings/wifi/Radio.c index ba431470872..9dffaad995d 100644 --- a/shared-bindings/wifi/Radio.c +++ b/shared-bindings/wifi/Radio.c @@ -6,9 +6,7 @@ #include "shared-bindings/wifi/__init__.h" #include "shared-bindings/wifi/AuthMode.h" -#if CIRCUITPY_WIFI_CONNECT_NETWORK #include "shared-bindings/wifi/Network.h" -#endif #include "shared-bindings/wifi/PowerManagement.h" #include @@ -476,20 +474,14 @@ MP_PROPERTY_GETTER(wifi_radio_ap_active_obj, //| ... //| static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_ssid, ARG_password, ARG_channel, ARG_bssid, ARG_timeout, - #if CIRCUITPY_WIFI_CONNECT_NETWORK - ARG_network, - #endif - }; + enum { ARG_ssid, ARG_password, ARG_channel, ARG_bssid, ARG_timeout, ARG_network }; static const mp_arg_t allowed_args[] = { { MP_QSTR_ssid, MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_password, MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - #if CIRCUITPY_WIFI_CONNECT_NETWORK { MP_QSTR_network, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - #endif }; wifi_radio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); @@ -505,7 +497,6 @@ static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_m mp_obj_t bssid_obj = args[ARG_bssid].u_obj; mp_int_t channel = args[ARG_channel].u_int; - #if CIRCUITPY_WIFI_CONNECT_NETWORK if (args[ARG_network].u_obj != mp_const_none) { if (ssid_obj != mp_const_none) { mp_raise_TypeError_varg(MP_ERROR_TEXT("Supply either %q or %q, not both"), MP_QSTR_ssid, MP_QSTR_network); @@ -515,7 +506,6 @@ static mp_obj_t wifi_radio_connect(size_t n_args, const mp_obj_t *pos_args, mp_m bssid_obj = common_hal_wifi_network_get_bssid(network); channel = mp_obj_get_int(common_hal_wifi_network_get_channel(network)); } - #endif if (ssid_obj == mp_const_none) { mp_raise_TypeError_varg(MP_ERROR_TEXT("'%q' argument required"), MP_QSTR_ssid);