feat: enhance ESP32 emulation with QEMU integration and update documentation
This commit is contained in:
parent
d2e7dc2f34
commit
a99a9d512f
|
|
@ -1,3 +1,29 @@
|
|||
# ---- Stage 0: Compile lcgamboa QEMU as Linux .so (full ESP32 GPIO emulation) ----
|
||||
FROM ubuntu:22.04 AS qemu-builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git python3 python3-pip python3-setuptools \
|
||||
ninja-build pkg-config flex bison \
|
||||
gcc g++ make ca-certificates \
|
||||
libglib2.0-dev libgcrypt20-dev libslirp-dev \
|
||||
libpixman-1-dev libfdt-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# QEMU 8.x requires meson >= 1.0
|
||||
RUN pip3 install meson
|
||||
|
||||
# Clone lcgamboa fork (picsimlab-esp32 branch adds GPIO/ADC/UART/RMT C callback API)
|
||||
RUN git clone --depth=1 --branch picsimlab-esp32 \
|
||||
https://github.com/lcgamboa/qemu /qemu-lcgamboa
|
||||
|
||||
WORKDIR /qemu-lcgamboa
|
||||
|
||||
# Build libqemu-xtensa.so (ESP32/S3) and libqemu-riscv32.so (ESP32-C3).
|
||||
# The script: configures with --extra-cflags=-fPIC, builds normally, then
|
||||
# re-links without softmmu_main.c.o and with -shared.
|
||||
RUN bash build_libqemu-esp32.sh
|
||||
|
||||
|
||||
# ---- Stage 1: Build frontend and wokwi-libs ----
|
||||
FROM node:20 AS frontend-builder
|
||||
|
||||
|
|
@ -33,11 +59,15 @@ RUN npm install && npm run build:docker
|
|||
# ---- Stage 2: Final Production Image ----
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Install system dependencies and nginx
|
||||
# Install system dependencies, nginx, and QEMU .so runtime libraries
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
nginx \
|
||||
libglib2.0-0 \
|
||||
libgcrypt20 \
|
||||
libslirp0 \
|
||||
libpixman-1-0 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
@ -75,6 +105,19 @@ COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html
|
|||
COPY deploy/entrypoint.sh /app/entrypoint.sh
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
# ── ESP32 emulation: lcgamboa QEMU .so + ROM binaries ────────────────────────
|
||||
# libqemu-xtensa.so → ESP32 / ESP32-S3 (Xtensa LX6/LX7)
|
||||
# libqemu-riscv32.so → ESP32-C3 (RISC-V)
|
||||
# esp32-v3-rom*.bin → boot/app ROM images required by esp32-picsimlab machine
|
||||
RUN mkdir -p /app/lib
|
||||
COPY --from=qemu-builder /qemu-lcgamboa/build/libqemu-xtensa.so /app/lib/
|
||||
COPY --from=qemu-builder /qemu-lcgamboa/build/libqemu-riscv32.so /app/lib/
|
||||
COPY --from=qemu-builder /qemu-lcgamboa/pc-bios/esp32-v3-rom.bin /app/lib/
|
||||
COPY --from=qemu-builder /qemu-lcgamboa/pc-bios/esp32-v3-rom-app.bin /app/lib/
|
||||
|
||||
# Activate full ESP32 emulation (GPIO + ADC + UART + PWM + NeoPixel + WiFi)
|
||||
ENV QEMU_ESP32_LIB=/app/lib/libqemu-xtensa.so
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["/app/entrypoint.sh"]
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -96,6 +96,22 @@ Component Picker showing 48 available components with visual previews, search, a
|
|||
- **UART0** serial output displayed in Serial Monitor
|
||||
- **ADC** — 12-bit, 3.3V reference on GPIO 26-29 (A0-A3)
|
||||
|
||||
### ESP32 Simulation (via lcgamboa QEMU)
|
||||
- **Real Xtensa LX6 dual-core emulation** via [lcgamboa/qemu](https://github.com/lcgamboa/qemu) fork
|
||||
- **Full GPIO** — all 40 GPIO pins with direction tracking and state callbacks
|
||||
- **UART0/1/2** — multi-UART serial with baud-rate detection
|
||||
- **ADC** — 12-bit, 3.3V reference on all ADC-capable pins (0–3300 mV injection)
|
||||
- **I2C** — synchronous bus with virtual device response support
|
||||
- **SPI** — full-duplex with configurable MISO byte injection
|
||||
- **RMT / NeoPixel** — hardware RMT decoder with WS2812 24-bit GRB frame decoding
|
||||
- **LEDC/PWM** — 16-channel duty cycle readout, mapped to LED brightness
|
||||
- **WiFi** — SLIRP NAT emulation (`WiFi.begin("PICSimLabWifi", "")`)
|
||||
- **arduino-esp32 2.0.17 (IDF 4.4.x)** — only compatible version with lcgamboa WiFi emulation
|
||||
- **Crash detection** — banner notification in UI with dismiss button
|
||||
- **Fully included in the Docker image** — zero extra setup required
|
||||
|
||||
See [docs/ESP32_EMULATION.md](docs/ESP32_EMULATION.md) for the complete installation guide.
|
||||
|
||||
### Serial Monitor
|
||||
- **Live serial output** — characters as the sketch sends them via `Serial.print()`
|
||||
- **Auto baud-rate detection** — reads hardware registers, no manual configuration needed
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.0from contextlib import asynccontextmanager
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import ctypes
|
|||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
|
||||
|
|
@ -51,8 +52,9 @@ logger = logging.getLogger(__name__)
|
|||
# MinGW64 bin — Windows needs this on the DLL search path for glib2/libgcrypt deps
|
||||
_MINGW64_BIN = r"C:\msys64\mingw64\bin"
|
||||
|
||||
# Default DLL path: same directory as this module (copied there after build)
|
||||
_DEFAULT_LIB = str(pathlib.Path(__file__).parent / "libqemu-xtensa.dll")
|
||||
# Default library path: .dll on Windows, .so on Linux/macOS
|
||||
_LIB_NAME = "libqemu-xtensa.dll" if sys.platform == "win32" else "libqemu-xtensa.so"
|
||||
_DEFAULT_LIB = str(pathlib.Path(__file__).parent / _LIB_NAME)
|
||||
|
||||
# ── GPIO pinmap ──────────────────────────────────────────────────────────────
|
||||
# pinmap[0] = total number of pin slots (40 for ESP32)
|
||||
|
|
@ -105,6 +107,7 @@ class Esp32LibBridge:
|
|||
"""
|
||||
|
||||
def __init__(self, lib_path: str, loop: asyncio.AbstractEventLoop):
|
||||
self._lib_path = lib_path
|
||||
if os.name == 'nt' and os.path.isdir(_MINGW64_BIN):
|
||||
os.add_dll_directory(_MINGW64_BIN)
|
||||
self._lib: ctypes.CDLL = ctypes.CDLL(lib_path)
|
||||
|
|
@ -160,8 +163,8 @@ class Esp32LibBridge:
|
|||
tmp.close()
|
||||
self._firmware_path = tmp.name
|
||||
|
||||
# ROM directory: esp32-v3-rom.bin lives beside the DLL
|
||||
rom_dir = str(pathlib.Path(_DEFAULT_LIB).parent).encode()
|
||||
# ROM directory: esp32-v3-rom.bin lives beside the library
|
||||
rom_dir = str(pathlib.Path(self._lib_path).parent).encode()
|
||||
|
||||
args_bytes = [
|
||||
b'qemu',
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"""
|
||||
EspLibManager — ESP32 emulation via lcgamboa libqemu-xtensa.dll.
|
||||
EspLibManager — ESP32 emulation via lcgamboa libqemu-xtensa (.dll/.so).
|
||||
|
||||
Exposes the same public API as EspQemuManager so simulation.py can
|
||||
transparently switch between the two backends:
|
||||
- DLL available → full GPIO + ADC + UART + I2C + SPI + RMT + WiFi (this module)
|
||||
- DLL missing → serial-only via subprocess (esp_qemu_manager.py)
|
||||
- lib available → full GPIO + ADC + UART + I2C + SPI + RMT + WiFi (this module)
|
||||
- lib missing → serial-only via subprocess (esp_qemu_manager.py)
|
||||
|
||||
Activation: set environment variable QEMU_ESP32_LIB to the DLL path,
|
||||
or place libqemu-xtensa.dll in the same directory as this module.
|
||||
Activation: set environment variable QEMU_ESP32_LIB to the library path,
|
||||
or place libqemu-xtensa.dll (Windows) / libqemu-xtensa.so (Linux) beside this module.
|
||||
|
||||
Events emitted via callback(event_type, data):
|
||||
system {event: 'booting'|'booted'|'crash'|'reboot'}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,30 @@
|
|||
|
||||
> Estado: **Funcional** · Backend completo · Frontend completo
|
||||
> Motor: **lcgamboa/qemu-8.1.3** · Plataforma: **arduino-esp32 2.0.17 (IDF 4.4.x)**
|
||||
> Disponible en: **Windows** (`.dll`) · **Linux / Docker** (`.so`, incluido en imagen oficial)
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
1. [Instalación rápida](#1-instalación-rápida)
|
||||
2. [Arquitectura general](#2-arquitectura-general)
|
||||
3. [Componentes del sistema](#3-componentes-del-sistema)
|
||||
4. [Firmware — Requisitos para lcgamboa](#4-firmware--requisitos-para-lcgamboa)
|
||||
5. [WiFi emulada](#5-wifi-emulada)
|
||||
6. [I2C emulado](#6-i2c-emulado)
|
||||
7. [RMT / NeoPixel (WS2812)](#7-rmt--neopixel-ws2812)
|
||||
8. [LEDC / PWM](#8-ledc--pwm)
|
||||
9. [Compilación de la DLL](#9-compilación-de-la-dll)
|
||||
10. [Tests](#10-tests)
|
||||
11. [Frontend — Eventos implementados](#11-frontend--eventos-implementados)
|
||||
12. [Limitaciones conocidas](#12-limitaciones-conocidas)
|
||||
13. [Variables de entorno](#13-variables-de-entorno)
|
||||
1. [Instalación rápida — Windows](#1-instalación-rápida--windows)
|
||||
2. [Instalación rápida — Docker / Linux](#2-instalación-rápida--docker--linux)
|
||||
3. [Arquitectura general](#3-arquitectura-general)
|
||||
4. [Componentes del sistema](#4-componentes-del-sistema)
|
||||
5. [Firmware — Requisitos para lcgamboa](#5-firmware--requisitos-para-lcgamboa)
|
||||
6. [WiFi emulada](#6-wifi-emulada)
|
||||
7. [I2C emulado](#7-i2c-emulado)
|
||||
8. [RMT / NeoPixel (WS2812)](#8-rmt--neopixel-ws2812)
|
||||
9. [LEDC / PWM](#9-ledc--pwm)
|
||||
10. [Compilar la librería manualmente](#10-compilar-la-librería-manualmente)
|
||||
11. [Tests](#11-tests)
|
||||
12. [Frontend — Eventos implementados](#12-frontend--eventos-implementados)
|
||||
13. [Limitaciones conocidas](#13-limitaciones-conocidas)
|
||||
14. [Variables de entorno](#14-variables-de-entorno)
|
||||
|
||||
---
|
||||
|
||||
## 1. Instalación rápida
|
||||
## 1. Instalación rápida — Windows
|
||||
|
||||
Esta sección cubre todo lo necesario para tener la emulación ESP32 funcionando desde cero en Windows.
|
||||
|
||||
|
|
@ -100,29 +102,17 @@ La DLL es el motor principal de la emulación. Hay que compilarla una vez desde
|
|||
# Asegurarse de tener el submodule
|
||||
git submodule update --init wokwi-libs/qemu-lcgamboa
|
||||
|
||||
# Abrir terminal MSYS2 MINGW64 y navegar al repo
|
||||
cd /e/Hardware/wokwi_clon # ajusta la ruta
|
||||
|
||||
# Paso 1: Configurar QEMU para Xtensa
|
||||
cd wokwi-libs/qemu-lcgamboa
|
||||
./configure \
|
||||
--target-list=xtensa-softmmu \
|
||||
--disable-werror \
|
||||
--enable-tcg \
|
||||
--enable-gcrypt \
|
||||
--enable-slirp \
|
||||
--enable-iconv \
|
||||
--without-default-features
|
||||
|
||||
# Paso 2: Compilar el binario principal (5-20 min según CPU)
|
||||
ninja -j$(nproc) qemu-system-xtensa.exe
|
||||
|
||||
# Paso 3: Relinkar como DLL (script automático)
|
||||
cd /e/Hardware/wokwi_clon
|
||||
bash build_qemu_step4.sh
|
||||
# En terminal MSYS2 MINGW64:
|
||||
cd /e/Hardware/wokwi_clon/wokwi-libs/qemu-lcgamboa
|
||||
bash build_libqemu-esp32-win.sh
|
||||
# Genera: build/libqemu-xtensa.dll y build/libqemu-riscv32.dll
|
||||
```
|
||||
|
||||
El script `build_qemu_step4.sh` genera `libqemu-xtensa.dll` y la copia automáticamente a `backend/app/services/`.
|
||||
Copia la DLL al backend:
|
||||
|
||||
```bash
|
||||
cp build/libqemu-xtensa.dll /e/Hardware/wokwi_clon/backend/app/services/
|
||||
```
|
||||
|
||||
**Verificar que la DLL se creó:**
|
||||
```bash
|
||||
|
|
@ -138,7 +128,7 @@ objdump -p backend/app/services/libqemu-xtensa.dll | grep -i "qemu_picsimlab\|qe
|
|||
|
||||
### 1.6 Obtener los ROM binaries del ESP32
|
||||
|
||||
La DLL necesita dos archivos ROM de Espressif para arrancar el ESP32. Vienen incluidos en la instalación de Espressif QEMU:
|
||||
La DLL necesita dos archivos ROM de Espressif para arrancar el ESP32. Deben colocarse en la misma carpeta que la DLL:
|
||||
|
||||
**Opción A — Desde esp-qemu (si está instalado):**
|
||||
```bash
|
||||
|
|
@ -146,13 +136,10 @@ copy "C:\esp-qemu\qemu\share\qemu\esp32-v3-rom.bin" backend\app\services\
|
|||
copy "C:\esp-qemu\qemu\share\qemu\esp32-v3-rom-app.bin" backend\app\services\
|
||||
```
|
||||
|
||||
**Opción B — Descargar directamente:**
|
||||
|
||||
Los ROM binaries son del repositorio oficial de Espressif:
|
||||
**Opción B — Desde el submodule lcgamboa (más fácil):**
|
||||
```bash
|
||||
# Busca en: https://github.com/espressif/qemu/tree/esp-develop/pc-bios
|
||||
# Descargar: esp32-v3-rom.bin y esp32-v3-rom-app.bin
|
||||
# Colocarlos en backend/app/services/
|
||||
cp wokwi-libs/qemu-lcgamboa/pc-bios/esp32-v3-rom.bin backend/app/services/
|
||||
cp wokwi-libs/qemu-lcgamboa/pc-bios/esp32-v3-rom-app.bin backend/app/services/
|
||||
```
|
||||
|
||||
**Verificar:**
|
||||
|
|
@ -199,7 +186,7 @@ uvicorn app.main:app --reload --port 8001
|
|||
El sistema detecta automáticamente la DLL. Verifica en los logs:
|
||||
```
|
||||
INFO: libqemu-xtensa.dll found at backend/app/services/libqemu-xtensa.dll
|
||||
INFO: EspLibManager: DLL mode active (GPIO, ADC, UART, WiFi, I2C, SPI, RMT, LEDC)
|
||||
INFO: EspLibManager: lib mode active (GPIO, ADC, UART, WiFi, I2C, SPI, RMT, LEDC)
|
||||
```
|
||||
|
||||
Si no aparece, verifica con:
|
||||
|
|
@ -207,7 +194,7 @@ Si no aparece, verifica con:
|
|||
python -c "
|
||||
import sys; sys.path.insert(0,'backend')
|
||||
from app.services.esp32_lib_manager import esp_lib_manager
|
||||
print('DLL disponible:', esp_lib_manager.is_available())
|
||||
print('lib disponible:', esp_lib_manager.is_available())
|
||||
"
|
||||
```
|
||||
|
||||
|
|
@ -235,20 +222,98 @@ El archivo `firmware.merged.bin` es el que se carga en la emulación.
|
|||
|
||||
---
|
||||
|
||||
## 2. Arquitectura general
|
||||
## 2. Instalación rápida — Docker / Linux
|
||||
|
||||
**La emulación ESP32 completa está incluida en la imagen Docker oficial.** No requiere ninguna instalación adicional — la `libqemu-xtensa.so` se compila automáticamente durante el build de la imagen a partir del fork lcgamboa.
|
||||
|
||||
### 2.1 Usar la imagen precompilada (recomendado)
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name velxio \
|
||||
-p 3080:80 \
|
||||
-v $(pwd)/data:/app/data \
|
||||
-e SECRET_KEY=tu-secreto \
|
||||
ghcr.io/davidmonterocrespo24/velxio:master
|
||||
```
|
||||
|
||||
La emulación ESP32 con GPIO completo está activa automáticamente. No se necesita ninguna variable de entorno adicional.
|
||||
|
||||
### 2.2 Build local de la imagen
|
||||
|
||||
```bash
|
||||
git clone https://github.com/davidmonterocrespo24/velxio.git
|
||||
cd velxio
|
||||
docker build -f Dockerfile.standalone -t velxio .
|
||||
docker run -d -p 3080:80 -e SECRET_KEY=secreto velxio
|
||||
```
|
||||
|
||||
> **Nota de build time:** La compilación de QEMU tarda 15-30 minutos la primera vez.
|
||||
> Los builds posteriores usan la capa Docker cacheada — son instantáneos mientras no
|
||||
> cambie el source de lcgamboa.
|
||||
|
||||
### 2.3 Verificar emulación ESP32 en el container
|
||||
|
||||
```bash
|
||||
# Verificar que .so y ROMs están presentes
|
||||
docker exec <container_id> ls -lh /app/lib/
|
||||
|
||||
# Verificar que ctypes puede cargar la .so
|
||||
docker exec <container_id> python3 -c \
|
||||
"import ctypes; ctypes.CDLL('/app/lib/libqemu-xtensa.so'); print('OK')"
|
||||
|
||||
# Verificar que el manager la detecta
|
||||
docker exec <container_id> python3 -c \
|
||||
"import sys; sys.path.insert(0,'/app')
|
||||
from app.services.esp32_lib_manager import esp_lib_manager
|
||||
print('ESP32 lib disponible:', esp_lib_manager.is_available())"
|
||||
```
|
||||
|
||||
### 2.4 Linux (sin Docker)
|
||||
|
||||
Si corres el backend directamente en Linux:
|
||||
|
||||
```bash
|
||||
# 1. Instalar dependencias de runtime
|
||||
sudo apt-get install -y libglib2.0-0 libgcrypt20 libslirp0 libpixman-1-0
|
||||
|
||||
# 2. Compilar la .so (requiere herramientas de build)
|
||||
sudo apt-get install -y git python3-pip ninja-build pkg-config flex bison \
|
||||
gcc g++ make libglib2.0-dev libgcrypt20-dev libslirp-dev libpixman-1-dev libfdt-dev
|
||||
pip3 install meson
|
||||
|
||||
git clone --depth=1 --branch picsimlab-esp32 \
|
||||
https://github.com/lcgamboa/qemu /tmp/qemu-lcgamboa
|
||||
cd /tmp/qemu-lcgamboa
|
||||
bash build_libqemu-esp32.sh
|
||||
# → build/libqemu-xtensa.so
|
||||
|
||||
# 3. Copiar .so y ROMs junto al módulo Python
|
||||
cp build/libqemu-xtensa.so /ruta/al/proyecto/backend/app/services/
|
||||
cp pc-bios/esp32-v3-rom.bin /ruta/al/proyecto/backend/app/services/
|
||||
cp pc-bios/esp32-v3-rom-app.bin /ruta/al/proyecto/backend/app/services/
|
||||
|
||||
# 4. Arrancar backend (auto-detecta la .so)
|
||||
cd /ruta/al/proyecto/backend
|
||||
uvicorn app.main:app --reload --port 8001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Arquitectura general
|
||||
|
||||
```
|
||||
Usuario (browser)
|
||||
└── WebSocket (/ws/{client_id})
|
||||
└── simulation.py (FastAPI router)
|
||||
├── EspLibManager ← backend con DLL (GPIO, WiFi, I2C, SPI, RMT…)
|
||||
├── EspLibManager ← backend con .so/.dll (GPIO, WiFi, I2C, SPI, RMT…)
|
||||
└── EspQemuManager ← fallback solo-UART via subprocess
|
||||
│
|
||||
[QEMU_ESP32_LIB=libqemu-xtensa.dll]
|
||||
[QEMU_ESP32_LIB=libqemu-xtensa.so|.dll]
|
||||
│
|
||||
Esp32LibBridge (ctypes)
|
||||
│
|
||||
libqemu-xtensa.dll ← lcgamboa fork de QEMU 8.1.3
|
||||
libqemu-xtensa.so/.dll ← lcgamboa fork de QEMU 8.1.3
|
||||
│
|
||||
Machine: esp32-picsimlab
|
||||
│
|
||||
|
|
@ -259,47 +324,55 @@ Usuario (browser)
|
|||
```
|
||||
|
||||
El sistema selecciona backend automáticamente:
|
||||
- **DLL disponible** → `EspLibManager` (GPIO completo + todos los periféricos)
|
||||
- **DLL ausente** → `EspQemuManager` (solo UART serial via TCP, subprocess QEMU)
|
||||
- **lib disponible** → `EspLibManager` (GPIO completo + todos los periféricos)
|
||||
- **lib ausente** → `EspQemuManager` (solo UART serial via TCP, subprocess QEMU)
|
||||
|
||||
Activación de DLL: colocar `libqemu-xtensa.dll` en `backend/app/services/` o setear:
|
||||
```bash
|
||||
QEMU_ESP32_LIB=C:/ruta/a/libqemu-xtensa.dll uvicorn app.main:app
|
||||
```
|
||||
Detección automática:
|
||||
| Plataforma | Lib buscada | Fuente |
|
||||
|------------|-------------|--------|
|
||||
| Docker / Linux | `/app/lib/libqemu-xtensa.so` | Compilada en el Dockerfile |
|
||||
| Windows (desarrollo) | `backend/app/services/libqemu-xtensa.dll` | Compilada con MSYS2 |
|
||||
| Custom | `$QEMU_ESP32_LIB` | Variable de entorno |
|
||||
|
||||
---
|
||||
|
||||
## 3. Componentes del sistema
|
||||
## 4. Componentes del sistema
|
||||
|
||||
### 3.1 `libqemu-xtensa.dll`
|
||||
### 4.1 `libqemu-xtensa.so` / `libqemu-xtensa.dll`
|
||||
|
||||
Compilada desde el fork [lcgamboa/qemu](https://github.com/lcgamboa/qemu) rama `qemu-8.1.3`.
|
||||
Compilada desde el fork [lcgamboa/qemu](https://github.com/lcgamboa/qemu) rama `picsimlab-esp32`.
|
||||
|
||||
**Dependencias en runtime (Windows) — resueltas automáticamente:**
|
||||
**Dependencias en runtime:**
|
||||
|
||||
*Windows (resueltas automáticamente desde `C:\msys64\mingw64\bin\`):*
|
||||
```
|
||||
C:\msys64\mingw64\bin\
|
||||
libglib-2.0-0.dll
|
||||
libgcrypt-20.dll
|
||||
libgpg-error-0.dll
|
||||
libslirp-0.dll
|
||||
libintl-8.dll
|
||||
libpcre2-8-0.dll
|
||||
(y ~15 DLLs más de MinGW64)
|
||||
libglib-2.0-0.dll, libgcrypt-20.dll, libslirp-0.dll,
|
||||
libgpg-error-0.dll, libintl-8.dll, libpcre2-8-0.dll (+~15 DLLs MinGW64)
|
||||
```
|
||||
|
||||
El bridge las registra automáticamente con `os.add_dll_directory()`.
|
||||
|
||||
**ROM binaries requeridas** (en la misma carpeta que la DLL):
|
||||
*Linux / Docker (paquetes del sistema):*
|
||||
```
|
||||
backend/app/services/
|
||||
libglib2.0-0, libgcrypt20, libslirp0, libpixman-1-0
|
||||
```
|
||||
|
||||
**ROM binaries requeridas** (en la misma carpeta que la lib):
|
||||
```
|
||||
# Windows (backend/app/services/):
|
||||
libqemu-xtensa.dll ← motor principal (no en git — 43 MB)
|
||||
esp32-v3-rom.bin ← ROM de boot del ESP32 (no en git — 446 KB)
|
||||
esp32-v3-rom-app.bin ← ROM de aplicación (no en git — 446 KB)
|
||||
|
||||
# Docker (/app/lib/):
|
||||
libqemu-xtensa.so ← compilada en Stage 0 del Dockerfile
|
||||
libqemu-riscv32.so ← ESP32-C3 (RISC-V)
|
||||
esp32-v3-rom.bin ← copiada de pc-bios/ del repo lcgamboa
|
||||
esp32-v3-rom-app.bin
|
||||
```
|
||||
|
||||
> Estos archivos están en `.gitignore` por su tamaño. Cada desarrollador los genera/obtiene localmente (ver sección 1.5 y 1.6).
|
||||
> En Windows estos archivos están en `.gitignore` por su tamaño. Cada desarrollador los genera localmente.
|
||||
> En Docker se incluyen automáticamente en la imagen.
|
||||
|
||||
**Exports de la DLL:**
|
||||
**Exports de la librería:**
|
||||
```c
|
||||
void qemu_init(int argc, char** argv, char** envp)
|
||||
void qemu_main_loop(void)
|
||||
|
|
@ -327,7 +400,7 @@ typedef struct {
|
|||
|
||||
---
|
||||
|
||||
### 3.2 GPIO Pinmap
|
||||
### 4.2 GPIO Pinmap
|
||||
|
||||
```python
|
||||
# Identity mapping: QEMU IRQ slot i → GPIO number i-1
|
||||
|
|
@ -344,7 +417,7 @@ El bridge traduce automáticamente slot → GPIO real antes de notificar listene
|
|||
|
||||
---
|
||||
|
||||
### 3.3 `Esp32LibBridge` (Python ctypes)
|
||||
### 4.3 `Esp32LibBridge` (Python ctypes)
|
||||
|
||||
Archivo: `backend/app/services/esp32_lib_bridge.py`
|
||||
|
||||
|
|
@ -378,12 +451,12 @@ bridge.get_tiocm() # UART modem lines bitmask
|
|||
```
|
||||
|
||||
**Threading crítico:**
|
||||
`qemu_init()` y `qemu_main_loop()` **deben correr en el mismo thread** (BQL — Big QEMU Lock es thread-local). El bridge los ejecuta en un único daemon thread y usa `threading.Event` para sincronizar el inicio:
|
||||
`qemu_init()` y `qemu_main_loop()` **deben correr en el mismo thread** (BQL — Big QEMU Lock es thread-local). El bridge los ejecuta en un único daemon thread:
|
||||
|
||||
```python
|
||||
# Correcto:
|
||||
def _qemu_thread():
|
||||
lib.qemu_init(argc, argv, None) # init + init_done.set()
|
||||
lib.qemu_init(argc, argv, None) # init
|
||||
lib.qemu_main_loop() # bloquea indefinidamente
|
||||
|
||||
# Incorrecto:
|
||||
|
|
@ -393,7 +466,7 @@ lib.qemu_main_loop() # en thread B ← crash: "qemu_mutex_unlock_iothread
|
|||
|
||||
---
|
||||
|
||||
### 3.4 `EspLibManager` (Python)
|
||||
### 4.4 `EspLibManager` (Python)
|
||||
|
||||
Archivo: `backend/app/services/esp32_lib_manager.py`
|
||||
|
||||
|
|
@ -414,7 +487,6 @@ Convierte callbacks de hardware en **eventos WebSocket** para el frontend:
|
|||
|
||||
**Detección de crash y reboot:**
|
||||
```python
|
||||
# El firmware imprime en UART cuando crashea:
|
||||
"Cache disabled but cached memory region accessed" → event: crash
|
||||
"Rebooting..." → event: reboot
|
||||
```
|
||||
|
|
@ -440,7 +512,7 @@ manager.get_status(client_id) # → dict con runtime sta
|
|||
|
||||
---
|
||||
|
||||
### 3.5 `simulation.py` — Mensajes WebSocket
|
||||
### 4.5 `simulation.py` — Mensajes WebSocket
|
||||
|
||||
**Frontend → Backend (mensajes entrantes):**
|
||||
|
||||
|
|
@ -460,9 +532,9 @@ manager.get_status(client_id) # → dict con runtime sta
|
|||
|
||||
---
|
||||
|
||||
## 4. Firmware — Requisitos para lcgamboa
|
||||
## 5. Firmware — Requisitos para lcgamboa
|
||||
|
||||
### 4.1 Versión de plataforma requerida
|
||||
### 5.1 Versión de plataforma requerida
|
||||
|
||||
**✅ Usar: arduino-esp32 2.x (IDF 4.4.x)**
|
||||
**❌ No usar: arduino-esp32 3.x (IDF 5.x)**
|
||||
|
|
@ -480,7 +552,7 @@ Cache disabled but cached memory region accessed
|
|||
EXCCAUSE: 0x00000007
|
||||
```
|
||||
|
||||
### 4.2 Imagen de flash
|
||||
### 5.2 Imagen de flash
|
||||
|
||||
La imagen debe ser un archivo binario completo de **4 MB** (formato merged flash):
|
||||
|
||||
|
|
@ -502,7 +574,7 @@ esptool --chip esp32 merge_bin \
|
|||
|
||||
El backend (`arduino_cli.py`) fuerza `FlashMode=dio` automáticamente para todos los targets `esp32:*`.
|
||||
|
||||
### 4.3 Sketch compatible con lcgamboa (ejemplo mínimo IRAM-safe)
|
||||
### 5.3 Sketch compatible con lcgamboa (ejemplo mínimo IRAM-safe)
|
||||
|
||||
Para sketches que necesiten máxima compatibilidad (sin Arduino framework):
|
||||
|
||||
|
|
@ -542,7 +614,7 @@ void IRAM_ATTR loop() { ets_delay_us(1000000); }
|
|||
|
||||
---
|
||||
|
||||
## 5. WiFi emulada
|
||||
## 6. WiFi emulada
|
||||
|
||||
lcgamboa implementa una WiFi simulada con SSIDs hardcoded:
|
||||
|
||||
|
|
@ -560,12 +632,12 @@ El ESP32 emulado puede:
|
|||
|
||||
**Limitaciones:**
|
||||
- No hay forma de configurar las SSIDs o contraseñas desde Python
|
||||
- La IP del "router" virtual es `10.0.2.2` (host Windows)
|
||||
- La IP del "router" virtual es `10.0.2.2` (host)
|
||||
- El ESP32 emulado es accesible en `localhost:PORT` via port forwarding SLIRP
|
||||
|
||||
---
|
||||
|
||||
## 6. I2C emulado
|
||||
## 7. I2C emulado
|
||||
|
||||
El callback I2C es **síncrono** — QEMU espera la respuesta antes de continuar:
|
||||
|
||||
|
|
@ -590,7 +662,7 @@ Desde WebSocket:
|
|||
|
||||
---
|
||||
|
||||
## 7. RMT / NeoPixel (WS2812)
|
||||
## 8. RMT / NeoPixel (WS2812)
|
||||
|
||||
El evento RMT lleva un item de 32 bits codificado así:
|
||||
```
|
||||
|
|
@ -620,7 +692,7 @@ El evento emitido al frontend:
|
|||
|
||||
---
|
||||
|
||||
## 8. LEDC / PWM
|
||||
## 9. LEDC / PWM
|
||||
|
||||
`qemu_picsimlab_get_internals(0)` retorna un puntero a un array de 16 `uint32_t` con el duty cycle de cada canal LEDC. Llamar periódicamente (cada ~50 ms):
|
||||
|
||||
|
|
@ -633,32 +705,20 @@ El duty máximo típico es 8192 (timer de 13 bits). Para brillo de LED: `duty_pc
|
|||
|
||||
---
|
||||
|
||||
## 9. Compilación de la DLL
|
||||
## 10. Compilar la librería manualmente
|
||||
|
||||
### 9.1 Proceso completo (resumen)
|
||||
### 10.1 Windows (MSYS2 MINGW64)
|
||||
|
||||
El script `build_libqemu-esp32-win.sh` en `wokwi-libs/qemu-lcgamboa/` automatiza el proceso:
|
||||
|
||||
```bash
|
||||
# En MSYS2 MINGW64:
|
||||
cd wokwi-libs/qemu-lcgamboa
|
||||
|
||||
./configure \
|
||||
--target-list=xtensa-softmmu \
|
||||
--disable-werror \
|
||||
--enable-tcg \
|
||||
--enable-gcrypt \
|
||||
--enable-slirp \
|
||||
--enable-iconv \
|
||||
--without-default-features
|
||||
|
||||
ninja -j$(nproc) qemu-system-xtensa.exe
|
||||
|
||||
# Desde bash normal (no MSYS2):
|
||||
bash build_qemu_step4.sh
|
||||
bash build_libqemu-esp32-win.sh
|
||||
# Genera: build/libqemu-xtensa.dll y build/libqemu-riscv32.dll
|
||||
```
|
||||
|
||||
### 9.2 Detalle del relink como DLL
|
||||
|
||||
El proceso extrae el comando de link de `build.ninja`, elimina `softmmu_main.c.obj` (que contiene `main()`), y agrega flags de DLL:
|
||||
El script configura QEMU con `--extra-cflags=-fPIC` (necesario para Windows/PE con ASLR), compila el binario completo y luego relinks eliminando `softmmu_main.c.obj` (que contiene `main()`):
|
||||
|
||||
```bash
|
||||
cc -m64 -mcx16 -shared \
|
||||
|
|
@ -668,23 +728,34 @@ cc -m64 -mcx16 -shared \
|
|||
@dll_link.rsp # todos los .obj excepto softmmu_main
|
||||
```
|
||||
|
||||
### 9.3 Verificar exports
|
||||
### 10.2 Linux
|
||||
|
||||
El script `build_libqemu-esp32.sh` produce `.so`:
|
||||
|
||||
```bash
|
||||
objdump -p libqemu-xtensa.dll | grep -i "qemu_picsimlab\|qemu_init\|qemu_main"
|
||||
# Debe mostrar:
|
||||
# qemu_init
|
||||
# qemu_main_loop
|
||||
# qemu_cleanup
|
||||
# qemu_picsimlab_register_callbacks
|
||||
# qemu_picsimlab_set_pin
|
||||
# qemu_picsimlab_set_apin
|
||||
# qemu_picsimlab_uart_receive
|
||||
# qemu_picsimlab_get_internals
|
||||
# qemu_picsimlab_get_TIOCM
|
||||
cd wokwi-libs/qemu-lcgamboa
|
||||
bash build_libqemu-esp32.sh
|
||||
# Genera: build/libqemu-xtensa.so y build/libqemu-riscv32.so
|
||||
```
|
||||
|
||||
### 9.4 Parche requerido en scripts/symlink-install-tree.py
|
||||
### 10.3 Verificar exports (ambas plataformas)
|
||||
|
||||
```bash
|
||||
# Linux:
|
||||
nm -D build/libqemu-xtensa.so | grep -i "qemu_picsimlab\|qemu_init\|qemu_main"
|
||||
|
||||
# Windows:
|
||||
objdump -p build/libqemu-xtensa.dll | grep -i "qemu_picsimlab\|qemu_init"
|
||||
|
||||
# Debe mostrar:
|
||||
# qemu_init, qemu_main_loop, qemu_cleanup
|
||||
# qemu_picsimlab_register_callbacks
|
||||
# qemu_picsimlab_set_pin, qemu_picsimlab_set_apin
|
||||
# qemu_picsimlab_uart_receive
|
||||
# qemu_picsimlab_get_internals, qemu_picsimlab_get_TIOCM
|
||||
```
|
||||
|
||||
### 10.4 Parche requerido en Windows (symlink-install-tree.py)
|
||||
|
||||
Windows no permite crear symlinks sin privilegios de administrador. El script de QEMU falla con `WinError 1314`. Parche aplicado:
|
||||
|
||||
|
|
@ -703,9 +774,9 @@ if os.name == 'nt':
|
|||
|
||||
---
|
||||
|
||||
## 10. Tests
|
||||
## 11. Tests
|
||||
|
||||
### 10.1 Test suite principal (28 tests)
|
||||
### 11.1 Test suite principal (28 tests)
|
||||
|
||||
Archivo: `test/esp32/test_esp32_lib_bridge.py`
|
||||
|
||||
|
|
@ -716,13 +787,13 @@ python -m pytest test/esp32/test_esp32_lib_bridge.py -v
|
|||
|
||||
| Grupo | Tests | Qué verifica |
|
||||
|-------|-------|--------------|
|
||||
| `TestDllExists` | 5 | Rutas de DLL, ROM binaries, MinGW64 |
|
||||
| `TestDllLoads` | 3 | Carga de DLL, symbols exportados |
|
||||
| `TestDllExists` | 5 | Rutas de lib, ROM binaries, dependencias de plataforma |
|
||||
| `TestDllLoads` | 3 | Carga de lib, symbols exportados |
|
||||
| `TestPinmap` | 3 | Estructura del pinmap, GPIO2 en slot 3 |
|
||||
| `TestManagerAvailability` | 2 | `is_available()`, API surface |
|
||||
| `TestEsp32LibIntegration` | 15 | QEMU real con firmware blink: boot, UART, GPIO, ADC, SPI, I2C |
|
||||
|
||||
### 10.2 Test integración Arduino ↔ ESP32 (13 tests)
|
||||
### 11.2 Test integración Arduino ↔ ESP32 (13 tests)
|
||||
|
||||
Archivo: `test/esp32/test_arduino_esp32_integration.py`
|
||||
|
||||
|
|
@ -747,13 +818,13 @@ python -m pytest test/esp32/test_arduino_esp32_integration.py -v
|
|||
**Firmware de test:** `test/esp32-emulator/binaries_lcgamboa/serial_led.ino.merged.bin`
|
||||
Sketch fuente: `test/esp32-emulator/sketches/serial_led/serial_led.ino`
|
||||
|
||||
### 10.3 Omitir tests de integración (solo unitarios)
|
||||
### 11.3 Omitir tests de integración (solo unitarios)
|
||||
|
||||
```bash
|
||||
SKIP_LIB_INTEGRATION=1 python -m pytest test/esp32/ -v
|
||||
```
|
||||
|
||||
### 10.4 Recompilar el firmware de test
|
||||
### 11.4 Recompilar el firmware de test
|
||||
|
||||
Si necesitas recompilar los binarios de test:
|
||||
|
||||
|
|
@ -787,7 +858,7 @@ esptool --chip esp32 merge_bin --fill-flash-size 4MB \
|
|||
|
||||
---
|
||||
|
||||
## 11. Frontend — Eventos implementados
|
||||
## 12. Frontend — Eventos implementados
|
||||
|
||||
Todos los eventos del backend están conectados al frontend:
|
||||
|
||||
|
|
@ -826,37 +897,49 @@ bridge.setSpiResponse(response) // Byte MISO de dispositivo SPI
|
|||
|
||||
---
|
||||
|
||||
## 12. Limitaciones conocidas (no solucionables sin modificar QEMU)
|
||||
## 13. Limitaciones conocidas (no solucionables sin modificar QEMU)
|
||||
|
||||
| Limitación | Causa | Workaround |
|
||||
|------------|-------|------------|
|
||||
| **Una sola instancia ESP32 por proceso** | QEMU usa estado global en variables estáticas | Lanzar múltiples procesos Python |
|
||||
| **WiFi solo con SSIDs hardcoded** | lcgamboa codifica "PICSimLabWifi" y "Espressif" en C | Modificar y recompilar la DLL |
|
||||
| **WiFi solo con SSIDs hardcoded** | lcgamboa codifica "PICSimLabWifi" y "Espressif" en C | Modificar y recompilar la lib |
|
||||
| **Sin BLE / Bluetooth Classic** | No implementado en lcgamboa | No disponible |
|
||||
| **Sin touch capacitivo** | `touchRead()` no tiene callback en picsimlab | No disponible |
|
||||
| **Sin DAC** | GPIO25/GPIO26 analógico no expuesto por picsimlab | No disponible |
|
||||
| **Flash fija en 4MB** | Hardcoded en la machine esp32-picsimlab | Recompilar DLL |
|
||||
| **Flash fija en 4MB** | Hardcoded en la machine esp32-picsimlab | Recompilar lib |
|
||||
| **arduino-esp32 3.x causa crash** | IDF 5.x maneja caché diferente al WiFi emulado | Usar 2.x (IDF 4.4.x) |
|
||||
|
||||
---
|
||||
|
||||
## 13. Variables de entorno
|
||||
## 14. Variables de entorno
|
||||
|
||||
| Variable | Valor | Efecto |
|
||||
|----------|-------|--------|
|
||||
| `QEMU_ESP32_LIB` | ruta a `libqemu-xtensa.dll` | Fuerza ruta de DLL (override auto-detect) |
|
||||
| `QEMU_ESP32_BINARY` | ruta a `qemu-system-xtensa.exe` | Fallback subprocess (sin DLL) |
|
||||
| Variable | Valor de ejemplo | Efecto |
|
||||
|----------|-----------------|--------|
|
||||
| `QEMU_ESP32_LIB` | `/app/lib/libqemu-xtensa.so` | Fuerza ruta de lib (override auto-detect) |
|
||||
| `QEMU_ESP32_BINARY` | `/usr/bin/qemu-system-xtensa` | Fallback subprocess (sin lib) |
|
||||
| `SKIP_LIB_INTEGRATION` | `1` | Omite tests de integración QEMU en pytest |
|
||||
|
||||
Si `QEMU_ESP32_LIB` no está seteado, el sistema busca `libqemu-xtensa.dll` en la misma carpeta que `esp32_lib_bridge.py` (`backend/app/services/`).
|
||||
**Auto-detección por plataforma:**
|
||||
|
||||
| Plataforma | Lib buscada automáticamente |
|
||||
|------------|----------------------------|
|
||||
| Docker / Linux | `/app/lib/libqemu-xtensa.so` (via `QEMU_ESP32_LIB`) |
|
||||
| Windows | `backend/app/services/libqemu-xtensa.dll` |
|
||||
| Custom | `$QEMU_ESP32_LIB` (si está seteado, tiene prioridad) |
|
||||
|
||||
**Ejemplos de arranque:**
|
||||
|
||||
**Ejemplo arranque completo:**
|
||||
```bash
|
||||
# Con DLL (emulación completa GPIO + WiFi + ADC + I2C + SPI + RMT + LEDC):
|
||||
# Docker — todo automático, no requiere variables extra:
|
||||
docker run -d -p 3080:80 -e SECRET_KEY=secreto ghcr.io/davidmonterocrespo24/velxio:master
|
||||
|
||||
# Windows con lib (emulación completa GPIO + WiFi + ADC + I2C + SPI + RMT + LEDC):
|
||||
cd backend && venv\Scripts\activate
|
||||
uvicorn app.main:app --reload --port 8001
|
||||
|
||||
# Sin DLL (fallback: solo UART serial via subprocess QEMU):
|
||||
QEMU_ESP32_BINARY=C:/esp-qemu/qemu/bin/qemu-system-xtensa.exe \
|
||||
uvicorn app.main:app --reload --port 8001
|
||||
# Linux con lib en ruta custom:
|
||||
QEMU_ESP32_LIB=/opt/velxio/libqemu-xtensa.so uvicorn app.main:app --port 8001
|
||||
|
||||
# Sin lib (fallback: solo UART serial via subprocess QEMU):
|
||||
QEMU_ESP32_BINARY=/usr/bin/qemu-system-xtensa uvicorn app.main:app --port 8001
|
||||
```
|
||||
|
|
|
|||
Loading…
Reference in New Issue