Conversation
wifi.radio.start_ap() returned silently with ap_active False: the whole body of common_hal_wifi_radio_start_ap() was commented-out ESP-IDF code. The AIROC driver already implements ap_enable/ap_disable through wifi_mgmt, so this is the hook-up, plus the pieces around it. start_ap(ssid, password, channel, authmode, max_connections) issues NET_REQUEST_WIFI_AP_ENABLE with the CircuitPython authmode mapped to WIFI_SECURITY_TYPE_NONE / PSK / SAE (the driver brings every PSK variant up as WPA2-AES-PSK; WPA1/TKIP is not offered separately). max_connections gets the ESP32 range check but cannot be applied: the CYW43439's association limit is fixed in the controller. The AP gets 192.168.4.1/24 by default (as on ESP32), settable with set_ipv4_address_ap(), and the DHCPv4 server starts with it (pool of 8 above the AP address, DNS pointing at the AP, RFC 8910 option 114 so phones and laptops find a captive portal automatically); start_dhcp_ap()/stop_dhcp_ap() remain explicit controls. stop_ap() tears it all down. ap_active asks the driver (WIFI_MODE_AP in the interface status) rather than trusting a flag. stations_ap lists the stations the driver reported through NET_EVENT_WIFI_AP_STA_CONNECTED/DISCONNECTED with the IPv4 address from the DHCP lease table (no RSSI: wifi_mgmt's station events carry none). set_ipv4_address() for the station side, start_dhcp() and stop_dhcp() are implemented on the way. The AIROC driver runs the AP on the same net_if as the station and refuses AP_ENABLE with -EBUSY while the station is associated, so there is no simultaneous AP+STA on these boards: disconnect() first. Soft reboot tears the AP down (wifi_reset), and start_ap() disables a running AP the driver still reports before enabling, so the two cannot get out of step -- a soft reboot was seen to leave the driver's flag set when its stop failed. Needs the AIROC driver fix in tyeth/zephyr (branch airoc-softap-fixes): without it the driver's chanspec composition makes the firmware reject every 2.4 GHz channel, and a station leaving took the AP's interface dormant. Verified on a Pico 2 W with an ESP32-C6 as the station: start_ap() completes in 0.15 s; the C6 associates in 7 s, gets 192.168.4.2 from the DHCP server with gateway/DNS 192.168.4.1, pings the AP in 5-8 ms; stations_ap shows it with its lease, empties when it disconnects (the AP stays active), and shows it again on rejoin; the AP survives stop_ap()/start_ap() cycles and a soft reboot. Flash +9,444 B, RAM +3,568 B (DHCPv4 server + socket service). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tyeth
force-pushed
the
zephyr-cp-pico2w-sockets-tls
branch
from
October 3, 2026 13:39
04a55c9 to
03775d2
Compare
tyeth
force-pushed
the
zephyr-cp-pico2w-softap
branch
from
October 3, 2026 13:39
8ea8f3f to
6e20a32
Compare
tyeth
added a commit
to tyeth/deepsleep_espnow_wifi_and_ble_env_collector
that referenced
this pull request
Oct 3, 2026
… ESP32 Redo of PR #11's first half on top of the hubmain/nodemain shims (#27/#29), the external RTC (#30) and the UTC clock (#31), with the hardware results posted on #11 folded in. caps.py (identical in collector/ and node/) probes what the port can do -- imports, a call that raises, the platform string; never a board id -- and keeps a wall clock where there is no RTC. On CircuitPython's Zephyr port time.time() and zero-arg time.localtime() raise "RTC is not supported", so caps keeps an epoch offset against time.monotonic_ns(), and `caps.time` is a stand-in for the time module whose time()/localtime() read it. On every ESP32 caps.time IS the time module and caps.set_epoch() is exactly the rtc.RTC().datetime write it replaces, so the collector's modules switch their `import time` for `time = caps.time` and not one of the ~30 call sites changes (test_datastore_sync's fake clock keeps working too). extrtc does the same only when time.time() has nothing behind it, so an ESP32 node never loads caps, and on a Pico the coin cell sets caps' clock the way it sets rtc.RTC() elsewhere (new test in test_extrtc.py). The hub's gates, each defaulting to the ESP32 path: * code.py refuses below 40 KB of free heap (a Pico W) and idles with the REPL up. #11 used 128 KB -- which would have refused the Pico 2 W: gc.mem_free() there reports ~70 KB at a bare REPL while the heap grows to ~208 KB, so the floor sits under the smallest board that can run the hub. On an ESP32 it is one comparison before `import hubmain`. * no espnow: the early block says so instead of "early ESP-NOW failed", net_espnow is not imported, and a _NoHub keeps the counters/API. * softAP: #11 treated start_ap() as a permanent stub. It was a stub before tyeth/circuitpython#22 and is real after it, so on Zephyr the AP is started and then believed only if ap_active says so (hubmain and net_captive). ap_active is not checked on ESP32, where it can lag a start_ap() that worked. * no APSTA on the CYW43439/AIROC (-EBUSY): with home-WiFi credentials the station wins and the early AP is skipped; if the join fails, net_captive brings the setup AP up late (fine on that port -- the early-AP rule is a C6 workaround). * no user SPI (board.SPI is the radio's PIO bus, custom busio raises): display and SD skipped before their modules load; I2C from caps.i2c() (board.I2C0, a callable on the tested Pico 2 W). * sockets: net_wifi allows min(6, caps.MAX_SOCKETS - 3) clients -- the two listeners and the captive DNS count against Zephyr's 6 -- and config.json "max_sockets" overrides it. 6 on ESP32, as before. * NTP and POST /api/time set the clock through caps; `import rtc` is gone from hubmain's top, where it would have stopped a Pico at load. caps is imported after the early radio bring-up, never above it; the two Zephyr facts the early block needs come from sys.platform inline. Checked under CPython with stub modules: an ESP32-shaped hub boots with a log identical to the previous commit's apart from the new "platform:" line; a Pico-2-W-shaped one (time.time() raising) joins the station, syncs NTP into caps' clock, skips display/SD, and with the join failing brings the AP up late -- or, with a stub start_ap(), reports that no AP came up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
wifi.radio.start_ap()was a commented-out ESP-IDF body;ap_activestayedFalse. This wires it to the AIROC driver throughwifi_mgmtand adds the pieces a captive portal needs.Implemented:
start_ap(ssid, password, channel, authmode, max_connections)→NET_REQUEST_WIFI_AP_ENABLE(authmode → NONE/PSK/SAE; the driver brings every PSK variant up as WPA2-AES-PSK;max_connectionsis range-checked but the CYW43439's limit is fixed in the controller), AP address 192.168.4.1/24 by default (set_ipv4_address_ap()to change), DHCPv4 server started with the AP (pool of 8, DNS = AP, RFC 8910 option 114 for captive-portal detection),stop_ap(),ap_active(asks the driver),stations_ap(fromNET_EVENT_WIFI_AP_STA_CONNECTED/DISCONNECTED, IP from the lease table, RSSINone— wifi_mgmt carries none),ipv4_address_ap/gateway_ap/subnet_ap,start_dhcp_ap/stop_dhcp_ap, plus station-sideset_ipv4_address(),start_dhcp(),stop_dhcp(). Soft reboot tears the AP down;start_ap()first disables an AP the driver still reports, so the two cannot get out of step.Constraint: the AIROC driver runs AP and STA on one
net_ifand returns-EBUSYforAP_ENABLEwhile the station is associated — no simultaneous AP+STA on these boards;disconnect()first.Requires tyeth/zephyr branch
airoc-softap-fixes(3943b1cd7): the driver pre-composed band+bandwidth bits into the chanspec thatwhd_wifi_init_ap()wraps inCH20MHZ_CHSPEC(), so every 2.4 GHz channel became a 5 GHz chanspec (0xd001) and the firmware rejected it withWLAN_BADCHAN— this is why the AP could never start, not the CircuitPython side. The same commit stops a station's DEAUTH from taking the AP's own interface dormant and raises the AP station events without wpa_supplicant.Verified on hardware (Pico 2 W AP, ESP32-C6 station):
start_ap()0.15 s; C6 associates in 7 s, gets 192.168.4.2 with gateway/DNS 192.168.4.1, pings the AP in 5–8 ms;stations_apshows it with its lease, empties when it leaves (AP stays active), returns on rejoin;stop_ap()/start_ap()cycles and a soft reboot with a station attached both leave a working AP; HTTPS served over it (see #21). Known nit: right after a soft reboot the station'sipv4_addressinstations_apcan readNoneuntil its lease is renewed.Size (Pico 2 W): flash +9,444 B, RAM +3,568 B (DHCPv4 server + socket service).
🤖 Generated with Claude Code