Commit Graph

307 Commits

Author SHA1 Message Date
David Montero Crespo cd3ee6172b copy: rewrite landing hero — drop SPICE jargon, lead with the boards
The previous hero ("Circuits + Code. / One Browser Tab. / SPICE-accurate.")
was optimised for EE engineers searching for circuit simulators. Most
visitors land here looking for an Arduino emulator they can use without
installing anything — the names of the supported boards are a stronger
hook than analog-simulation accuracy.

Restored the older "Arduino, ESP32 & Raspberry Pi. / Right in your
browser." framing and tightened the subtitle to action verbs (Write,
wire, run) plus concrete numbers (19 boards, 48+ parts). Drops:
- "SPICE-accurate" — kept on the dedicated /arduino-emulator,
  /circuit-simulator etc. SEO landing pages where the audience is
  actively looking for it
- "ngspice", "co-simulated", "custom chips in C or Rust" — niche, fit
  better in the features section below
2026-05-05 11:29:45 -03:00
David Montero Crespo 26e0c2be60 feat(components): add mergeComponents API to ComponentRegistry
Forgotten in the prior commit (case-mismatch on Windows tracked the wrong
filename). Adds the public method overlays use to splice extra components
into the picker after default-metadata load. Components with an existing
id are replaced; new ones are appended.
2026-05-05 10:34:43 -03:00
David Montero Crespo 8b14815094 feat(components): pro_only flag + registry merge API + picker gate hook
Three small additions so private overlays can add components gated behind
a paid subscription without forking the picker:

- types/component-metadata.ts: optional pro_only?: boolean field on
  ComponentMetadata. Self-hosters never set it; picker behaves identically.
- services/componentRegistry.ts: new mergeComponents() public method.
  Pro overlay calls this after the default registry has loaded to splice
  in extra components (replacing any with the same id).
- components/ComponentPickerModal.tsx: when a pro_only component is
  clicked, the picker first calls window.__velxio_pro_gate__(component)
  if defined. If the gate returns true, the click is consumed (overlay
  shows an upgrade modal). If absent or returns false, the click passes
  through to onSelectComponent as normal.

Net upstream change: ~25 lines, all backwards-compatible. OSS image
behaves exactly as before since no overlay sets pro_only or installs
the gate.
2026-05-05 10:33:53 -03:00
David Montero Crespo 77b3e86b50 feat(frontend): /pricing route placeholder + UserResponse subscription fields
Two upstream additions to support private overlays implementing paid tiers
without forking client code:

- store/useAuthStore.ts: UserResponse extended with optional
  is_paid_subscriber, subscription_status, subscription_period_end. The
  backend now returns these in /api/auth/me; the persist middleware
  serialises them automatically.
- pages/PricingPlaceholder.tsx (NEW): the /pricing route. Renders a polite
  "this image is fully free" message for self-hosters plus a
  data-velxio-slot="pricing-page" target where private overlays can
  portal-inject a real pricing page.
- App.tsx: register the /pricing route after /about.

Self-hosted OSS image: /pricing shows the placeholder, no behavioural
change anywhere else. Production with a private overlay: /pricing shows
the overlay's full pricing UI.

Frontend build verified.
2026-05-05 10:24:01 -03:00
David Montero Crespo a5b5e57aae feat: add data-velxio-slot markers for overlay portal targets
Three small markers (each one HTML attribute) so private overlays can
portal-inject UI into well-defined places without forking the upstream
component:

- AppHeader user dropdown: data-velxio-slot="user-menu"
  Lets overlays add menu items between "My projects" and "Sign out"
  (e.g. a Privacy / opt-out item).
- AdminPage tab bar: data-velxio-slot="admin-tabs"
  Lets overlays add extra tabs alongside Dashboard / Users / Projects /
  Boards (e.g. a Pro Analytics tab).
- AdminPage tab content area: data-velxio-slot="admin-tab-content"
  Sibling div where overlay tab content can portal-render.

Generic markers, no overlay-specific code in upstream. Anyone with
private extensions can use them. The OSS build is otherwise unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:28:02 -03:00
David Montero Crespo edd2ac32d5 feat: add optional extension hooks for private overlays
Three small, backwards-compatible hooks let anyone with private features
(velxio.dev's analytics, custom integrations, paid tiers, …) layer them
on top of the open-source build without forking files.

Backend (app/main.py):
- After standard router registration, try-import an optional `app.pro`
  module exposing `register_pro(app)`. ImportError is silently swallowed
  (the OSS image doesn't ship `app.pro`, so this is a no-op there).

Frontend:
- EditorToolbar: new optional `rightSlot` prop renders extra elements
  after the built-in right-group buttons (mirrors the existing
  `centerSlot` pattern).
- main.tsx: dynamic `import('@pro/index')` gated by VITE_PRO_BUILD env.
  When unset (OSS build), the branch is dead-code-eliminated and no pro
  chunk is emitted.
- vite.config.ts: `@pro` alias resolves to `src/__pro_stub__/` by default.
  Private builds set `VITE_PRO_BUILD=true` and `PRO_OVERLAY_PATH=<path>`
  to point at their real overlay tree.
- src/__pro_stub__/index.ts: 1-line no-op `mountPro` so TypeScript and
  Vite resolvers stay happy in OSS builds.

Verified: `npm run build:docker` succeeds; `npm test` passes 1161/1162;
the OSS bundle (43 MB) contains zero references to `__pro_stub__`,
`@pro`, or `pro/index` (verified via `grep dist/`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:03:20 -03:00
David Montero 71e90f9b90 fix(compile): bump frontend axios timeout 180s → 600s for ESP-IDF builds
Cold ESP-IDF builds (esp32, esp32-c3, esp32-cam) routinely take 5-10
minutes the first time a project is compiled. The 180s axios timeout
on POST /api/compile/ was cutting the connection long before the
backend finished, surfacing as the misleading 'No response from
server. Is the backend running on port 8001?' error.

Bumping the client timeout to 600s aligns with the nginx
proxy_read_timeout (also 600s) so the chain end-to-end is consistent.

Arduino sketches still compile in seconds — the timeout is an upper
bound, not a delay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:32:24 +02:00
David Montero 78a834700b fix(examples): use esp32-devkit-c-v4 for ePaper examples (GPIO 16/17)
The 4 ESP32 ePaper examples (BW weather, BWR alert, UC8179 dashboard,
ACeP rainbow) wired GxEPD2 to GPIO 16 (RST) and GPIO 17 (DC), which is
the canonical pinout shown in every GxEPD2 example. But those pins are
not broken out on the DevKit V1 variant (PINS_ESP32) — they only exist
on DevKit-C-V4 (PINS_ESP32_DEVKIT_C_V4).

Result: the RST and DC wires fell back to (0,0) and rendered as a red
+ purple line shooting from the corner of the board. CLAUDE.md §6a
documents this exact symptom.

Switching boardType to 'esp32-devkit-c-v4' renders the variant whose
pinInfo includes 16 and 17. Also rename pinName 'GND' → 'GND.1' since
DevKit-C-V4 exposes three GND pins as GND.1/2/3 (not a plain 'GND').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 06:37:20 +02:00
David Montero Crespo eb9a3ec92f chore: stop committing package-lock.json (cross-platform breakage)
A lock file pins platform-specific native binaries — Rollup, esbuild, swc.
A lock generated on Windows brings @rollup/rollup-win32-x64-msvc but no
Linux variant; a lock generated on Linux does the inverse. The Docker
build kept blowing up with MODULE_NOT_FOUND on rollup/dist/native.js
whenever the lock came from a contributor's non-Linux machine.

Trade-off: we lose npm's transitive-version pinning. Mitigated by:
- package.json caret ranges keep majors stable
- Docker image is rebuilt + retagged per release, so a deployed image
  has a frozen dep set regardless of the lock
- Production uses a pinned upstream commit via velxio-prod's submodule,
  not lock-driven repro
- Dependabot still flags vulnerable transitives via package.json scans

Changes:
- .gitignore: ignore package-lock.json everywhere
- .dockerignore: same (defense-in-depth — never enter build context)
- Dockerfile.standalone: keep `rm -f package-lock.json` as a safety net
  for `docker build` runs from trees with a local lock
- frontend-tests.yml: `npm ci` → `npm install` (npm ci requires a lock)
- Delete the two committed locks (frontend/ + root). The test/* and
  vscode-extension/* locks are left as-is — internal tooling, separate
  install paths, not in the Docker build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 01:08:23 -03:00
David Montero Crespo 531c337d19 fix(install): unblock self-hosting + drop forced wokwi clones
Resolves several install pain points reported by users (#108, #120) and
removes the obligatory upstream-clone step that confused contributors and
slowed down every Docker build.

Install fixes:
- nginx: server_name → catch-all default_server, drop Debian's stock site
  so reverse-proxied users no longer get the "Welcome to nginx" page.
- entrypoint: auto-generate SECRET_KEY at first boot, persisted under
  data/.secret_key. backend/.env is now optional in docker-compose.yml.
- backend: add greenlet>=3.0.0 (SQLAlchemy async dep that was missing on
  some Python builds — caused uvicorn startup failures on WSL).

Wokwi libs come from npm:
- @wokwi/elements 1.9.2, avr8js 0.21.0, rp2040js 1.3.2 are pinned in
  frontend/package.json. Vite aliases removed.
- Dockerfile.standalone no longer clones avr8js / rp2040js / wokwi-elements
  / wokwi-boards. Frontend stage is just COPY + npm install + build:docker.
- Board SVGs vendored under frontend/public/boards/ (10 deduped against
  existing files, 2 truly new). third-party/wokwi-* clones become reference-
  only credits — generate-component-metadata.ts skips gracefully when absent.

Production config split out:
- docker-compose.prod.yml, deploy/nginx.prod.conf, nginx-host-velxio*.conf,
  update-third-party.bat removed. Production deployment lives in its own
  repo: https://github.com/velxio/velxio-prod (host nginx + HTTPS + backups
  + pinned upstream commit).

Verified locally: 1161 frontend tests pass, build:docker completes clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 00:04:11 -03:00
David Montero 888cb5b92b fix(autosave): only project owner triggers auto-save
Without an ownership check, viewing someone else's project (admin
inspection, browsing public projects) caused the auto-save hook to
PUT the project on every store change. The backend correctly rejects
non-owner updates with 403, but the frontend surfaced these as
"save fail" to the user — misleading and noisy in logs.

The hook now stays idle unless the authenticated user matches
currentProject.ownerUsername. Manual saves through SaveProjectModal
are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:41:32 +02:00
David Montero Crespo 9cd5061732 refactor: rename wokwi-libs/ → third-party/
The directory grew well beyond Wokwi-only contents: it now hosts
lcgamboa's QEMU fork (qemu-lcgamboa), Espressif's esp32-camera, the
ngspice WASM build, fritzing-parts, picowi, an alternative QEMU
(qemu-esp32), the 100_Days_100_IoT_Projects examples repo, and
Wokwi's own avr8js/rp2040js/wokwi-elements/wokwi-features/wokwi-boards.
"wokwi-libs" was misleading — half the contents have nothing to do
with Wokwi. "third-party/" is the standard convention for vendored
external dependencies.

Mechanical changes:

  Path rename:
    wokwi-libs/ → third-party/
    update-wokwi-libs.bat → update-third-party.bat
    docs/WOKWI_LIBS.md → docs/THIRD_PARTY.md

  Submodule reconfiguration:
    .gitmodules — 4 path= and section names updated
    .git/modules/wokwi-libs/ → .git/modules/third-party/
    each submodule's .git file rewired to ../../.git/modules/third-party/<name>

  Reference updates (~80 files): vite.config.ts aliases, Dockerfile
    COPY paths, GH Actions workflow steps, build_qemu_*.sh, all
    docs/* and test/*/autosearch/* entries that mention the path,
    package-lock.json file: dependencies, .gitignore patterns,
    sitemap.xml + index.html SEO blurbs, scripts/generate-component-*,
    .dockerignore, .idea/vcs.xml. Bulk replaced both `wokwi-libs/`
    (path) and bare `wokwi-libs` (textual mentions in docs/comments).

Verified:
  - npx tsc -b --noEmit produces no new errors related to these paths
  - vite.config.ts aliases now point at ../third-party/avr8js etc.
  - All 4 git submodules (avr8js, rp2040js, wokwi-elements,
    wokwi-features) are linked under third-party/ with their
    worktrees re-populated and config files referencing the new path
  - `grep -r wokwi-libs` returns zero hits outside node_modules,
    .vite, frontend/dist, third-party/ (upstream submodule contents),
    *.pyc caches, and *.dll.pre-camera rollback binaries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:58:57 -03:00
David Montero Crespo 36914e209c feat(webcam): universal compatibility — any webcam on any PC
User goal: ESP32-CAM live preview that works with any webcam,
regardless of resolution, brand, or scene complexity. The previous
fixed-quality 0.28 was fragile (intermittent decode errors on
moving/textured scenes) and capped visual quality unnecessarily.

Two-layer fix; either alone is insufficient:

LAYER A — Bounded JPEG encoder (frontend, this repo)
  frontend/src/hooks/useWebcamFrames.ts:
    encodeBoundedJpeg() walks a quality ladder [0.6, 0.5, ..., 0.1]
    until the JPEG fits in MAX_FRAME_BYTES (23 000). If even q=0.1
    overshoots — extreme HD/4K scenes — falls back to a 240×180
    downscaled canvas at q=0.4. Guarantees every emitted frame fits
    the deliverable budget regardless of webcam hardware.

    The hook now exposes lastQualityUsed + lastDownscaled so UI can
    surface when auto-tuning kicks in.

  frontend/src/components/simulator/CameraToggle.tsx:
    Tooltip shows "(auto-tuned to q=0.X)" or "(auto-downscaled, q=0.X)"
    while streaming so users see what the encoder picked.

LAYER B — Multi-lap descriptor ring walker (qemu-lcgamboa, submodule)
  Bumps the QEMU per-frame deliverable cap from 8 KiB to ~32 KiB by
  letting the walker reset the descriptor ring up to 4 times per
  VSYNC. Submodule pointer bumped to eb8b7a5d.

Combined, the demo now supports:
  - Cheap 480p webcams: q=0.6, 5-10 KiB JPEGs, sharp
  - Logitech mid-range:  q=0.5-0.6, 8-15 KiB JPEGs, sharp
  - HD 1080p webcams:    q=0.4-0.6, 15-23 KiB JPEGs, sharp
  - 4K complex scenes:   downscaled, still readable

Documented as bug closure in:
  test/test-esp32-cam/autosearch/15_universal_webcam_compat.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:48:24 -03:00
David Montero Crespo 2c08e84fc6 fix(useWebcamFrames): JPEG quality 0.35 → 0.28 — intermittent decode fails
User reported "JPG Decompression Failed! Data format error" hitting
intermittently with quality 0.35. Worker log showed actual JPEG
payloads at 7959-8123 bytes per frame — right at the QEMU emulator's
8192-byte deliverable budget (8 EOFs × 1024 bytes from cam_hal's
default 16-descriptor ring).

The webcam JPEG encoder produces variable-size output: simple uniform
scenes compress to ~6 KiB, complex/textured/moving frames bloat to
~9-10 KiB. Anything over 8192 gets truncated mid-Huffman-scan in the
firmware framebuffer, my walker injects FF D9 at byte 8190 to keep
cam_verify_jpeg_eoi happy, but the upstream jpg2rgb565 actually
parses the structure and chokes on the truncated stream.

Quality 0.28 keeps even the worst-case complex frame comfortably
under 8 KiB. Visual quality is still much better than the 0.25
fallback — fine for a 160×120 preview where the user cares about
"is my face there" not "did the JPEG quantization tables converge".

Real long-term fix would be to bump EOFS_PER_FRAME and lift the 8 KiB
ceiling — but that touches the QEMU walker (DLL rebuild cycle) and
risks breaking the descriptor-ring math. Doing this frontend tweak
first to unblock the demo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:32:46 -03:00
David Montero Crespo d4d015c25d perf(spi): batch SPI bytes per WS message — ~50× faster TFT in emulator
User reported the ESP32-CAM + ILI9341 live preview at ~1 frame/min.
Profile: 80×60 preview pushes 9600 SPI bytes per drawRGBBitmap, and
each byte was emitting a full {type:'spi_event'} JSON message over
the worker→backend→WS→frontend pipeline. Per-byte overhead ~150-200µs
in Python (json.dumps + sys.stdout.write+flush dominates) plus
asyncio + WS dispatch. Net: 1.5-2 sec/frame minimum, much worse with
GIL contention.

Fix: buffer MOSI bytes in the worker and emit a single base64-encoded
`spi_batch` message when CS goes HIGH (transaction ended) or the
buffer crosses 4 KiB. ~9600 events/frame collapse to ~3 messages.

  backend/app/services/esp32_worker.py:_on_spi_event
    - Add _spi_byte_buf bytearray + threading.Lock
    - On op==0x00 (byte): append; flush early if buf >= 4096
    - On op==0x01 (CS change): flush buffer, then emit the CS event
      via the legacy spi_event channel (ePaper / custom chips that
      observe CS still get it).

  frontend/src/simulation/Esp32Bridge.ts
    - New 'spi_batch' message handler decodes b64 and replays each
      byte through the existing onSpiByte callback. Parts that
      subscribed via simulator.spi.onByte don't notice the protocol
      change. The 'spi_event' branch still handles CS changes plus
      legacy single-byte payloads for backwards compat.

Now that 38 KB/frame is cheap, restore preview to 160×120 + JPEG
quality 0.35 in the gallery example. Real measured speedup: ~50× on
the QVGA preview demo. Real hardware was never affected — it runs
SPI at 80 MHz and pushes the bitmap in ~4 ms either way.

PSRAM emulation is unrelated to this bottleneck and was left untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:25:06 -03:00
David Montero Crespo a20b10a252 perf(esp32-cam-lcd-preview): 4x faster preview by shrinking SPI traffic
User reported the live preview "looks slow" after the JPEG decode fix.
Diagnosis: each tft.drawRGBBitmap pushes width × height × 2 bytes over
SPI, and every byte takes a full QEMU → worker → backend (WS) → frontend
round-trip. At 160×120 that's 38 400 messages per frame; the bus
saturates at ~0.2 fps perceived.

Two changes shrink the per-frame SPI bandwidth:

1. Preview 160×120 → 80×60 (and JPG_SCALE_2X → JPG_SCALE_4X).
   38 400 bytes/frame → 9 600 bytes/frame. Already 4× faster.

2. Status bar redraw throttled to every 10th frame instead of every
   frame. The text writes (printf, fillRect, fillCircle) account for
   another ~1-2 KB of SPI traffic per loop iteration. Skipping 9 of
   every 10 redraws frees up a chunk more bandwidth without losing
   the headline numbers (fps, frame counter) — they just refresh
   once a second instead of 5x/sec.

Also dropped the trailing `delay(20)` — we don't need an artificial
throttle, the SPI bus is the throttle.

Real-hardware effect: zero. ESP32 SPI runs at 80 MHz; a full
160×120 bitmap pushes in ~4 ms either way.

Applied in two places:
- examples/esp32-cam-lcd-preview/esp32-cam-lcd-preview.ino
- frontend/src/data/examples.ts (in-app gallery copy)

Long-term plan: batch SPI bytes at the worker level (one WS message
per N bytes instead of per byte) — that's a deeper change in
qemu-lcgamboa + Esp32Bridge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:15:06 -03:00
David Montero Crespo c4c446cf39 fix(useWebcamFrames): drop JPEG quality 0.6 → 0.25 for emulator preview
The ESP32-CAM + ILI9341 example was rendering grey-X "decode failed"
rectangles. Serial showed:

    E (53868) esp_jpg_decode: JPG Decompression Failed!
                              Data format error

Root cause: the QEMU emulation delivers up to 8 KiB of JPEG bytes per
frame (8 EOFs × 1024 = 8192) plus a 2-byte FF D9 EOI injection at the
end of that window. Real webcam frames at quality 0.6 are ~11 KiB —
they get truncated mid-Huffman-scan in the firmware framebuffer.
cam_verify_jpeg_eoi accepts the frame (it found FF D9), but the
upstream jpg2rgb565() actually parses the JPEG and rejects the
truncated structure.

Quality 0.25 produces ~3-5 KiB JPEGs that fit the budget entirely.
The decoder finds the natural EOI well before our injection point,
parses cleanly, and renders to the TFT. Visual quality is fine for
an emulator preview — the user is seeing their webcam, not editing
print-quality photos.

Long-term fix is a smarter QEMU walker that ring-wraps to deliver
bigger JPEGs (>16 KiB possible by reusing descriptors mid-frame),
but that's a separate change in qemu-lcgamboa. This frontend tweak
unblocks the demo without another DLL rebuild cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:08:17 -03:00
David Montero Crespo 6d56fe9a90 fix(tests): restore Frontend Tests CI — patch stale RP2040 mocks + install-libraries
CI's Frontend Tests workflow had been failing on master for ~15 runs.
Two pre-existing issues, neither related to the SPI refactor in 8b1433d
or the ESP32-CAM work:

1. RP2040Simulator mock missing attachCyw43 method (23 test files)

   PR #126 (8e769f8 "feat(multi-board): add wire-aware cross-board
   interconnect router", merged 2026-04-25) added a Pico-W-specific
   `sim.attachCyw43(bridge)` call inside addBoard(). The 23 test files
   that mock RP2040Simulator with vi.fn weren't updated; whenever a
   test path created a Pico W board the mock threw "TypeError:
   sim.attachCyw43 is not a function" and aborted addBoard.

   Fix: add `this.attachCyw43 = vi.fn()` to every affected mock.
   Also pre-populate `this.spi = { onByte: null, completeTransfer: vi.fn() }`
   so any future SPI-part tests don't trip on the new generic .spi
   adapter from 8b1433d.

2. install-libraries.test.ts payload mismatch

   PR #135 (b1026ec7 "library-version-uninstall", merged 2026-04-29)
   extended `installLibrary(name)` to `installLibrary(name, version?)`
   and now sends `{name, version: version ?? null}` over the wire.
   The test still asserted `{name}` only and failed.

   Fix: assert `{name, version: null}` for the no-version call.

Verified locally: 1161 passed | 1 skipped (was 1117 passed | 44 failed).

Backend E2E "Run HC-SR04 e2e test" is a separate failure that needs
its own investigation — it downloads QEMU binaries from a release and
runs real firmware compilation, which I can't reproduce on Windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:46:32 -03:00
David Montero Crespo c068612077
Merge pull request #137 from davidmonterocrespo24/esp32-cam
Esp32 cam
2026-05-02 22:40:06 -03:00
David Montero Crespo 8b1433deae refactor(spi): unify SPI bus interface across all simulators
Previous fix added an ESP32-specific code path inside ili9341Simulation
to subscribe to the QEMU worker's spi_event stream. That made the LCD
work on ESP32-CAM but left the underlying issue unsolved: every other
SPI part (custom chips, future SD-card emulators, the SSD168x ePaper
already in the codebase) would also need its own per-board branching.

The right shape: every simulator exposes a `.spi` member matching the
SAME SpiBusLike interface, and SPI parts hook .spi.onByte without
caring which board they're attached to. AVRSimulator already had
this — now everything else does too.

  frontend/src/simulation/SpiBus.ts (new)
    Defines the contract — `onByte: (mosi) => void | null` plus
    optional `completeTransfer(miso)`. Documents the single-listener
    semantics that AVR has had since day one.

  frontend/src/store/useSimulatorStore.ts
    Esp32BridgeShim gets a lazy `.spi` getter that wraps
    bridge.onSpiByte (the per-byte WS event from the QEMU worker).
    completeTransfer is a no-op because the worker drives MISO via
    its own _spi_response global. Covers ESP32 (Xtensa), ESP32-S3,
    ESP32-CAM, ESP32-C3 — every kind that routes through Esp32Bridge.

  frontend/src/simulation/RP2040Simulator.ts
    Adds a lazy `.spi` getter that re-routes rp2040.spi[0].onTransmit
    through the adapter. Default loopback (the prior behaviour) is
    preserved when no part has accessed `.spi` yet — only consumers
    that opt in see their handler invoked. Covers Pico and Pico W.

  frontend/src/simulation/parts/ComplexParts.ts
    ili9341Simulation no longer has an ESP32 special case. Single
    code path: `simulator.spi.onByte = handler`. Works on AVR,
    RP2040, all ESP32 variants. Same pattern is now available to
    every future SPI part — ssd1306, sd-card, oled, etc.

The Esp32Bridge.ts spi_event field-name fix from 6afa62e (msg.data.event
instead of the non-existent msg.data.data) stays in place — that's what
makes the per-byte stream actually arrive in the bridge.

Verified: ILI9341 + ESP32-CAM gallery example renders the live webcam
preview after a hard refresh. The same simulation code works on Arduino
Uno + ILI9341 (the existing ili9341-test-sketch in example_zip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:36:29 -03:00
David Montero Crespo 6afa62ea17 fix(ili9341): add ESP32 SPI byte routing so the LCD renders on ESP32-CAM
The ILI9341 part simulation only hooked AVR's SPI peripheral. For
ESP32 the simulator is Esp32BridgeShim (no .spi member), so
attachEvents bailed early and the LCD stayed black even though the
firmware was driving SPI traffic correctly.

The QEMU worker already emits per-byte spi_event WS messages
(see backend/app/services/esp32_worker.py::_on_spi_event), and the
Esp32Bridge already had an onSpiEvent hook — but the bridge was
reading msg.data.data (a non-existent field) instead of decoding
the worker's {bus, event, response} format. Fixed.

Two changes:

1. Esp32Bridge.ts: decode the spi_event payload correctly. The
   worker encodes byte transfers as `mosi << 8` (op = low byte = 0x00)
   and CS-line changes as `((cs<<1)|level) << 8 | 0x01` (op == 0x01).
   Added onSpiByte (per-byte) and onSpiCsChange callbacks alongside
   the existing onSpiEvent for backwards compat.

2. ComplexParts.ts ili9341Simulation: detect Esp32BridgeShim via
   `getBridge()` duck-type check. When present, subscribe to
   bridge.onSpiByte and feed bytes into the same processCommand /
   processData pipeline used by the AVR path. DC tracking via
   pinManager.onPinChange already works for ESP32 because the bridge
   fires triggerPinChange on every gpio_change WS event.

Verified end-to-end: ESP32-CAM + ILI9341 example in the gallery now
renders the live webcam preview to the simulated TFT (160×120 RGB565
centered in the 320×240 panel) at ~3-4 fps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:31:24 -03:00
David Montero Crespo 155b962c21 feat(gallery): add 2 ESP32-CAM examples to the in-app gallery
Adds two listed examples to the gallery (book icon → Examples) so
users can one-click load the new ESP32-CAM emulation:

1. ESP32-CAM: Webcam Demo (sensors / beginner)
   Minimal sketch — init OV2640, verify SCCB chip-id, loop on
   esp_camera_fb_get() printing frame metadata to Serial. Proves
   the emulation is alive without any external components.

2. ESP32-CAM + ILI9341 Live Preview (displays / intermediate)
   Full demo — decode JPEG with jpg2rgb565() (built-in to
   esp32-camera/conversions, header exposed by the Velxio compile
   template) and render the resulting RGB565 bitmap to a 320×240
   SPI TFT. Pre-wired diagram: ILI9341 connected via VSPI to GPIOs
   12-15 (the only block free after OV2640 takes over the rest of
   the AI-Thinker pins).

Type changes:
- ExampleProject.boardType union extended with 'esp32-cam'
- BOARD_TABS in ExamplesGallery.tsx gets a new "ESP32-CAM" tab
  (orange #d35400)

Both examples use boardFilter: 'esp32-cam' so they show under
the new tab and not the generic ESP32 one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:21:19 -03:00
David Montero Crespo e73c1d341c feat: ESP32-CAM emulation with webcam frame bridge
First open-source end-to-end emulation of the AI-Thinker ESP32-CAM
in QEMU, paired with a browser webcam → firmware bridge so users can
develop camera sketches without hardware. Status: esp_camera_init()
returns ESP_OK; OV2640 chip-id verifies (PID/VER/MIDH/MIDL exactly
match the datasheet); GPIO 25 VSYNC NEGEDGE interrupt enabled by
the upstream driver. Final piece (cam_task accepting frames) is in
progress — descriptor walker fix landed in this commit.

Backend (Python/FastAPI):
- simulation.py: camera_attach/frame/detach WS handlers
- esp32_worker.py: ctypes binding to velxio_push_camera_frame +
  feature-detection fallback for older DLLs
- esp32_lib_manager.py: forward camera commands to the worker stdin
- esp-idf-template/main/CMakeLists.txt: esp32-camera headers added
  via add_prebuilt_library + REQUIRES driver (resolves i2c_master_*
  symbols). LED_BUILTIN=2 fallback for sketches that hardcode it.

Frontend (React/TS):
- EditorToolbar.tsx: ESP32-CAM (and the rest of the ESP32 family)
  added to isQemuBoard list — Run button now starts the QEMU bridge
  for these boards instead of falling through to the AVR path
- useWebcamFrames.ts: getUserMedia → OffscreenCanvas →
  toBlob('image/jpeg') → base64 → WS at ~10 fps
- CameraToggle.tsx: header button with status colors + frame counter
- SimulatorCanvas.tsx: render CameraToggle for esp32-cam boards
- Esp32Bridge.ts: sendCameraAttach/Frame/Detach + chunked btoa
- useSimulatorStore.ts: diagnostic log on compileBoardProgram
- components-metadata.json: regen including esp32-cam component

Submodule pointer:
- wokwi-libs/qemu-lcgamboa → ff8eee0 (camera devices commit on
  davidmonterocrespo24/qemu-lcgamboa branch picsimlab-esp32)

Investigation + tests in test/test-esp32-cam/:
- 13 autosearch markdown docs (overview, SOTA, OV2640 spec, DVP/I2S
  spec, build blueprint, blockers resolved, descriptor walker fix)
- 5 sketches (camera_init, sccb_probe, dma_smoke, frame_roundtrip,
  webcam_demo) + 8 live + WS regression tests
- README with the user-facing flow

.gitignore:
- libqemu-*.dll.{pre-camera,new,bak} (rollback points, regenerated)
- wokwi-libs/esp32-camera/ (clone consumed by arduino-esp32 path,
  not part of this repo)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:29:01 -03:00
David Montero Crespo 81f3563b9f
Merge pull request #127 from naweiss/fix/oscilloscope
Fix Oscilloscope
2026-05-02 19:15:34 -03:00
ZhadowValker cc43a956ba feat: Add library version management and uninstall functionality
Backend:
- Add version field to InstallLibraryRequest
- Add fallback and requested_version to InstallResponse
- Add DELETE /api/libraries/uninstall endpoint
- Enhance install_library() for versioned installs (LibName@version)
- Add semver validation and fallback logic
- Add uninstall_library() method
- Fix _parse_version() to reject non-numeric version parts

Frontend:
- Update installLibrary() with optional version parameter
- Add uninstallLibrary() and resolveLibraryVersion() helpers
- Add version selector dropdown in Library Manager
- Add UNINSTALL button for installed libraries
- Show fallback messages when requested version unavailable
- Add parseLibSpec() and version badges in InstallLibrariesModal
2026-05-02 12:54:09 +05:30
David Montero Crespo 6a88375bc0 feat: persist multi-board projects + add auto-save
The project save/load pipeline only persisted a single `board_type`, so
multi-board workspaces silently lost every board except the active one
on save, and wires referencing the dropped boards' IDs orphaned to the
canvas corner on reload. An audit of the production backup found 74/306
projects (24%) with at least one orphaned wire and 174/301 non-trivial
projects whose code was still the default Blink template — strong signal
that users save once and never re-save.

Backend
- Add `boards_json` column on `projects` with idempotent ALTER TABLE in
  the lifespan migration list.
- New `FileGroup` schema + `file_groups` array on
  ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat.
- `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via
  `read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on
  read; legacy single-list `files` only updates the active group, leaving
  other boards' files intact.
- `_persist_files_from_body` honors file_groups → files → code priority.

Frontend
- `useSimulatorStore.addBoard` accepts an optional `explicitId` so
  saved board IDs can be restored verbatim (wires reference IDs literally).
- New `loadProjectState({boards, fileGroups, components, wires,
  activeBoardId})` action: tears down current boards, recreates from the
  payload, restores file groups atomically, recalculates wire positions
  on the next frame, and refreshes the Interconnect.
- `useEditorStore.replaceFileGroups` for atomic multi-group restore.
- `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through
  `buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects
  by synthesising a default board from `board_type`).

Auto-save (#useAutoSaveProject hook)
- 2.5s debounced silent PUT triggered ONLY when an authenticated user
  has a `currentProject` with a UUID. State hash detects real changes
  vs. UI-only churn; baseline is reset on project load so the just-loaded
  state isn't immediately re-saved.
- `beforeunload` flush via `fetch keepalive: true` (supports PUT +
  credentials, survives unload).
- Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error).

Backfill script (one-off, idempotent)
- `backend/scripts/backfill_boards_2026_05.py` populates `boards_json`
  for legacy projects. Heuristic per project, based on which board IDs
  the wires reference:
    Case A — wires only ref 'arduino-uno' but board_type ≠ uno:
             rename id→board_type and rewrite wire endpoints.
    Case B — single-board normal: keep verbatim.
    Case C — multi-board: recreate one board per distinct ref, infer
             kind by stripping trailing -N suffix.
  Also moves any flat files into the active board's group subdir.
  Stdlib-only, runs from host or `docker exec`.

Docker
- `Dockerfile.standalone` now copies `backend/scripts/` into the image
  so the backfill is callable via `docker exec velxio-app python
  /app/scripts/backfill_boards_2026_05.py --apply`.

Verified locally on the restored production backup (363 projects):
33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans.
Re-running the script after apply skips all 363 (idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:43:33 -03:00
David Montero Crespo a9b48a3cc6 feat: implement EditorPage with file explorer and simulator canvas components 2026-04-30 15:27:58 -03:00
naweiss ec730fb463 Bug fix: missing boardId in getOscilloscopeCallback 2026-04-30 08:28:17 +03:00
David Montero Crespo 2e4c470f75 feat: add support for UC8159c (ACeP 7-colour) display
- Implement UC8159cDecoder for handling 7-colour ACeP panels.
- Introduce painting functions for UC8159c frames in EPaperPart.
- Update EPaperPart to handle both SSD168x and UC8159c frame types.
- Add integration tests for EPaperPart and UC8159cDecoder.
- Create example sketch for 5.65" ACeP 7-colour panel.
- Enhance error handling in test cases for library dependencies.
2026-04-30 00:27:40 -03:00
David Montero 2dd152e383 SEO improvements 2026-04-30 00:27:39 -03:00
David Montero Crespo bcc7129aa3 feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-29 22:23:00 -03:00
David Montero Crespo 175b248108 feat(epaper): Add SVG layouts and emulation plan for ePaper panels
- Introduced SVG layout dimensions for Phase 1 (B/W mono) and Phase 2 (colour) ePaper panels, detailing active areas, bezels, and pin layouts.
- Developed a phased emulation plan outlining the architecture and deliverables for different panel types, including SSD168x and UC81xx.
- Created a canonical "Hello, World!" sketch for the 1.54" ePaper panel, ensuring compatibility across ESP32, Raspberry Pi Pico, and Arduino Uno.
- Implemented a pure Python SSD168x decoder to validate SPI command sets and framebuffers against specifications.
- Added tests for compiling the hello-world sketch across supported boards and for the SSD168x protocol to ensure correct framebuffer behavior.
2026-04-29 02:33:59 -03:00
David Montero Crespo 641ac8c1de Add comprehensive tests for Cyw43Emulator functionality and lifecycle
- Implemented handshake tests to validate initial bus state and register responses.
- Created end-to-end tests for Pico W LED blinking using MicroPython firmware.
- Added SDPCM framing tests to ensure proper encoding and decoding of control frames.
- Developed IOCTL tests to verify command responses and state changes in the emulator.
- Established a full lifecycle test for WiFi operations, including scanning, connecting, and packet handling.
- Introduced TypeScript configuration for test files to ensure compatibility and strict type checking.
2026-04-29 00:21:26 -03:00
David Montero Crespo b39041ca4c feat(editor): enhance toolbar layout with center slot for file tabs and improve responsiveness 2026-04-28 23:23:20 -03:00
David Montero Crespo 7a26d01965 feat(attiny85): add Web Component for ATtiny85 with pinInfo support 2026-04-28 22:57:50 -03:00
David Montero Crespo 0a66c70512 feat(examples): update dual Pico example to demonstrate bidirectional digital handshake 2026-04-28 22:38:54 -03:00
David Montero Crespo 2ac94aed24 fix(loadExample): update filename logic for Arduino-style boards to ensure correct file extension 2026-04-28 20:40:48 -03:00
David Montero Crespo 7f2014bef7 Add ESP32 chip demos and comprehensive tests for I2C, SPI, and UART interactions
- Implemented `esp32_spi_chip_demo.ino` to demonstrate SPI communication with a 74HC595 shift register.
- Created `esp32_uart_chip_demo.ino` for UART loopback testing with ROT13 transformation.
- Added Python tests for compiling chips and sketches, ensuring valid WASM output and successful compilation for various board families.
- Developed end-to-end tests for ESP32 with custom chips using I2C and SPI, validating synchronous communication through the backend.
- Introduced GPIO bridge tests to verify serial communication and GPIO state changes.
- Ensured all tests validate the expected behavior of the custom chips and their interaction with the ESP32 firmware.
2026-04-28 19:24:39 -03:00
David Montero Crespo fa170a082a Add shared validators and test configurations for Velxio projects
https://github.com/kritishmohapatra/100_Days_100_IoT_Projects
- Introduced `_lib.py` containing shared validators for board support and static source analysis for MicroPython projects.
- Added `conftest.py` to configure pytest for the test suite, simplifying import paths.
- Created `NOT_SUPPORTED.md` files for two projects indicating they cannot be emulated in Velxio due to lack of source code.
- Implemented unit tests for the unsupported projects to verify the presence of the NOT_SUPPORTED marker and source preservation.
2026-04-28 00:36:05 -03:00
David Montero Crespo 63896e2049 feat(activity): add user daily activity metrics and modal for detailed project interaction 2026-04-26 19:39:45 -03:00
David Montero Crespo 8e769f8a4e feat(multi-board): add wire-aware cross-board interconnect router
Fixes the user-reported bug where two RPi Pico W boards wired GP0↔GP1
running SerialPassthrough don't communicate. Replaces the broken
broadcast-style cross-board logic in addBoard (only routed AVR↔Pi3B,
ignored wires entirely, no RP2040↔anything path) with a wire-aware
Interconnect singleton.

Architecture: digital pin transitions are the lowest-common-denominator
abstraction. Each simulator's hardware peripherals (UART/I2C/SPI) and
bit-banging libraries (SoftwareSerial, software I2C) decode the
transitions naturally — propagate the pin and the protocols come for
free. For cross-process boards (ESP32 backend QEMU, Pi3B QEMU) a
byte-level shortcut is additionally enabled on hardware-UART pin
pairs to handle high-baud links over WebSocket latency.

Implementation:
- New simulation/Interconnect.ts singleton subscribes to wire/board
  changes via the Zustand store. Handlers per tier: browser-sim →
  pinManager.onPinChange, ESP32 → Esp32Bridge.sendPinEvent, Pi3B →
  bridge.sendPinEvent. Re-entrancy guard via per-(board,pin) Set.
- New utils/boardProtocols.ts classifies pins (uart-tx, i2c-sda, etc.)
  per board kind, used as optimization hint for the byte shortcut.
- types/wire.ts: added signalType field, exports WireSignalType /
  WireColorMap (fixes a pre-existing TS import error in wireColors).
- Deleted the bridgeMap/simulatorMap broadcast forEach blocks in
  addBoard. Initial board + future boards register with Interconnect
  via setInterconnectRuntime + store subscription.
- PinManager.resetPinStates() helper for test isolation.

Tests (16 new files, 96 tests, all passing):
- Per-pair × per-protocol matrix: dual-arduino-digital,
  dual-pico-digital, arduino-pico-digital, triple-pico-digital-chain,
  dual-arduino-hw-uart, dual-arduino-software-serial,
  arduino-pico-mixed-uart, arduino-esp32-uart, dual-esp32-uart,
  pi3-pico-uart, arduino-pico-i2c, arduino-arduino-spi,
  interconnect-routing, dual-arduino-multi-protocol (UART+I2C+SPI+
  digital + concurrent), dual-pico-multi-protocol (UART0+UART1 alt+
  I2C0+I2C1+SPI0+digital + 3-Pico star topology)
- Updated dual-pico-serial-passthrough to assert correct behaviour
- Backend test/multi_board_esp32/test_dual_esp32_serial.py for two
  real QEMU instances (skip-graceful when lcgamboa lib absent)

Verified: 1107/1107 tests pass, zero regressions, vite build OK.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 19:47:40 -03:00
David Montero Crespo 5bf3a3d5ed feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.

Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
  event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
  signup_country, last_country) and Project (compile/run/update counts,
  last_compiled/run timestamps) kept in sync by MetricsService for O(1)
  dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
  boards, board-diversity, top-users, top-projects, countries,
  users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs

Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 19:47:40 -03:00
davidmonterocrespo24 4d17dfd832 feat: add /v2-5 release landing page for Velxio 2.5 (SPICE)
Mirrors the /v2 page structure but targets the 2.5 launch: ngspice-WASM
analog simulation, hybrid digital + analog co-simulation with Arduino /
ESP32 / RP2040, expanded component catalog, live instruments, 40 new
analog/hybrid examples.

- Reuses Velxio2Page.css + SEOPage.css — no new stylesheet to maintain
- Adds SoftwareApplication, BreadcrumbList, and FAQPage JSON-LD for
  rich-results eligibility
- Registers the route in App, entry-server (SSR prerender), and
  seoRoutes (sitemap, priority 0.95 / changefreq weekly)
2026-04-24 16:42:47 +02:00
davidmonterocrespo24 7e026179aa feat(photodiode): add lux control in sensor panel and property dialog
The SPICE emitter already reads properties.lux (default 500, 100 nA/lux)
but the UI had no way to set it — the static dialog rejected the "range"
control type and there was no entry in SENSOR_CONTROLS for the live panel.

- Add photodiode entry in SENSOR_CONTROLS (slider 0-1000 lux)
- Register a minimal PartSimulationRegistry handler that forwards slider
  values via emitPropertyChange so the netlist memo invalidates
- Switch the photodiode lux control from "range" to "number" so the
  static ComponentPropertyDialog renders an editable input
2026-04-24 16:42:36 +02:00
davidmonterocrespo24 4708d54195 fix(examples): re-create board when loading an Arduino example after an analog one
Loading an analog-only example removes every board. The single-board
branch of loadExample then called setBoardType, which only maps over
existing entries in boards[] and silently did nothing when the array
was empty — components rendered but no board. Fall back to addBoard +
setActiveBoardId when there are no boards.
2026-04-23 05:22:35 +02:00
David Montero Crespo 6ca0f91074 refactor: make 'generatedAt' optional in component metadata and remove timestamp generation to prevent CI drift 2026-04-21 17:35:13 -03:00
David Montero Crespo ff918375d7 test: increase timeout for blink sketch compilation test 2026-04-21 17:23:36 -03:00
David Montero Crespo 692af00258 style: update ESLint rules and clean up code formatting across multiple files 2026-04-21 17:14:27 -03:00
David Montero Crespo 53efc226a3 Merge branch 'master' into feature/electrical-simulation-ngspice
# Conflicts:
#	deploy/entrypoint.sh
#	frontend/src/components/examples/ExamplesGallery.tsx
2026-04-21 17:11:26 -03:00
David Montero Crespo 212ecd1bcb refactor: rename components and update prefixes to 'velxio-' for consistency
- Modified the index file to reflect the new naming convention for Velxio components.
- Changed JSX declarations to use 'velxio-' prefix for various components.
- Updated component overrides to replace 'wokwi-' with 'velxio-' for logic gates and other components.
- Adjusted SVG generation script to use 'velxio-' prefix for BMP280 and Raspberry Pi components.
- Marked submodules as dirty in QEMU and RP2040 libraries.
- Added .prettierignore and .prettierrc.json for consistent code formatting.
- Introduced InstrumentComponent with support for Voltmeter and Ammeter, including pin information handling.
2026-04-21 16:45:45 -03:00