From 08ad3f91bb51b34a7f264dae9d3c82b76f35b928 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Sat, 1 Aug 2026 15:35:16 -0700 Subject: [PATCH 1/4] zephyr-cp/wifi: implement radio.ap_info common_hal_wifi_radio_get_ap_info() returned mp_const_none unconditionally, with the espressif implementation left commented out beneath it. So there was no way to read the RSSI, BSSID or channel of the AP actually associated with. The only workaround was a full scan matched against the connected SSID, which costs a scan, briefly takes the radio away from the association being asked about, and cannot distinguish the connected AP from another radio broadcasting the same SSID. Zephyr already exposes this through NET_REQUEST_WIFI_IFACE_STATUS. Translate the resulting wifi_iface_status into the wifi_scan_result that wifi.Network wraps, and return None when there is nothing to report. Two details worth keeping: - Guarded on WIFI_STATE_ASSOCIATED rather than a connected flag alone. Associated is the weakest state in which BSSID and RSSI are meaningful. - status.rssi is int, scan_result.rssi is int8_t dBm. Clamped rather than truncated: a wrapped value would surface as a positive dBm, which is the same class of bug as the driver's unsigned-magnitude RSSI fixed in siwx917/fix-scan-rssi-sign. Verified on BRD2605A against the scan-based workaround it replaces: ap_info ('foreverrun', 'b0:19:21:df:d4:03', -43, 5) scan ('foreverrun', 'b0:19:21:df:d4:03', -42, 5) bssid match True | channel match True | rssi delta -1 Same BSSID and channel; the 1 dBm difference is the two samples being taken a scan apart. The BSSID is also distinct from wifi.radio.mac_address, confirming it reports the access point rather than the station. Depends on siwx917/feat-wifi-station-connect: the guard needs self->connected to be maintained, which is what that branch fixes. Co-Authored-By: Claude Opus 5 (1M context) (cherry picked from commit 098263a8eb81119ec682df54b05873fa5609c8a6) --- ports/zephyr-cp/common-hal/wifi/Radio.c | 37 +++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/ports/zephyr-cp/common-hal/wifi/Radio.c b/ports/zephyr-cp/common-hal/wifi/Radio.c index 7658deac26e..78ff844a167 100644 --- a/ports/zephyr-cp/common-hal/wifi/Radio.c +++ b/ports/zephyr-cp/common-hal/wifi/Radio.c @@ -594,8 +594,41 @@ bool common_hal_wifi_radio_get_connected(wifi_radio_obj_t *self) { } mp_obj_t common_hal_wifi_radio_get_ap_info(wifi_radio_obj_t *self) { - // if (!esp_netif_is_netif_up(self->netif)) { - return mp_const_none; + if (self->sta_netif == NULL || !self->connected) { + return mp_const_none; + } + + // NET_REQUEST_WIFI_IFACE_STATUS carries everything a wifi.Network needs, so + // this reports the live association without spending a scan on it. + struct wifi_iface_status status = { 0 }; + if (net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, self->sta_netif, + &status, sizeof(status)) != 0) { + return mp_const_none; + } + + // Associated is the weakest state that has a meaningful BSSID and RSSI. + if (status.state < WIFI_STATE_ASSOCIATED) { + return mp_const_none; + } + + // wifi.Network wraps a scan result, so translate the status into one. + wifi_network_obj_t *ap_info = mp_obj_malloc(wifi_network_obj_t, &wifi_network_type); + size_t ssid_len = MIN(status.ssid_len, sizeof(ap_info->scan_result.ssid) - 1); + memcpy(ap_info->scan_result.ssid, status.ssid, ssid_len); + ap_info->scan_result.ssid[ssid_len] = '\0'; + ap_info->scan_result.ssid_length = ssid_len; + memcpy(ap_info->scan_result.mac, status.bssid, WIFI_MAC_ADDR_LEN); + ap_info->scan_result.mac_length = WIFI_MAC_ADDR_LEN; + ap_info->scan_result.band = status.band; + ap_info->scan_result.channel = status.channel; + ap_info->scan_result.security = status.security; + ap_info->scan_result.wpa3_ent_type = status.wpa3_ent_type; + ap_info->scan_result.mfp = status.mfp; + // status.rssi is int, scan_result.rssi is int8_t. Clamp, since a truncated + // value would wrap to a positive dBm. + ap_info->scan_result.rssi = (int8_t)MIN(MAX(status.rssi, INT8_MIN), INT8_MAX); + return MP_OBJ_FROM_PTR(ap_info); + // } // // Make sure the interface is in STA mode From 75b247098a64b0d5d964c5a252bf21ca40cd14ef Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Thu, 13 Aug 2026 20:29:03 -0700 Subject: [PATCH 2/4] zephyr-cp/wifi: implement radio.ping() on Zephyr's ICMP API common_hal_wifi_radio_ping() was a stub: the ESP-IDF body was commented out and it ended `return 0;`. The binding treats -1 and only -1 as failure (shared-bindings/wifi/Radio.c), so 0 was handed to Python as a successful 0 ms round trip. Callers written as `if result is None` read every failed ping as a success, including pings to unreachable addresses and pings issued while the radio was not even associated. Fixes mikeysklar/circuitpython#46. Implemented on net_icmp_init_ctx() / net_icmp_send_echo_request(). No Kconfig change is needed: there is no CONFIG_NET_ICMPV4 symbol in this Zephyr revision, ICMP gates on NET_IP/NET_IPV4, and NET_IPV4 is already set for this board. Notes on the implementation: - Returns elapsed milliseconds, and -1 for every failure path: bad context, send failure, timeout, and interruption. Never 0 except for a genuine sub-millisecond round trip. - Per-call state lives on the caller's stack and reaches the reply handler as the ICMP context's user_data, so there are no globals. This is safe in both directions: icmp.c assigns ctx->user_data before handing the packet to the stack, so a racing reply cannot see a stale pointer, and net_icmp_cleanup_ctx() takes the same lock the stack holds while dispatching handlers, so teardown cannot race a handler mid-dereference. - The handler matches on identifier and sequence and returns NET_CONTINUE on a mismatch. Without it, back-to-back pings report each other's timings. It reads the header with net_pkt_get_data(), which leaves the packet cursor alone, because NET_CONTINUE passes the packet to the next handler. - The wait polls with k_sem_take clamped to the time actually remaining, so ctrl-C stays responsive at 50 ms granularity while a sub-50 ms timeout cannot report a round trip that exceeded it. - Arrival is timestamped inside the handler rather than after the semaphore wakes, so the measurement excludes scheduling delay. - This Zephyr renamed the socket address types, so the destination is a struct net_sockaddr_in with NET_AF_INET. Code copied from older ICMP examples will not compile. Verified on BRD2605A: ping(gateway 192.168.0.1) 0.02 (float), wall 0.023 s ping(192.0.2.1, timeout=2) None, wall 2.003 s ping("not-an-address") ValueError: Only IPv4 addresses supported interleaved: 192.168.0.1 0.014 -> 1.1.1.1 0.024 192.168.0.1 0.010 -> 8.8.8.8 0.022 The interleaved run exercises the sequence guard: the local gateway stays at 10-14 ms across both visits while the two internet hosts sit at 22-24 ms, and every reported value tracks its own wall-clock measurement to within 3 ms. Costs 1,392 B of flash. Known limitation: an unroutable address and a routable but absent one are indistinguishable from Python, since both return None after the timeout. Destination-unreachable replies are not observed either, as the context is registered for NET_ICMPV4_ECHO_REPLY only. Built and tested on top of siwx917/fix-dns-zvfs-poll-max, though it does not depend on it. Radio.c is identical at both bases. Co-Authored-By: Claude Opus 5 (1M context) (cherry picked from commit 6fc0583d2ea4d2445843388e1e96b4da23e69485) --- ports/zephyr-cp/common-hal/wifi/Radio.c | 211 +++++++++++++++++++----- 1 file changed, 168 insertions(+), 43 deletions(-) diff --git a/ports/zephyr-cp/common-hal/wifi/Radio.c b/ports/zephyr-cp/common-hal/wifi/Radio.c index 78ff844a167..6c1c3e90056 100644 --- a/ports/zephyr-cp/common-hal/wifi/Radio.c +++ b/ports/zephyr-cp/common-hal/wifi/Radio.c @@ -30,8 +30,11 @@ // dns_resolve_get_default() for radio.ipv4_dns. #include #include +// net_icmp_* for radio.ping(). +#include #include #include +#include #if CIRCUITPY_MDNS #include "common-hal/mdns/Server.h" @@ -895,57 +898,179 @@ void common_hal_wifi_radio_set_ipv4_address_ap(wifi_radio_obj_t *self, mp_obj_t // common_hal_wifi_radio_start_dhcp_server(self); // restart access point DHCP } -// static void ping_success_cb(esp_ping_handle_t hdl, void *args) { -// wifi_radio_obj_t *self = (wifi_radio_obj_t *)args; -// esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &self->ping_elapsed_time, sizeof(self->ping_elapsed_time)); -// } +// Zephyr delivers the echo reply on the network RX thread, not the caller's, so +// the two halves share this state through the ICMP context's user_data. It lives +// on the calling thread's stack, hence the cleanup discipline in ping() below. +typedef struct { + struct k_sem reply_sem; + int64_t sent_ms; + int64_t elapsed_ms; + uint16_t identifier; + uint16_t sequence; +} ping_session_t; + +// Zephyr keeps struct net_icmpv4_echo_req in subsys/net/ip/icmpv4.h, a private +// header that code outside the net stack cannot include, so mirror the four +// bytes that follow the ICMP header here. +struct ping_echo_hdr { + uint16_t identifier; + uint16_t sequence; +} __packed; + +// An echo carries no port number, so the sequence is the only thing telling two +// back-to-back requests apart; without it a late reply would be reported as the +// next call's round trip. The identifier is randomized once per boot. +static uint16_t ping_identifier; +static uint16_t ping_sequence; + +static enum net_verdict ping_reply_handler(struct net_icmp_ctx *ctx, + struct net_pkt *pkt, + struct net_icmp_ip_hdr *ip_hdr, + struct net_icmp_hdr *icmp_hdr, + void *user_data) { + NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(echo_access, struct ping_echo_hdr); + ping_session_t *session = user_data; + struct ping_echo_hdr *echo; + + (void)ctx; + (void)ip_hdr; + (void)icmp_hdr; + + if (session == NULL) { + return NET_CONTINUE; + } + + // net_pkt_get_data() leaves the cursor where it found it, which the + // NET_CONTINUE below relies on: the next handler starts at the echo header. + echo = (struct ping_echo_hdr *)net_pkt_get_data(pkt, &echo_access); + if (echo == NULL) { + return NET_CONTINUE; + } + + if (net_ntohs(echo->identifier) != session->identifier || + net_ntohs(echo->sequence) != session->sequence) { + // A reply to an earlier ping of ours, or to somebody else's. Leave it + // alone rather than waking the caller with a round trip time that + // belongs to a different request. + return NET_CONTINUE; + } + + // Stamp arrival here rather than after k_sem_take() returns, so the + // measurement does not absorb the woken thread's scheduling delay. + session->elapsed_ms = k_uptime_get() - session->sent_ms; + k_sem_give(&session->reply_sem); + + return NET_OK; +} mp_int_t common_hal_wifi_radio_ping(wifi_radio_obj_t *self, mp_obj_t ip_address, mp_float_t timeout) { - // esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); - // ipaddress_ipaddress_to_esp_idf(ip_address, &ping_config.target_addr); - // ping_config.count = 1; - - // // We must fetch ping information using the callback mechanism, because the session storage is freed when - // // the ping session is done, even before esp_ping_delete_session(). - // esp_ping_callbacks_t ping_callbacks = { - // .on_ping_success = ping_success_cb, - // .cb_args = (void *)self, - // }; + // radio.ping() is documented to take an ipaddress.IPv4Address. + if (!mp_obj_is_type(ip_address, &ipaddress_ipv4address_type)) { + mp_raise_ValueError(MP_ERROR_TEXT("Only IPv4 addresses supported")); + } - // size_t timeout_ms = timeout * 1000; + // get_packed() takes a concrete ipaddress_ipv4address_obj_t *, so the + // MP_OBJ_TO_PTR is needed under object representations C and D. + ipaddress_ipv4address_obj_t *addr_obj = MP_OBJ_TO_PTR(ip_address); + size_t packed_len; + const char *packed = mp_obj_str_get_data( + common_hal_ipaddress_ipv4address_get_packed(addr_obj), &packed_len); + if (packed_len != sizeof(struct net_in_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("Only IPv4 addresses supported")); + } - // // ESP-IDF creates a task to do the ping session. It shuts down when done, but only after a one second delay. - // // Calling common_hal_wifi_radio_ping() too fast will cause resource exhaustion. - // esp_ping_handle_t ping; - // if (esp_ping_new_session(&ping_config, &ping_callbacks, &ping) != ESP_OK) { - // // Wait for old task to go away and then try again. - // // Empirical testing shows we have to wait at least two seconds, despite the task - // // having a one-second timeout. - // common_hal_time_delay_ms(2000); - // // Return if interrupted now, to show the interruption as KeyboardInterrupt instead of the - // // IDF error. - // if (mp_hal_is_interrupted()) { - // return (uint32_t)(-1); - // } - // CHECK_ESP_RESULT(esp_ping_new_session(&ping_config, &ping_callbacks, &ping)); - // } + // This Zephyr renamed the socket address types, so the destination is a + // struct net_sockaddr_in carrying NET_AF_INET, not a sockaddr_in/AF_INET. + struct net_sockaddr_in dst = { + .sin_family = NET_AF_INET, + }; + memcpy(&dst.sin_addr, packed, sizeof(dst.sin_addr)); + + if (ping_identifier == 0) { + // Seeded lazily. sys_rand16_get() may legitimately return 0, in which + // case we simply reseed on the next call. + ping_identifier = sys_rand16_get(); + } - // // Use all ones as a flag that the elapsed time was not set (ping failed or timed out). - // self->ping_elapsed_time = (uint32_t)(-1); + ping_session_t session = { + .identifier = ping_identifier, + .sequence = ++ping_sequence, + .elapsed_ms = -1, + }; + k_sem_init(&session.reply_sem, 0, 1); - // esp_ping_start(ping); + struct net_icmp_ctx icmp_ctx; + int res = net_icmp_init_ctx(&icmp_ctx, NET_AF_INET, NET_ICMPV4_ECHO_REPLY, 0, + ping_reply_handler); + if (res < 0) { + LOG_DBG("ping: net_icmp_init_ctx failed (%d)", res); + return -1; + } - // uint32_t start_time = common_hal_time_monotonic_ms(); - // while ((self->ping_elapsed_time == (uint32_t)(-1)) && - // (common_hal_time_monotonic_ms() - start_time < timeout_ms) && - // !mp_hal_is_interrupted()) { - // RUN_BACKGROUND_TASKS; - // } - // esp_ping_stop(ping); - // esp_ping_delete_session(ping); + struct net_icmp_ping_params params = { + .identifier = session.identifier, + .sequence = session.sequence, + .tc_tos = 0, + // A negative priority leaves the packet at the stack default and lets + // tc_tos drive the DSCP/ECN bits instead. + .priority = -1, + .data = NULL, + .data_size = 0, + }; + + session.sent_ms = k_uptime_get(); + + // A NULL sta_netif is fine; the stack picks an interface from the + // destination. This is the blocking send, as Zephyr's net shell uses: it + // waits up to a second for a buffer, so a ping can take timeout + 1s. + res = net_icmp_send_echo_request(&icmp_ctx, self->sta_netif, + (struct net_sockaddr *)&dst, ¶ms, &session); + if (res < 0) { + LOG_DBG("ping: send failed (%d)", res); + (void)net_icmp_cleanup_ctx(&icmp_ctx); + return -1; + } + + // Wait for ping_reply_handler() to signal, staying responsive to ctrl-C at + // the same 50 ms granularity as the association wait in + // common_hal_wifi_radio_connect(). + mp_float_t timeout_s = timeout <= 0 ? (mp_float_t)0.5 : timeout; + int64_t deadline = k_uptime_get() + (int64_t)(timeout_s * 1000); + bool replied = false; + while (true) { + int64_t remaining_ms = deadline - k_uptime_get(); + if (remaining_ms <= 0) { + break; + } + + // Poll in 50 ms slices so ctrl-C stays responsive, clamped to the + // caller's deadline so a reply arriving after the timeout is not + // reported as a success. MIN() is avoided here because py/misc.h and + // zephyr/sys/util.h both define it and this file includes both. + int64_t wait_ms = remaining_ms < 50 ? remaining_ms : 50; + + if (k_sem_take(&session.reply_sem, K_MSEC(wait_ms)) == 0) { + replied = true; + break; + } + if (mp_hal_is_interrupted()) { + break; + } + } + + // Unregister before returning. net_icmp_cleanup_ctx() takes the same mutex + // the stack holds while dispatching handlers, so once it returns no handler + // can still be looking at session, which lives on this stack frame. This + // has to happen on every exit path, including the error paths above. + (void)net_icmp_cleanup_ctx(&icmp_ctx); + + if (!replied || session.elapsed_ms < 0) { + return -1; + } - // return (mp_int_t)self->ping_elapsed_time; - return 0; + // shared-bindings turns exactly -1 into None and divides anything else by + // 1000, so every failure path must return -1, not 0. + return (mp_int_t)session.elapsed_ms; } void common_hal_wifi_radio_gc_collect(wifi_radio_obj_t *self) { From 2ebfde00f82f44c861d769ea6bb8974cbc362864 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Sun, 23 Aug 2026 09:45:22 -0700 Subject: [PATCH 3/4] zephyr-cp/wifi: let boards turn radio.ping() off to save flash Adds CIRCUITPY_WIFI_PING to the port Kconfig, default y, and sets it to n for nrf7002dk_nrf5340_cpuapp, which is at 99.85% of flash before this series and overflows by 516 bytes once the ICMP ping code is in. With the option off, common_hal_wifi_radio_ping() returns -1, so radio.ping() reports None the same way it does for an unreachable host. Built for nordic_nrf7002dk with the option off: links at 966388 of 966656 bytes with the Homebrew arm-none-eabi toolchain, which is about 1 KB larger than the Zephyr SDK build CI uses. --- ports/zephyr-cp/Kconfig | 9 +++++++++ ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf | 3 +++ ports/zephyr-cp/common-hal/wifi/Radio.c | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/ports/zephyr-cp/Kconfig b/ports/zephyr-cp/Kconfig index 015864100ec..b202b53d53f 100644 --- a/ports/zephyr-cp/Kconfig +++ b/ports/zephyr-cp/Kconfig @@ -25,6 +25,15 @@ config UART_LINE_CTRL config ENTROPY_GENERATOR default y +# ===== CircuitPython feature defaults — enabled by default, boards can disable ===== + +config CIRCUITPY_WIFI_PING + bool "wifi.radio.ping() on Zephyr's ICMP API" + default y + help + Boards that are out of flash can set this to n. radio.ping() then + returns None, as it does for an unreachable host. + # ===== Bluetooth defaults ===== # Use a variable for the chosen name so the comma isn't parsed as an argument separator diff --git a/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf b/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf index 2255bd760d3..a4da934009a 100644 --- a/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf +++ b/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf @@ -8,3 +8,6 @@ CONFIG_LOG=n CONFIG_ASSERT=n CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_BT=n + +# Out of flash; radio.ping() alone overflows it. +CONFIG_CIRCUITPY_WIFI_PING=n diff --git a/ports/zephyr-cp/common-hal/wifi/Radio.c b/ports/zephyr-cp/common-hal/wifi/Radio.c index 6c1c3e90056..513b4f84334 100644 --- a/ports/zephyr-cp/common-hal/wifi/Radio.c +++ b/ports/zephyr-cp/common-hal/wifi/Radio.c @@ -30,8 +30,10 @@ // dns_resolve_get_default() for radio.ipv4_dns. #include #include +#if defined(CONFIG_CIRCUITPY_WIFI_PING) // net_icmp_* for radio.ping(). #include +#endif #include #include #include @@ -898,6 +900,7 @@ void common_hal_wifi_radio_set_ipv4_address_ap(wifi_radio_obj_t *self, mp_obj_t // common_hal_wifi_radio_start_dhcp_server(self); // restart access point DHCP } +#if defined(CONFIG_CIRCUITPY_WIFI_PING) // Zephyr delivers the echo reply on the network RX thread, not the caller's, so // the two halves share this state through the ICMP context's user_data. It lives // on the calling thread's stack, hence the cleanup discipline in ping() below. @@ -1072,6 +1075,12 @@ mp_int_t common_hal_wifi_radio_ping(wifi_radio_obj_t *self, mp_obj_t ip_address, // 1000, so every failure path must return -1, not 0. return (mp_int_t)session.elapsed_ms; } +#else +mp_int_t common_hal_wifi_radio_ping(wifi_radio_obj_t *self, mp_obj_t ip_address, mp_float_t timeout) { + // Boards that turn ping off to save flash report no reply. + return -1; +} +#endif void common_hal_wifi_radio_gc_collect(wifi_radio_obj_t *self) { // Only bother to scan the actual object references. From 1fb1c8087241fccb8c0ab6aa03f50859268030e9 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Tue, 25 Aug 2026 12:03:10 -0700 Subject: [PATCH 4/4] zephyr-cp/wifi: gate radio.ping() from circuitpython.toml, not Kconfig Review feedback from #11229: keep Kconfig for Zephyr configuration and put CircuitPython feature settings in circuitpython.toml. Drops the CIRCUITPY_WIFI_PING Kconfig symbol and the CONFIG_ override in the board .conf. The flag is now emitted by build_circuitpython.py the same way CIRCUITPY_ULAB is, defaulting on, and nordic_nrf7002dk opts out in its circuitpython.toml. The C guards become #if CIRCUITPY_WIFI_PING. Verified on two SiWx917-DK2605A boards, ping still compiled in and working: 5 of 5 replies from the gateway and None for an unreachable host on both. --- ports/zephyr-cp/Kconfig | 9 --------- .../zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml | 3 +++ ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf | 3 --- ports/zephyr-cp/common-hal/wifi/Radio.c | 4 ++-- ports/zephyr-cp/cptools/build_circuitpython.py | 6 ++++++ 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/ports/zephyr-cp/Kconfig b/ports/zephyr-cp/Kconfig index b202b53d53f..015864100ec 100644 --- a/ports/zephyr-cp/Kconfig +++ b/ports/zephyr-cp/Kconfig @@ -25,15 +25,6 @@ config UART_LINE_CTRL config ENTROPY_GENERATOR default y -# ===== CircuitPython feature defaults — enabled by default, boards can disable ===== - -config CIRCUITPY_WIFI_PING - bool "wifi.radio.ping() on Zephyr's ICMP API" - default y - help - Boards that are out of flash can set this to n. radio.ping() then - returns None, as it does for an unreachable host. - # ===== Bluetooth defaults ===== # Use a variable for the chosen name so the comma isn't parsed as an argument separator diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml index f37513d252b..badaffb8e1b 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml @@ -7,3 +7,6 @@ DISABLED_MODULES=["aesio", "adafruit_bus_device", "zlib", "jpegio", "tilepalette # ulab is on by default. This board has under 3 KB of flash headroom, and # ulab costs about 90 KB, so it opts out. CIRCUITPY_ULAB = false + +# The same 3 KB is why radio.ping() is off here too. +CIRCUITPY_WIFI_PING = false diff --git a/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf b/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf index a4da934009a..2255bd760d3 100644 --- a/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf +++ b/ports/zephyr-cp/boards/nrf7002dk_nrf5340_cpuapp.conf @@ -8,6 +8,3 @@ CONFIG_LOG=n CONFIG_ASSERT=n CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_BT=n - -# Out of flash; radio.ping() alone overflows it. -CONFIG_CIRCUITPY_WIFI_PING=n diff --git a/ports/zephyr-cp/common-hal/wifi/Radio.c b/ports/zephyr-cp/common-hal/wifi/Radio.c index 513b4f84334..a85f6bbb736 100644 --- a/ports/zephyr-cp/common-hal/wifi/Radio.c +++ b/ports/zephyr-cp/common-hal/wifi/Radio.c @@ -30,7 +30,7 @@ // dns_resolve_get_default() for radio.ipv4_dns. #include #include -#if defined(CONFIG_CIRCUITPY_WIFI_PING) +#if CIRCUITPY_WIFI_PING // net_icmp_* for radio.ping(). #include #endif @@ -900,7 +900,7 @@ void common_hal_wifi_radio_set_ipv4_address_ap(wifi_radio_obj_t *self, mp_obj_t // common_hal_wifi_radio_start_dhcp_server(self); // restart access point DHCP } -#if defined(CONFIG_CIRCUITPY_WIFI_PING) +#if CIRCUITPY_WIFI_PING // Zephyr delivers the echo reply on the network RX thread, not the caller's, so // the two halves share this state through the ICMP context's user_data. It lives // on the calling thread's stack, hence the cleanup discipline in ping() below. diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 72cd7d28342..cb36ce7f3da 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -657,6 +657,12 @@ async def build_circuitpython(): # noqa: C901 str(top / "extmod" / "ulab" / "code"), ) ) + # radio.ping() builds on Zephyr's ICMP API. On by default; boards that cannot + # spare the flash set CIRCUITPY_WIFI_PING = false in their circuitpython.toml + # and radio.ping() then reports no reply, as it does for an unreachable host. + circuitpython_flags.append( + f"-DCIRCUITPY_WIFI_PING={1 if mpconfigboard.get('CIRCUITPY_WIFI_PING', True) else 0}" + ) source_files = supervisor_source + hal_source + ["extmod/vfs.c"] if ulab_enabled: