Commit Graph

1305 Commits

Author SHA1 Message Date
David Montero 90c2d1da51 feat(sim): WiFi run-gate seam so free users get an upgrade prompt, not a crash
Pico W WiFi is a paid overlay feature. A free/web user running a Pico W sketch
that uses WiFi had no peripheral attached -> the simulator picked the plain Pico
firmware (no `network` module) -> the run crashed on `import network` with a
raw Python traceback (on a public example page, no less).

Add lib/proWifiGate.ts (mirrors proBoardGate): a stable doorbell the overlay
fills in. useSimulatorStore gates both loadMicroPythonProgram (before loading
firmware) and startBoard (run backstop for example/loaded boards): if the gate
blocks, fire the upgrade prompt and skip the run. Non-WiFi Pico W sketches still
run for free. No-op in OSS (default 'allow' -> a Pico W runs as a plain Pico).
2026-06-15 18:16:50 +02:00
David Montero cd3fdded3d chore(test): prune research/exploration dirs not run by CI
The test/ tree carried a lot of one-off research, repro, and exploration
material (autosearch notes, test_100_days, test_Raspberry_Pi_Pico_W, the Intel/Z80
+ custom-chip + ePaper + micropython repro harnesses, etc.) that no GitHub Actions
workflow nor deploy.sh runs. It bloats a fresh clone for no community benefit.

Keep only the dirs CI actually exercises:
  - test/backend/      (backend-unit-tests.yml, backend-e2e-tests.yml, deploy.sh)
  - test/esp32_cam/    (backend-e2e-tests.yml)
  - test/test_circuit/ (test-circuit.yml)

No workflow, script, or Dockerfile referenced any removed path.
2026-06-15 17:10:41 +02:00
David Montero Crespo fae9208fae Merge branch 'master' of https://github.com/davidmonterocrespo24/velxio 2026-06-15 11:57:59 -03:00
David Montero Crespo cf6a609671 Raspberri pi 4 y 5 2026-06-15 11:57:10 -03:00
David Montero 375b952d47 refactor(examples): move Pico W WiFi examples to the pro overlay seam
The Pico W WiFi showcase examples are a paid-overlay feature now (the WiFi
engine moved to the overlay). Read them through a build-time `@pro` seam so the
SSR prerender + gallery + sitemap include them when built with the overlay, and
OSS gets an empty stub.

- data/examples.ts: import { proExamples } from '@pro/data/proExamples' (static,
  build-time) instead of the local examples-picow-wifi.ts; delete that file.
- src/__pro_stub__/data/proExamples.ts: OSS no-op (empty list) for the @pro alias.
- vitest.config.ts: mirror the @pro alias (stub by default / overlay when
  VITE_PRO_BUILD) so tests loading examples.ts resolve it.
- scripts/generate-sitemap.mjs: also parse <PRO_OVERLAY_PATH>/data/proExamples.ts
  when building with the overlay (the script reads example IDs from source text,
  so it can't follow the alias).
- Tests: drop the picow-wifi import/usage from the 5 OSS example tests (they
  validate the OSS set now); prune the 4 obsolete picow netlist snapshots. The
  overlay's proExamples get their own coverage in pro/.../__tests__/.
2026-06-15 15:54:44 +02:00
David Montero 7db9e41278 test: drop Pico W backend tests (moved to the pro overlay)
test/backend/unit/test_picow_inbound_gateway.py and
test/backend/integration/test_picow_net_bridge.py imported
app.services.picow_net, which moved to the private overlay in the open-core
split. They now live under pro/backend/tests/ and run against the overlay.
Removing them here unbreaks the OSS pytest collection (and the deploy gate).
2026-06-15 08:54:16 +02:00
David Montero b820c372cc feat(seo): add /v3 release landing page + refresh About for 3.0
- New Velxio3Page (/v3): retro CPUs (Z80/8080/4004/4040/8086), MicroSD,
  ePaper, multi-board interconnect, ngspice WASM migration, undo/redo,
  -88% bundle, 100+ examples. JSON-LD (SoftwareApplication 3.0.0, FAQ,
  breadcrumb). Mirrors the v2.5 page; reuses SEOPage.css/Velxio2Page.css.
- Wire-up: App.tsx route, entry-server.tsx prerender map, seoRoutes.ts
  entry (priority 0.95, prerendered to dist/v3/index.html). v2-5 demoted
  to 0.9.
- AboutPage: Velxio 3.0 is now the 'latest' release card (v2.5 + v2 kept);
  stats refreshed (boards 17 -> 19+, CPU architectures 6 -> 10+ for the
  new retro ISAs).
- i18n: new v3 block in releases.json + about.releases.v3* in common2.json,
  auto-translated to all 9 locales via translate-i18n.mjs.
2026-06-15 08:52:09 +02:00
David Montero b166dfd3e2 test: refresh stale picow-wifi-relay-web-server netlist snapshot
The stored snapshot predated the relay-LED netlist emission (current-sense
V-source + LED diode model), so examples-netlist-snapshot failed on a clean
checkout regardless of any source change. Regenerate it to match the current
NetlistBuilder output. Unblocks the deploy test gate.
2026-06-15 08:45:27 +02:00
David Montero fb813ffde0 feat(opencore): extract Pico W WiFi to a pluggable PIO peripheral seam
Move the CYW43439 (Pico W) WiFi emulation out of the open-source tree so it
can ship as a paid feature in a private overlay. OSS keeps a plain Pico W
(no WiFi); the overlay registers the cyw43 protocol + backend network stack
at runtime via generic seams.

Frontend:
- Add simulation/PioPeripheral.ts: a generic "PIO bus peripheral" seam
  (feedWord / inDiscardableWriteData / resetFraming / hostWakeLevel /
  onHostWake / onSimulationStart). No factory is installed in OSS, so
  createPioPeripheral() returns null and a Pico W simulates as a plain Pico.
- RP2040Simulator: keep the fragile PIO-FIFO plumbing (it must re-run after
  loadMicroPython swaps the chip) but drive it through PioPeripheral instead
  of an inlined cyw43 import (attachCyw43 -> attachPioPeripheral, etc.).
- useSimulatorStore: generic attach/detach + setBoardWifiStatus; drop the
  cyw43 bridge map.
- MicroPythonLoader: add registerFirmwareVariant() so an overlay can add the
  RPI_PICO_W build; remove the OSS pico-w config + bundled .uf2.
- Delete simulation/cyw43/ (moved to the overlay).

Backend:
- core/hooks.py: add generic register_ws_sim_handler / dispatch_ws_sim_message
  and register_gateway_proxy / dispatch_gateway_proxy seams.
- simulation.py: route start_picow / stop_picow / picow_packet_out through the
  ws_sim_handler hook (the overlay handles + gates them).
- iot_gateway.py: resolve the Pico W gateway through the gateway_proxy hook.
- Delete services/picow_net/ + picow_net_bridge.py (moved to the overlay).

Tests: move the cyw43/picow suites to the overlay; update RP2040Simulator
mock stubs to attachPioPeripheral.
2026-06-15 08:33:28 +02:00
David Montero 07a0bfef6e seo: emit trailing-slash canonical + sitemap URLs
Static/docs/example routes are served as <route>/index.html and nginx
301-redirects the slash-less form to add the trailing slash. The sitemap
generator, the prerender canonical/og:url, and the client useSEO canonical
all emitted the slash-LESS form, so every sitemap URL was fetched as a
redirect (filed under 'Page with redirect' in Search Console) and each
canonical pointed at a redirecting URL.

Align all three to the trailing-slash form so sitemap URL == canonical ==
served URL == 200, with no redirect hop.

- generate-sitemap.mjs: append '/' to every <loc> (root stays '/')
- prerender-seo.mjs: withSlash() on canonical + og:url (routes + examples)
- useSEO.ts: withTrailingSlash() on canonical + og:url (covers dynamic
  project pages too)
2026-06-15 06:10:32 +02:00
David Montero Crespo 09eaf00c55 fix(canvas): don't treat the active board as a delete target on Delete/Backspace
The canvas had two independent window keydown listeners. The wire handler
removed the selected wire and returned, but that return cannot stop the
separate component/board handler, which fell into 'else if (activeBoardId)'
and popped the board-removal confirmation. activeBoardId is not a visual
selection -- it is just the board whose code is open in the editor, so it is
effectively always set. Pressing Delete to remove a wire (or after deleting a
component) therefore always asked to remove the board.

Remove the keyboard board-delete branch entirely: Delete/Backspace now only
removes the selected component. Board removal stays on its deliberate paths
(right-click Remove board, touch pin-picker delete). Also add the text-field
guard (input/textarea/select/contenteditable) to the wire handler so Backspace
while typing in the AI chat no longer deletes a selected wire.
2026-06-14 23:28:49 -03:00
David Montero Crespo f9ebdc240e fix(i18n): load + resolve non-English locales on direct navigation
Every non-English page fell back to English when loaded directly (deep link,
refresh, SEO-indexed URL). Two causes:

1. index.ts seeded lng with the URL locale at init, but only the English
   bundle is inlined. LocaleSync only fetches a locale bundle when
   i18n.language !== target, which was already false on a direct non-default
   load, so the bundle was never loaded. Init at DEFAULT_LOCALE and let
   LocaleSync drive the locale from the URL (a real changeLanguage that
   re-renders).

2. The region-coded locales (zh-cn, pt-br) never resolved their own bundle:
   i18next's default code formatting rewrote them to zh-CN / pt-BR, which
   failed the lowercase supportedLngs check and were dropped from the resolve
   hierarchy. Add lowerCaseLng: true.

Verified in a production build: /zh-cn/* and /pt-br/* render translated;
toResolveHierarchy now returns ['zh-cn','en'] / ['pt-br','en'].
2026-06-14 08:17:05 +02:00
David Montero 54e265b108 fix(picow): drop the broken 'Open in tab' link from the device panel
Opening the gateway in a new tab backgrounds the emulation tab and pauses
its rAF, freezing the chip — the page then 502s. The in-tab iframe is the
only way the Pico W device page stays reachable, so the panel no longer
offers an open-in-tab affordance (Reload + Close only).
2026-06-14 07:42:44 +02:00
David Montero ec40204f92 feat(picow): wire a visible LED to GP2 in the relay example
Confirmed the RP2040 GPIO -> PinManager -> wokwi-led path is fully wired
(identical to the AVR path): a wokwi-led wired anode->GP2, cathode->GND
lights when MicroPython drives GP2 HIGH. The board's componentId is its
boardType ('pi-pico-w') since loadExample's addBoard gives the first
board of a kind id == kind.

Add a red LED on GP2 to picow-wifi-relay-web-server and flip the relay
logic to active-high (ON => GP2 HIGH => LED lit) so the toggle is visible
on the canvas, not just in the device panel.
2026-06-14 07:20:52 +02:00
David Montero 1478f7f543 fix(picow): make the device panel a draggable, non-modal floating window
The first iframe panel was a full-screen modal with a backdrop: it
covered the canvas and blocked the editor, so you couldn't watch the
board react or keep clicking buttons/wiring while it was open, and it
couldn't be moved.

Now it's a small floating panel docked top-right, draggable by its title
bar, resizable, with NO backdrop — the canvas and editor stay fully
interactive underneath.

Also: the relay and async-led device pages now show a big colored ON/OFF
indicator (these are web-server demos; the GP2/onboard LED isn't drawn on
the canvas, so the panel is where you see the state flip).
2026-06-14 06:28:29 +02:00
David Montero 80d2086907 fix(picow): open the IoT gateway in an in-app iframe, not a new tab
The Pico W emulation runs in THIS browser tab via requestAnimationFrame.
Opening the gateway with target=_blank / window.open backgrounds the
emulation tab; the browser then pauses its rAF, the simulated chip
freezes, and the gateway can no longer reach the server on it — the
request times out (502) and toggles do nothing.

Render the served page in a same-tab iframe panel (openDeviceGateway) for
the Pico W so the emulation stays in the foreground and keeps answering.
The ESP32 is unchanged (its server runs in QEMU on the backend, immune to
tab visibility), so it keeps opening in a new tab.

Also: the async-led page now shows the LED state (the board's onboard LED
isn't drawn on the canvas) so the toggle has visible feedback.
2026-06-14 04:19:48 +02:00
David Montero Crespo 3b8266a75f
Merge pull request #244 from davidmonterocrespo24/fix/picow-gateway-browser-robustness
Fix/picow gateway browser robustness
2026-06-13 22:13:35 -03:00
David Montero 2dcdabe72f fix(picow): web examples use relative URLs + a resilient server loop
The served page loads under /api/gateway/<id>/, so absolute fetches like
fetch('/on') hit velxio.dev/on instead of the chip — the LED/relay/servo
controls did nothing. Use relative paths (fetch('on')) so they resolve
under the gateway. The relay example now has real ON/OFF buttons and
wraps its blocking accept loop in try/except so a dropped browser
connection can't kill it.

e2e now fires two sequential requests (first with a browser-sized header)
against a non-resilient blocking server and asserts both are served.
2026-06-14 03:11:54 +02:00
David Montero bb4d06cc7a fix(picow): make the IoT gateway robust to real browsers
Two real-world failures hit the Pico W gateway that the headless e2e
(node fetch, tiny request, fast timing) didn't surface:

- A browser sends KILOBYTES of headers (cookies, User-Agent, sec-*).
  Forwarded verbatim, the request overran the chip's small recv()
  (e.g. recv(1024)); lwIP then RST the connection on close-with-unread-
  data, crashing blocking-socket sketches with ECONNRESET. Now we forward
  a MINIMAL request (method, path, Host, Connection: close, and
  Content-Type/Length for bodies) — nothing a tiny server can choke on.

- After a gateway request completed we dropped the connection immediately,
  so a late chip segment (retransmitted FIN / trailing ACK) no longer
  matched and fell through to the chip-initiated NAT, which RST it. Add a
  short TIME_WAIT: keep the connection briefly and re-ACK late segments so
  the chip closes cleanly, never RSTing it.
2026-06-14 03:11:54 +02:00
David Montero Crespo 319decf1bd
Merge pull request #243 from davidmonterocrespo24/feat/picow-iot-gateway
Feat/picow iot gateway
2026-06-13 21:37:58 -03:00
David Montero 1aba6e2e48 feat(picow): surface the IoT gateway in the UI (parity with ESP32)
- SerialMonitor linkifies http://10.13.37.x (the Pico W subnet) the same
  way it already does http://192.168.4.x for the ESP32, turning the
  sketch's printed URL into an 'Open IoT Gateway' link.
- SimulatorCanvas shows the clickable WiFi badge for the Pico W too
  (normalizing its 'started' status, which carries the fixed IP, to
  got_ip so it reuses the ESP32 badge styling + launcher).
- async-led and servo-web examples print a clickable http://<ip>/ line
  so the gateway link appears (relay-web-server already did).

Gated e2e (CYW43_GATEWAY_E2E=1) drives the real emulator + a running
backend and asserts the served page comes back through /api/gateway.
2026-06-14 02:15:53 +02:00
David Montero 173cc3ea36 feat(picow): IoT gateway — proxy browser HTTP into the chip's server
ESP32 web-server examples are reachable from the browser via
/api/gateway/<client_id>/ (QEMU slirp hostfwd). The Pico W server lives
in the browser-side lwIP, so there was no inbound path: visiting the
chip's IP did nothing.

Add the mirror of tcp_nat.py: tcp_inbound.TcpInbound originates a TCP
connection INTO the chip over the WebSocket bridge (SYN -> SYN+ACK ->
ACK -> request -> response -> FIN), so the backend can fetch a page the
sketch serves on 10.13.37.42:80 and hand it back to the browser.

- bridge.py routes chip TCP segments addressed to a gateway-opened
  connection to TcpInbound (before the chip-initiated NAT, which would
  RST them); exposes http_into_chip() + ensure_chip_mac() (primes the
  chip's gateway ARP).
- iot_gateway.py: same /api/gateway/<client_id>/ route now falls through
  to the Pico W bridge when there's no ESP32 instance, builds a raw
  HTTP/1.1 request, and parses the chip's response. Same plan gate, same
  URL shape — the browser sees no difference between ESP32 and Pico W.

Validated end to end (real RP2040 emulator serving an HTTP page ->
gateway returns it) plus 6 unit tests for the TCP state machine,
response parsing and ARP priming.
2026-06-14 02:15:53 +02:00
David Montero Crespo 9fc0655612 i18n: fill 19 missing keys in de/fr/it/ja/ru
quotaModal.* (10), editor.share.updateFailed + visibility labels/hints (7),
editor.toolbar.exportBom + exportScreenshot (2) were present in en but absent
in de/fr/it/ja/ru. es/pt-br/zh-cn were already complete. Translated via
DeepSeek; interpolation tokens and JSON shape preserved, existing values
untouched.
2026-06-13 08:01:21 +02:00
David Montero Crespo b3558a4c42
Merge pull request #242 from davidmonterocrespo24/feat/picow-internet-bridge
Feat/picow internet bridge
2026-06-13 03:00:13 -03:00
David Montero fd0edd8c6d feat(cyw43): bridge Pico W DNS/TCP/UDP to the backend for real internet
Wi-Fi sketches on the emulated Pico W associate via the chip's built-in
virtual net (DHCP/ARP answered locally), but outbound traffic had no
route, so DNS/MQTT/HTTP failed with OSError -2.

Wire the emulator's outbound DATA path to the backend picow_net bridge:

- Cyw43Emulator forwards every outbound Ethernet frame EXCEPT DHCP/ARP
  (still answered locally) to firePacketOut -> the WS bridge, which NATs
  DNS/TCP/UDP to the real internet and injects replies back.
- The virtual net stays ON unconditionally and shares the backend's
  subnet, gateway and gateway MAC (10.13.37.0/24, gw 10.13.37.1). Nothing
  is mutually exclusive, so an absent or flaky bridge can never break the
  Wi-Fi association -- it just falls back to no-internet, as before.
- useSimulatorStore opens the bridge (cyw43.connect()) for Wi-Fi sketches.

Validated end to end against a running backend: WiFi connect + DHCP, DNS
resolves example.com, TCP connect + HTTP GET returns 200 OK. Gated e2e in
picow-bridge-e2e.investigate.test.ts (CYW43_BRIDGE_E2E=1).
2026-06-13 05:27:49 +02:00
David Montero 2639f80a22 fix(cyw43): word-align SDPCM frames so the F2 byte-swap preserves the tail
The CYW43439 F2 (radio frame) channel is word-oriented: the real chip
always drives frames padded up to a 4-byte boundary and the host reads
that word-aligned length, byte-swapping every 32-bit word on the way in.

encodeSdpcm built buffers of exactly 12 + payload bytes, so any frame
whose total length was not a multiple of 4 ended with a partial word.
The emulator's F2 read path (encodeFrameWords) byte-swaps whole words and
copies the leftover tail raw; the host's symmetric per-word swap then
mangles that final word, corrupting the last 1-3 bytes of the frame.

This was invisible for DHCP/ARP (UDP checksum 0 -> lwIP skips the check,
and the damage lands in trailing option padding) but silently dropped
every DNS answer and TCP segment (real checksum -> lwIP discards the
frame), so getaddrinfo()/connect() retried forever.

Pad the backing buffer to a 4-byte boundary while keeping the size header
at the true length, so the driver still parses exactly the real frame and
ignores the pad. Matches real hardware framing.
2026-06-13 05:27:38 +02:00
David Montero Crespo 6345ba5fab
Merge pull request #241 from davidmonterocrespo24/fix/littlefs-utf8-truncation
fix(micropython): write LittleFS files with UTF-8 byte length
2026-06-12 23:11:04 -03:00
David Montero 13e0841681 fix(micropython): write LittleFS files with UTF-8 byte length
loadUserFiles passed content.length (UTF-16 code units) as the byte count
to lfs_write_file, but cwrap marshals the content to the heap as UTF-8. A
file with multi-byte chars (e.g. an em-dash in a comment) is then written
short by the multi-byte overhead, truncating the tail. The async-LED Wi-Fi
example (2 em-dashes) lost its last 4 bytes, turning the final
'asyncio.run(main())' into 'asyncio.run(main' -> SyntaxError at EOF. Use
the UTF-8 byte length so the whole file lands; ASCII files are unaffected.
2026-06-13 04:08:02 +02:00
David Montero Crespo 2f4e205a59
Merge pull request #240 from davidmonterocrespo24/wip/picow-cyw43-emulation
Wip/picow cyw43 emulation
2026-06-12 22:42:13 -03:00
David Montero 1061685e84 feat(cyw43): WiFi-now — virtual net handles association, bridge deferred
For the first deploy, keep the chip emulator's built-in virtual DHCP/ARP
net ON and leave the backend internet bridge dormant (not validated end
to end yet). A Pico W board now associates and gets a link-local IP
locally (isconnected True); outbound internet (MQTT/HTTP) has no route
until the picow_net bridge is wired. Revert is a one-liner in the store
(cyw43.wifiEnabled = hasWifi; cyw43.connect()) + setVirtualNet(null).
2026-06-13 03:33:15 +02:00
David Montero Crespo 2cb1ad3f05
Merge pull request #239 from davidmonterocrespo24/wip/picow-cyw43-emulation
Wip/picow cyw43 emulation
2026-06-12 22:27:39 -03:00
David Montero e68746ed57 test(cyw43): validate WiFi on the production RP2040Simulator path
Headless test that drives the REAL RP2040Simulator (attachCyw43 +
installCyw43PioHooks + lockstep PIO stepping in runFrameForTime), boots
the Pico W firmware, injects a WiFi-connect snippet over the raw REPL, and
asserts isconnected(). Result:

  PYBOOT
  ACTIVE False            (this fw's active() getter reports link status)
  CONN_OK 192.168.4.2     (DHCP-leased IP, isconnected() == True)
  MAINPY_DONE

Reaches link-up in ~31s wall — the production lockstep PIO stepping is
faster than the harness's setTimeout-cranked PIO.

Also fixes a real production bug: the RP2040 logger was
ConsoleLogger(LogLevel.Error) which THROWS on rp2040js unaligned-read
warnings — lwIP reads the IPv4 header at ethernet offset 14 on every
received packet, so WiFi would have crashed on the first DHCP reply.
Now constructed with throwOnError=false.

Gated behind CYW43_PROD_HARNESS=1 (boots real firmware, ~30s).
2026-06-13 00:43:48 +02:00
David Montero c10e63764c feat(cyw43): wire WiFi bring-up into production RP2040Simulator
Port the gSPI wiring proven in the boot harness into the real simulator
so WiFi works in the browser, not just the test:

- Non-dropping TX FIFO (head-pointer queue) in installCyw43PioHooks, so
  the 260-word F2 IOCTL writes aren't truncated, with the firmware/
  backplane bulk-write fast-path (inDiscardableWriteData) keeping the
  ~224 KB download cheap. Fully restorable on detach.
- Drive WL_HOST_WAKE (GPIO24) from emu.onHostWake, and re-sync the pin
  level after installCyw43PioHooks (loadMicroPython resets GPIO while the
  chip's queue persists).
- With a backend bridge attached, disable the built-in DHCP/ARP net so
  the bridge owns the network.

Production steps the PIO in lockstep with the CPU (pioStepAccum), so no
PIO-rate crank is needed (that was harness-only). The emulator-side fixes
(host-wake, F2 byte order, join events, BDC header, virtual DHCP/ARP) are
already shared. Not yet exercised in a browser e2e — the headless harness
is the verification today.
2026-06-12 23:48:04 +02:00
David Montero 4d631b3a98 feat(cyw43): virtual DHCP/ARP net — WiFi reaches LINK_UP (isconnected)
The Pico W now connects end to end with NO backend: status reaches
CYW43_LINK_UP (3) and network.WLAN().isconnected() returns True.

After association the STA's lwIP broadcasts DHCP DISCOVER and ARPs the
gateway over the cyw43 DATA channel. A self-contained virtual network
(new virtualNet.ts) answers them:
  - DHCP DISCOVER -> OFFER, REQUEST -> ACK (Ethernet+IPv4+UDP+BOOTP, valid
    IPv4 header checksum, UDP checksum 0), leasing 192.168.4.2 with gateway
    192.168.4.1.
  - ARP who-has the gateway -> is-at the AP MAC.
On by default (Cyw43EmulatorOptions.virtualNet); pass null when an
external packet bridge owns the network.

Also fixes injectPacket to prepend the 4-byte BDC header that chip->host
DATA frames need (same as the event-frame fix), so injected packets parse.

Boot harness now reports: STEP_CONNECT_CALLED status=3 / POLL 0 status 3
conn True / HARNESS_DONE.

Known: receiving packets triggers ~30 rp2040js unaligned-read warnings
(lwIP reads the IPv4 header at ethernet offset 14); non-fatal here, but
the production RP2040Simulator must use a non-throwing logger.
2026-06-12 23:43:14 +02:00
David Montero 74365d4ae9 feat(cyw43): WiFi associates — join events drive link up (status NOIP)
The Pico W now joins the virtual AP end to end: active(True) returns,
connect() runs the full WPA/SET_SSID sequence, and the link comes up.

Root causes fixed (each blocked the join):
- mcast_list GET returned empty, so the driver read its own request bytes
  as the address count (ASCII 'mcas' ~1.9e9) and looped ~2e9 times,
  hanging wifi_on. GETs now return a zero-filled buffer of the asked-for
  length (count 0 / status 0), never empty.
- Async event frames lacked the 4-byte BDC header the driver expects at
  SDPCM header_length, so it read the broadcast-MAC byte as data_offset
  and the payload pointed out of bounds (WRONG_PAYLOAD_TYPE). Prepend BDC.
- WLC_E_LINK signalled link-up via the reason field, but the driver
  checks ev->flags & 1. encodeEventFrame now takes a flags arg; LINK uses
  flags=1.
- Join needs WIFI_JOIN_STATE_KEYED, which only a WLC_E_PSK_SUP(status=6)
  event sets (connect(ssid, "") still configures the WPA supplicant).
  Emit it on a successful join.
- Join events were raised synchronously during the SET_SSID ioctl, so the
  driver processed them before cyw43_wifi_join set wifi_join_state=ACTIVE,
  wiping the bits. Defer events until just after the ioctl reply.
- Event-mask stored 4 bytes misaligned vs queueEvent's read offset.
- SET/GET kind bit is 0x2 (SDPCM_SET), not 0x1.

Remaining for status UP / isconnected: DHCP (needs the packet-transport
bridge or an emulator-side DHCP responder).
2026-06-12 23:33:24 +02:00
David Montero 6bdb590b0a perf(cyw43): fast-path firmware download in boot harness
Add PioBusSniffer.inDiscardableWriteData(): true while framing a large
non-F2 write (firmware/backplane bulk write the chip discards). The boot
harness drops those data words (keeping ~4 so the PIO raises TXSTALL,
which is all the driver's write path waits for) instead of bit-banging
the full ~224 KB through the PIO. F2/SDPCM IOCTL writes and every
count/command word are retained in full, so the bring-up still completes
the 23-IOCTL wifi_on sequence (F1 framing 3613 -> 97, F2 unchanged).

Also adds IPSR + PC-histogram sampling: confirmed the post-mcast_list
stall is thread-mode (no GPIO IRQ storm) inside MicroPython's host-side
cyw43_cb_tcpip_init (lwIP), above the chip emulation.
2026-06-12 23:01:00 +02:00
David Montero c4cbb17591 feat(cyw43): host-wake IRQ + F2 frame byte-order + SET/GET fix
Unblocks the full wifi_on IOCTL sequence in the boot harness (clm_load
through the 23-IOCTL bring-up, no crash):

- Drive WL_HOST_WAKE (GPIO24, active-high): the driver gates poll_device
  on this pin until its first packet (had_successful_packet), so without
  it the first IOCTL response is never read. Emulator now exposes
  onHostWake(level) and toggles it with the inbound-frame queue.
- Encode F2/SDPCM frame reads per 32-bit word (encodeFrameWords), same
  as register reads: the DMA-in sets channel bswap=true, so an un-encoded
  frame landed byte-reversed -> header_length read back as garbage and
  the driver dereferenced ioctl_header at an unaligned address (crash).
  Guarded to boot mode pass-through (no F2 traffic there; keeps unit tests).
- Fix SET/GET detection: SDPCM_SET is bit 1 (0x2), not 0x1; echo the
  kind bit in IOCTL responses.
- Add IOCTL/SDPCM debug counters + sequence log for the harness.

Harness (investigation, CYW43_HARNESS=1 only): non-dropping TX FIFO so
large F2 writes are not truncated, crank PIO steps/tick so the firmware
drains in wall-clock, GPIO24 host-wake wiring, CPU-fault + PC-histogram
+ PIO-state instrumentation.

Remaining: stall after mcast_list (#22) inside cyw43_cb_tcpip_init.
2026-06-12 22:42:49 +02:00
David Montero b172cadbc5 wip(picow): deterministic gSPI framing via per-transfer restart hook
cyw43_spi_transfer calls pio_sm_restart before each transfer's count words, so
hooking restart() to reset the sniffer makes framing deterministic across the
firmware-stream fast-path (no phantom-transfer carryover). Verified: restarts
fire 3625x (once per transfer), F1 phantom count drops, and the CLM IOCTL write
now frames correctly (cmd decodes to F2, 'clmload' payload). Wired into
RP2040Simulator + the harness.

Remaining (next session): the CLM/IOCTL write doesn't complete its payload and
wifi_on still fails (active()=False) — bus_init stalls at/around clm_load with
only 2 STATUS reads and goes idle. Next: trace the CLM write's DMA/PIO drain and
the SDPCM IOCTL response path. See findings.md F-13.
2026-06-12 21:21:28 +02:00
David Montero f183f8add2 wip(picow): Phase 3 instrumentation — confirm credit frame is queued+visible
debugInboundCount + STATUS-read tracking show initInbound=1, statusReads=2,
statusReadsWithPkt=2, finalInbound=1: the credit frame IS visible at both STATUS
reads (not a credit tight-loop). The driver reaches clm_load's F2-ready check
(passes) but the F2 IOCTL write never appears on the bus and bus_init returns.
Next: instrument the F2-write path. See findings.md F-13.
2026-06-12 20:18:50 +02:00
David Montero 847c2b894b wip(picow): Phase 3 diagnosis — harness function histogram + active() probes
Pins the connect blocker: zero F2 transfers (F0=11 F1=97 F2=0), so the host
never sends an IOCTL — it stalls on SDPCM bus credits in clm_load (STATUS shows
no F2_PACKET_AVAILABLE) and times out, so wifi_on fails and active() stays
False. Next: make the credit-granting frame visible in SPI_STATUS during the
stall. See project/picow-wifi-emulation/findings.md F-13.
2026-06-12 20:15:12 +02:00
David Montero 9197fcaaf6 wip(picow): CYW43 emulation — chip bring-up works, active(True) returns
Brings the Pico W CYW43439 gSPI emulation from "fails at the first register
read" to "the chip boots fully and MicroPython's network.WLAN().active(True)
returns" — validated end-to-end against the real RPI_PICO_W firmware via a
headless boot harness.

What now works (Phases 1-2):
- PioBusSniffer rewritten to the real cyw43_bus_pio_spi framing
  [out_bits][in_bits][cmd][write_data], skipping the two PIO loop-counter
  words. Self-healing: validates count1 (= tx_length*8-1, 4-aligned, <=2052)
  and skips non-conforming words — re-syncs after the extra word rp2040js
  pushes on large writes AND fast-paths the ~224 KB firmware stream.
- Dual word-order regime: boot 16-bit-LE (swap16x2 / swap16) flips to 32-bit
  big-endian (bswap32) at the SPI_BUS_CONTROL write. Calibrated empirically
  against the firmware. Sniffer reads the mode via setModeProvider().
- Cyw43Emulator: encodeReadWord (per-regime), readBytes-sized backplane reads
  with the value in the last word (response-delay pad), ALP+HT clocks and F2
  always ready, AI core registers (IOCTRL/RESETCTRL), interrupt register
  reports no errors, f1Mem echo store, SDPCM bus-credit granting + initial
  frame.
- RP2040Simulator: serves chip responses on rxFIFO.pull (on-demand) instead of
  racing the async DMA/PIO; passes readBytes through.

Not done yet (Phase 3+): connect() runs but stalls in the power-management /
save-restore phase before any F2/IOCTL traffic; packet transport (Tier 2) and
firmware-clocking perf are open. See project/picow-wifi-emulation/ for the full
research, phases, and findings.

The boot harness (picow-cyw43-boot-harness.investigate.test.ts) is gated behind
CYW43_HARNESS=1 so it stays out of the normal test run.
2026-06-12 20:04:06 +02:00
David Montero Crespo 478fd75a7e
Merge pull request #238 from davidmonterocrespo24/fix/picow-micropython-wifi-firmware
fix(sim): Pico W MicroPython loads the RPI_PICO_W firmware (network +…
2026-06-12 12:01:05 -03:00
David Montero 4d80a9d1c3 fix(sim): Pico W MicroPython loads the RPI_PICO_W firmware (network + CYW43)
The RP2040 MicroPython loader always fetched the plain RPI_PICO build, which
ships no `network` module and no CYW43 WiFi driver. Every Pico W WiFi/MQTT
example therefore failed at `import network` ("no module named 'network'"),
which surfaced as a compile/run error in the editor.

- getFirmware()/loadUserFiles() are now variant-aware. pi-pico-w boards load
  RPI_PICO_W-20230426-v1.20.0 (network/socket/ssl + the CYW43439 driver) and
  write the LittleFS at the W board's flash offset (0x12c000, 212 blocks)
  instead of the plain Pico's 0xa0000/352. The W firmware spans flash to
  ~0xab000 and would otherwise be clobbered by the filesystem. Each variant
  gets its own IndexedDB cache key.
- The variant is selected by the presence of the already-wired CYW43 emulator
  (attachCyw43 runs for pi-pico-w boards only).
- loadMicroPython swaps in a fresh RP2040 each run, so the CYW43 PIO-FIFO hooks
  are re-installed on the new instance; otherwise the driver's gSPI traffic
  never reaches the emulator and WiFi never comes up.
- Bundle micropython-rp2040w.uf2 as the offline fallback.
- Point the ThingsBoard example at the simulator's Velxio-GUEST network.
2026-06-12 16:46:41 +02:00
velxio-deploy a19f5940ee chore(examples): refresh 1 thumb file(s) [auto] 2026-06-12 16:08:06 +02:00
David Montero Crespo 61c549de47
Merge pull request #237 from davidmonterocrespo24/fix/picow-wifi-examples-boardtype
fix(examples): move WiFi/MQTT 100-days examples to Pico W (network module)
2026-06-12 10:58:19 -03:00
David Montero a44a3e700f fix(examples): WiFi/MQTT 100-days examples must run on Pico W, not plain Pico
8 MicroPython examples that use `import network` (Blynk IoT relay, ThingsBoard
IoT, OTA update, DHT11 HTTP CSV logger, async LED control, web servo, websocket
LED, IoT relay web server) had boardType "raspberry-pi-pico". A plain Pico
(RP2040) has no WiFi and no `network` module, so they failed at runtime with
`ImportError: no module named 'network'` (the banner even shows "Raspberry Pi
Pico with RP2040"). Move them all to "pi-pico-w", which has WiFi + network.
2026-06-12 15:57:23 +02:00
David Montero Crespo 3c580cac36
Merge pull request #236 from davidmonterocrespo24/feat/esp32-wifi-mqtt-example
feat(examples): ESP32 WiFi + MQTT (PubSubClient) gallery example
2026-06-12 10:54:21 -03:00
David Montero 0fc76611b2 feat(examples): ESP32 WiFi + MQTT (PubSubClient) gallery example
Adds a self-contained ESP32 networking example for the /examples gallery
(addresses feature request #115). The sketch joins the emulator AP
"Velxio-GUEST", connects to a public MQTT broker (broker.hivemq.com:1883),
then publishes to its own topic and subscribes to it so each message
round-trips through the broker and toggles GPIO2 -- no external client or
local broker needed; just open the Serial Monitor.

Verified end to end in QEMU: WiFi associates (IP 192.168.4.15), DNS resolves
and outbound TCP to :1883 succeeds via slirp NAT. PubSubClient is auto-
installed via the example's `libraries` field.
2026-06-12 15:52:11 +02:00
David Montero Crespo 47d08733b8
Merge pull request #235 from davidmonterocrespo24/fix/arduino-mega-i2c-classify
fix(interconnect): classify Arduino Mega UART/I2C function-label pins
2026-06-12 10:07:43 -03:00
David Montero fddc03aa60 fix(interconnect): classify Arduino Mega UART/I2C function-label pins
Follow-up audit after the ESP32 fix: classifyPin() was run for every board
against the protocol pin labels its element actually exposes. One real gap
remained -- Arduino Mega. Its dedicated SDA/SCL pins are only labelled (not
numbered), so I2C links drawn on them came back 'digital' and never bridged.
Map every Mega function label (TX/RX, TX0-3/RX0-3, SDA/SCL) to its pin number.

Audit result for the rest (added as board-protocols-audit.test.ts):
- Arduino Uno/Nano, Pico/Pico-W, STM32 Blue Pill: already OK.
- ESP32 / ESP32-C3: fixed earlier (esp32-uart-pin-classify).
- Raspberry Pi 3/4/5: OK -- the element labels pins by physical number (1..40)
  which normalize to BCM, so no function-label gap exists there.
2026-06-12 08:29:03 +02:00