zephyr-cp/wifi: implement station connect, IPv4 getters and real authmode - #11228
Draft
mikeysklar wants to merge 7 commits into
Draft
zephyr-cp/wifi: implement station connect, IPv4 getters and real authmode#11228mikeysklar wants to merge 7 commits into
mikeysklar wants to merge 7 commits into
Conversation
The event handler has a NET_EVENT_WIFI_SCAN_RESULT case that queues each AP as it arrives, but that event was never in the subscription mask, so the case never ran and scans always returned zero networks. RAW_SCAN_RESULT is in the mask but is not a substitute. It carries raw beacon frames and only fires when CONFIG_WIFI_MGMT_RAW_SCAN_RESULTS is enabled, which it is not by default. Measured on a Raspberry Pi Pico 2 W running raspberrypi_rpi_pico2_w_zephyr, built from 069144c and flashed over SWD with pyOCD: len([1 for n in wifi.radio.start_scanning_networks()]) before 0 after 204 Same board, same probe, same script, with only this change reverted for the before run.
The Wi-Fi common-hal printed on every net event with raw printk. That output is unconditional, so it corrupts the serial handshake that raw-REPL tooling relies on, and it cannot be turned down per module. Register a cp_wifi log module and route the existing calls through it, at CONFIG_LOG_DEFAULT_LEVEL as supervisor/usb.c already does. Two printks in start_scanning_networks() only restated the message raised on the following line, so they are dropped rather than converted. Also fixes two defects the conversion exposed: - The unhandled-event print passed a uint64_t mgmt_event to %x, truncating to 32 bits. Since the layer lives in the high bits, every unhandled Wi-Fi event aliased to the same value. - NET_EVENT_IPV4_ADDR_ADD was already subscribed but had no case, so it fell through to the unhandled-event path and the status bar kept reading "No IP" after DHCP bound, while wifi.radio.ipv4_address returned the real lease.
common_hal_wifi_radio_connect() was a stub: the body was commented-out
ESP-IDF code and it returned WIFI_RADIO_ERROR_NONE without attempting
anything, so connect() silently "succeeded" while never associating.
get_connected() returned a hardcoded false and the IPv4 getters returned
None. No zephyr-cp board could join a network.
Implement connect() with NET_REQUEST_WIFI_CONNECT:
- build wifi_connect_req_params from ssid/password/channel/bssid
- wait on a semaphore signalled from CONNECT_RESULT (or DISCONNECT_RESULT,
which is how a failed attempt reports), honouring the timeout argument
and staying interruptible
- map wifi_conn_status to the CircuitPython error codes so a wrong
password raises AUTH_FAIL instead of appearing to succeed
- start DHCPv4 and wait for an address
Also implement get_connected(), get_ipv4_address() and get_ipv4_gateway()
from the Zephyr net_if state.
get_mac_address() returned an uninitialized stack buffer; read the real
address from net_if_get_link_addr() instead.
Track the associated SSID so a repeat connect() to the same network returns
without tearing down a working link, on both the normal and the -EALREADY
path.
Security is fixed at WIFI_SECURITY_TYPE_PSK here. Transition-mode APs
negotiate up from there; per-network selection follows in the next commit.
Security type has to be chosen per network. The SiWx91x driver maps WIFI_SECURITY_TYPE_PSK to SL_WIFI_WPA2 and WPA_AUTO_PERSONAL to SL_WIFI_WPA3_TRANSITION, and neither works everywhere: a WPA2-PSK AP rejects WPA3 transition and a WPA3-SAE AP rejects WPA2, both surfacing identically as "Authentication failure". So cache the most recent scan (24 entries, same-SSID replace) and look the SSID up in connect(), falling back to WPA2-PSK when it was not seen. Known limit: that fallback is silently wrong for a WPA3-only hidden AP. get_authmode() built its mask from a switch that was entirely commented out (ESP-IDF leftover) and always returned an empty list, which reads as an open network. Translate Zephyr's wifi_security_type instead. The EAP and OWE arms are taken from the header and are not exercised on hardware. Adds the ipv4_subnet and ipv4_dns getters alongside the address and gateway getters from the previous commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wifi_radio_get_ipv4_address(), the raw uint32_t getter that supervisor/shared/web_workflow/web_workflow.c uses for the status bar and for /cp/version.json's "ip" field, was a leftover ESP-IDF stub that returned 0 unconditionally. It is a separate entry point from common_hal_wifi_radio_get_ipv4_address(), the Python-facing getter: one underlying address, two functions, only one of them implemented. board_name and hostname in version.json stay empty, for an unrelated reason: both come from the mDNS responder, and zephyr-cp has no common-hal/mdns, so CIRCUITPY_MDNS never reaches web_workflow.c. That is a new component rather than a bug fix, so it is left out of this series. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
common_hal_wifi_radio_get_addresses() returned mp_const_none
unconditionally, which is the wrong type in both states for the
shared-bindings contract ("addresses: Sequence[str] ... Empty sequence when
not connected"): None instead of a tuple when connected, None instead of an
empty tuple when not.
Reuse wifi_radio_get_ipv4_address() and format it as a string, which is what
the espressif and raspberrypi ports return here rather than IPv4Address
objects.
get_addresses_ap() had the same problem and is corrected to
mp_const_empty_tuple, without claiming AP mode works.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wifi_radio_get_mac_address(self, uint8_t *) is declared in shared-bindings/wifi/Radio.h but was never implemented for this port; only the Python-facing common_hal_wifi_radio_get_mac_address() existed. Add the raw helper and have the existing function call it rather than duplicating the netif read. common_hal_wifi_radio_get_ipv4_gateway() and _subnet() read net_if_ip.ipv4 unconditionally, but that struct member only exists when CONFIG_NET_IPV4 is set. This file builds for every Wi-Fi board in the port's CI matrix, and nrf7002dk does not enable IPv4, so the unguarded access breaks that build. Guard both.
mikeysklar
force-pushed
the
zephyr-wifi/pr3-connect
branch
from
August 22, 2026 21:38
70db1a3 to
d16ed05
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements Wi-Fi station connect for zephyr-cp, and fills in the IPv4 getters,
wifi.radio.addresses, realauthmodereporting, and the raw MAC helper.Why
common_hal_wifi_radio_connect()was a stub. Its body was commented-out ESP-IDFcode and it returned
WIFI_RADIO_ERROR_NONEwithout attempting anything, soconnect()silently "succeeded" while never associating.get_connected()returned a hardcoded
falseand the IPv4 getters returnedNone. No zephyr-cpboard could join a network.
The individual fixes:
NET_REQUEST_WIFI_CONNECT, waiting on a semaphore signalledfrom
CONNECT_RESULT(orDISCONNECT_RESULT, which is how a failed attemptreports), honouring the timeout and staying interruptible, then starting DHCPv4.
wifi_conn_statusis mapped to the CircuitPython error codes so a wrongpassword raises
AUTH_FAILinstead of appearing to succeed.WIFI_SECURITY_TYPE_PSKto WPA2 andWPA_AUTO_PERSONALto WPA3-transition,and neither works everywhere: a WPA2-PSK AP rejects WPA3-transition and a
WPA3-SAE AP rejects WPA2, both surfacing identically as "Authentication
failure". So the most recent scan is cached (24 entries) and the SSID looked up
at connect time, falling back to WPA2-PSK when unseen. Known limit: that
fallback is silently wrong for a WPA3-only hidden AP.
get_authmode()built its mask from a switch that was entirely commentedout and always returned an empty list, which reads as an open network.
get_mac_address()returned an uninitialized stack buffer."ip"field in/cp/version.jsonwas always 0:wifi_radio_get_ipv4_address()is a separate entry point from the Python-facing getter and was a separate stub.
wifi.radio.addressesreturnedNone, the wrong type in both states forthe documented
Sequence[str]contract.net_if_ip.ipv4is guarded, since that member only exists withCONFIG_NET_IPV4and this file builds for every Wi-Fi board in the port,including nrf7002dk which does not enable it.
Hardware tested
siwx917_dk2605a, SoC SiWG917M111MGTBA), twoboards: one on macOS 15, one on Ubuntu 24.04.
Not tested: WPA3-SAE association, hidden networks, AP mode (not implemented on
this port), and any non-SiWx917 zephyr-cp board.
How I tested it
code.py:Serial output:
Scan now reports real per-network authmodes rather than an empty list, including
the WPA2/WPA3 distinction the security selection depends on:
The
"ip"field fix, over the web workflow on the second board:board_nameandhostnamestay empty for an unrelated reason noted above: thisport has no
common-hal/mdns.Scope
AP mode stays unimplemented.
radio.dns(the setter) is deliberately notincluded; only the read-only
ipv4_dnsgetter is here.Notes
No new translatable strings and no
locale/circuitpython.potchurn. The twoMP_ERROR_TEXTuses reuse "Only IPv4 addresses supported", which already existsand is used by the espressif and raspberrypi ports.
Second of three PRs. Stacked on #11223 and on the logging PR, so this diff also
shows their changes until they merge.
AI assistance
Written with Claude Code. I reviewed the diff myself and verified the behaviour
on the hardware listed above.