Local-first visual environment for creating, programming, and behaviorally simulating embedded electronics projects.
The project already includes a functional web prototype with a visual board, official component catalog, CodeMirror editor, Arduino-compatible WASM firmware runtime, local Clang diagnostics, Serial TX/RX, simulated ESP32/ESP8266 Wi-Fi, analog inputs through WASM, initial I2C/SPI support for sensors and ADCs, virtual HTTP, virtual/real MQTT, an incremental electrical solver, multi-board simulation, and complete examples for HC-SR04 + LED, FC-37, LDR, BMP280, external ADCs, and water-pump control through MQTT.
The current architecture treats components as packages: each official component owns its manifest and can load CSS, simulation behavior, firmware libraries, C++ shims, and WASM imports from its own folder.
Virtual Embedded Lab is currently in public alpha. Some components and embedded APIs may have partial support.
- Try online at temporary URL: https://virtual-lab.mathmpr.com;
- Report a bug into the issue tracker;
- If you want to contribute, read CONTRIBUTING.md and submit a pull request;
- Request a new component via issues also;
- If you can, sponsor the project through GitHub Sponsors;
- Current version is:
0.1.0-alpha.1(pre-release); - Questions? Send an email to
matheusprador@gmail.com;
Drag.and.compile.an.existing.project.webm
Creating.a.new.project.and.run.it.webm
This project is open source under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
In practical terms:
- You may use, study, modify, and redistribute the code.
- If you modify the project and offer that modified version as a network service, you must make the corresponding source code available to the users of that service, as required by the AGPLv3.
- The project is provided without warranties. Simulations are educational tools and do not replace physical validation, real measurements, or proper safety practices.
The name "Virtual Embedded Lab", the logo, visual identity, and confusingly similar marks are not granted for uses that imply that an unofficial version, fork, hosted instance, or distribution is the official project.
You may use the name for truthful attribution, reference to the original project, compatibility statements, and license compliance. When redistributing or hosting a modified version, make it clear that it is unofficial unless the maintainers explicitly authorize otherwise.
Contributions are welcome. Read CONTRIBUTING.md before submitting changes.
By contributing, you agree that your contribution is distributed under the same project license: AGPL-3.0-or-later.
Required:
- Node.js 24 or newer.
- Project dependencies installed with
npm install. clang++available inPATH, or configured throughCLANGXX/CLANG_PATH.lld/wasm-ldavailable in the Clang toolchain.
Optional, depending on the scenario:
- Docker or Podman to isolate WASM firmware compilation in public deployments.
- A network-accessible MQTT TCP broker when a project uses
network.mqtt.mode: "real". - For the
ESP Water Control Pump Reservoirexample, the real flow depends on the MQTT/backend contract from the external projecthttps://github.com/mathmpr/water-control.
The UI uses WASM as the only firmware execution path. Therefore, clang++ and wasm-ld are required to run firmware simulations locally. Without clang++, the server returns CLANG_UNAVAILABLE. Without wasm-ld, the /api/firmware/compile-wasm job finishes with WASM_TOOLCHAIN_UNAVAILABLE.
For public use, do not compile firmware directly on the host without isolation. The WASM compiler supports container sandboxing with WASM_COMPILER_SANDBOX=docker or WASM_COMPILER_SANDBOX=podman. The production instance can also use an external sandbox runner through WASM_COMPILER_SANDBOX=external.
npm installUbuntu/Debian:
sudo apt update
sudo apt install clang lldFedora:
sudo dnf install clang lldArch Linux:
sudo pacman -S clang lldmacOS with Homebrew:
brew install llvmOn macOS, if the Homebrew LLVM clang++ is not in PATH, configure CLANGXX:
export CLANGXX="$(brew --prefix llvm)/bin/clang++"Windows:
- Install LLVM through the official installer or through the package manager used by your environment.
- Make sure
clang++.exeandwasm-ld.exeare inPATH. - Alternatively, set
CLANGXXto the executable path.
Example:
$env:CLANGXX="C:\Program Files\LLVM\bin\clang++.exe"clang++ --version
wasm-ld --versionOr, if you use an environment variable:
$CLANGXX --version
wasm-ld --versionnpm ci
npm run devThe application starts at http://127.0.0.1:4173 by default.
By default, local development calls clang++ directly on the host. For public deployments, configure a container runtime:
export WASM_COMPILER_SANDBOX=docker
export WASM_COMPILER_IMAGE=virtual-embedded-lab-wasm-toolchain:latestPodman is also supported:
export WASM_COMPILER_SANDBOX=podman
export WASM_COMPILER_IMAGE=virtual-embedded-lab-wasm-toolchain:latestThe container runner is called with networking disabled, CPU limits, memory limits, and process limits. Optional variables:
WASM_COMPILER_CONTAINER_RUNTIME: overrides thedocker/podmanbinary.WASM_COMPILER_CPUS: default1.WASM_COMPILER_MEMORY: default256m.WASM_COMPILER_PIDS_LIMIT: default64.
For hardened server deployments, the external runner mode can delegate compilation to a separate unprivileged user:
export WASM_COMPILER_SANDBOX=external
export WASM_COMPILER_SANDBOX_RUNNER=/usr/bin/sudo
export WASM_COMPILER_SANDBOX_RUNNER_ARGS='["-n","/usr/local/sbin/virtual-lab-wasm-compile"]'The public server should also configure:
export MAX_FIRMWARE_SOURCE_BYTES=30720
export COMPILE_RATE_LIMIT_WINDOW_MS=60000
export COMPILE_RATE_LIMIT_MAX=8npm testThe tests use the native Node.js 24 runner with --experimental-transform-types.
- The UI loads official components through
GET /api/components. - Examples live in
examples/**/project.jsonand are loaded through the Examples modal. - Official example C/C++ firmware lives in
examples/<slug>/firmware/*.inoand is referenced by path inproject.json; the examples API resolves those files before returning the project to the UI. - The current default example is
examples/esp32-s3-snake-hub75/project.json. - WASM examples cover HC-SR04, Arduino Serial LED, Serial bridge multi-board, pull-up button, buzzer beep, BBC micro:bit V2 Heart, ESP32 AC Energy Meter POC, ESP32-S3 HUB75 Snake Game, ESP32-C3 LED Blink, ESP32 counter blink, ESP32 Wi-Fi Signal, ESP32 Wi-Fi Failover, ESP32 HTTP/TCP, ESP8266 MQTT water pump, FC-37 Rain Digital, LDR Light Analog, BMP280 Weather I2C, ADS1015/ADS1115 Single Ended, MCP3008 Single Ended, and ESP Water Control Pump Reservoir.
- Additional maker examples cover analog inputs with potentiometer/LM35/soil moisture, digital sensors with PIR, TTP223, tilt, SW-420, Hall, reed, IR obstacle sensor and slide switch, a switching gallery with BJTs, MOSFETs, relays, SSRs, PC817 and ULN drivers, and an ESP8266 example where an LDR controls an RGB LED through PWM.
- Official components live in
components/official/**/component.json. - Components may declare local contributions in
ui/,simulation/, andfirmware/; the core loads those files throughcontributions.styles,contributions.simulationBehaviors, andcontributions.wasmImports. - New official components must follow
docs/official-component-guidelines.md,docs/component-description.md,docs/component-contract.md, and the templateadd-components/new-component-example.mdbefore implementation. - The official catalog includes Arduino UNO, Arduino Nano, BBC micro:bit V2, ESP32 DevKitC V4, ESP32-C3 DevKit, ESP32-S3 DevKit, ESP8266 NodeMCU, HC-SR04, FC-37 Rain Sensor, LDR Light Sensor, BMP280, ADS1015, ADS1115, MCP3008, ZMPT101B, SCT, LM35, PIR HC-SR501, TTP223, tilt switch, SW-420, A3144 Hall, reed switch, IR obstacle sensor, potentiometer, trimpot, capacitive soil sensor, maker BJTs, maker N-channel MOSFETs, MOSFET modules, electromechanical relays, solid-state relays, PC817, ULN2003A, ULN2803A, AC Mains Environment, AC Load, P5 RGB HUB75 64x32, 5V DC supply, 74AHCT245, pull-up button, buzzer, distance environment, Rain Environment, Light Environment, Climate Environment, Analog Voltage Source, Wi-Fi Signal, water pump, solid-state relay, water reservoir, resistors, capacitors, red/green/blue/yellow LEDs, and common-cathode RGB LED.
- Arduino UNO exposes the built-in
LLED on D13/LED_BUILTIN; ESP32 DevKitC V4 exposesPWRand programmableLDon GPIO2/LED_BUILTIN. - Built-in LED blink sketches run continuously until Pause/Reset, respect
delay()through virtual time, and animate thedigitalWritetimeline; undeclaredLED_PIN/PINidentifiers are treated as aliases forLED_BUILTIN. - The board supports pan/zoom, drag-and-drop, colored wires, wire/component removal, in-memory Undo/Redo, and JSON import/export.
- The bottom panel has Code, Console, Serial, and Problems tabs, with main-view switching.
- The signal monitor is attached to the inspector.
- The current solver covers simple LED/resistor series paths, current, power, overcurrent, excessive resistance, insufficient voltage, and basic shorts.
- The UI executes firmware through the WASM path; WASM compilation failures block simulation and display diagnostics, with no IR fallback.
- The JavaScript IR still exists as legacy/test code, but is deprecated as a firmware execution path, isolated in
legacy-ir-simulation.js, and should not receive new features. - The server has an in-memory WASM compilation queue.
POST /api/firmware/compile-wasmcreates a job and returns202with ajobId;GET /api/firmware/compile-wasm/:jobIdreturns the status and, once finished, the compiled WASM. At most two compilations run simultaneously. - The Run button shows a loader, stays disabled while compiling, and keeps the animation visible for at least 3 seconds to avoid flicker on very fast compilations.
- Initial public sharing does not use accounts or a database.
POST /api/shared-projectscreates an opaque 32-character identifier, saves the project toshared/<id>/project.json, and updates the UI URL to/<id>without a refresh.PUT /api/shared-projects/:idupdates the same project, andGET /api/shared-projects/:idloads the public project. - Successful WASM builds are cached in memory by a hash of the code, constants, and toolchain/sandbox configuration.
- ESP32/ESP8266/Wi-Fi support covers
WiFi.mode,WiFi.begin,WiFi.status,WiFi.softAP,WiFi.scanNetworks,WiFi.RSSI,WiFi.RSSI(ssid),WiFi.internetAvailable(), virtualWiFiClientTCP/HTTP, and virtual or realAsyncMqttClientMQTT through WASM imports connected toArduinoRuntime. - Multi-board projects can keep separate firmware per microcontroller; the firmware selector changes the active code in the editor and the runtime executes one WASM session per board.
- BBC micro:bit V2 exposes the main edge connector and a simulated 5x5 built-in LED matrix controlled by
digitalWritein the heart example. ESP32 AC Energy Meter POCmodels two residential phases with ZMPT101B, SCT, ADS1115, and A-N/B-N/A-B loads, calculating Vrms, Irms, W, VA, power factor, and kWh in WASM firmware.ESP32-C3 LED Blinkmodels an ESP32-C3 DevKit driving an external red LED through GPIO4 with a current-limiting resistor.ESP32-S3 HUB75 Snake Gamemodels a P5 RGB HUB75 64x32 panel, four pull-up direction buttons, start/pause button, buzzer, 5V/10A power supply, and 74AHCT245. The firmware draws the snake in a 32x32 left area and score/level on the right through a simulated RGB framebuffer.ESP Water Control Pump Reservoirmodels ESP32 sender, ESP8266 asker, SSR, pump, and reservoir. It uses real MQTT and expects topics/payloads/tokens compatible withhttps://github.com/mathmpr/water-control.- FC-37 support covers digital reading through
digitalReadonDO, driven by the standalone Rain Environment without resetting virtual time when rain changes. - LDR support covers
analogRead(A0)through a voltage divider with a resistor, driven by the standalone Light Environment without resetting virtual time when light changes. - BMP280 support covers
Wire.begin()and a minimalBMP280shim class, registered by I2C address and driven by the standalone Climate Environment without resetting virtual time when temperature/pressure change. - External ADC support covers
ADS1015,ADS1115, andMCP3008through minimal shim classes, driven by Analog Voltage Source without resetting virtual time when voltage changes.
docs/component-description.md: explains how a component is packaged, including manifest, visual model, contributions, firmware, shims, and behaviors.docs/component-contract.md: defines the minimum contract validated by tests.docs/official-component-guidelines.md: defines architectural rules to avoid new coupling in the core.add-components/new-component-example.md: template for planning and accepting a new official component.
- Electron integration is not included yet.
- The solver is not a general nodal/SPICE solver.
- Environment connections are still drawn as regular visual wires, although they are serialized separately.
- Manifest-based pin mapping exists for main scenarios, but does not yet cover the full ESP32/ESP8266 electrical/peripheral model.
- Firmware WASM covers a subset of Arduino/C++; APIs outside the shim block simulation until implemented in the WASM path.
WiFiClientuses virtual HTTP through a separate adapter, with routes declared innetwork.http;AsyncMqttClientuses a virtual MQTT broker declared innetwork.mqttor a real MQTT broker through the backend bridge whennetwork.mqtt.modeis"real". Real cryptographic TLS/HTTPS, MQTT authentication, and persistent broker sessions are not part of the deterministic simulator yet.- I2C/SPI support is still initial:
Wire/SPIexist as small subsets for devices registered by the runtime; there is no full raw bus model and no complete Adafruit/MCP library support. - FC-37 exposes
AOin the manifest, but analog FC-37 reading is outside the initial delivery. - The water-tank example is not self-contained in real mode: it depends on a broker/backend compatible with
https://github.com/mathmpr/water-control, validasker/sendertokens, and a network-accessible MQTT TCP broker. - Browser-side compilation fallback was evaluated and is not part of the public MVP; the recommended path is a server with isolated
clang++/wasm-ld. - Undo/Redo only exists during the current session.
- The signal monitor is contextual/illustrative and does not render a real time waveform yet.