Guarda de regresion para lo que se acaba de arreglar. Recorre el grafo de
cableado (no solo el vecino inmediato del LED, porque la resistencia
protege igual desde el catodo o desde el otro lado de un transistor o un
rele) y falla si algun LED llega a un pin sin limitar la corriente.
Es el peor tipo de ejemplo roto: el sketch imprime "LED ON", el runtime
quema el LED y no aparece ningun error, solo un LED oscuro.
El modelo electrico es honesto: un LED colgado directo de un pin a 3,3 V
o 5 V pide una corriente absurda, el simulador lo quema y se queda
oscuro aunque el sketch imprima "LED ON". Ya se habia arreglado en los
ejemplos de Raspberry Pi con un solo LED, pero quedaban 24 sin proteger
-- sobre todo los de Pico (i2c-scanner, spi-loopback, adc-read,
multi-protocol, serial-echo, eeprom, rtc) y todos los RGB, que necesitan
una resistencia POR CANAL.
Se anaden 36 resistencias de 220 ohm en serie, cada una entre el pin y
el anodo, colocadas al lado de su LED. Los ejemplos que ya llevaban la
resistencia en el catodo se dejan como estan: protege igual.
Con esto la galeria pasa de 24 LEDs que se queman al pulsar Play a 0.
Every Pi example hung its LED straight off a GPIO pin, so the electrical
model solved 2.4e29 A and burnt it out: the script printed LED ON while
the canvas stayed dark. Written before the SPICE engine landed, and
nobody noticed because reaching that point took a 90 s boot. 220R in
series, like the ESP32 examples already do.
Without this we cannot tell whether the in-browser path is actually
displacing guest boots — the whole point of the dual engine is a number
(% of runs that never touched the backend), so the event has to say.
Guests stay on '-nic none' unless a profile opts in, and opting in does
NOT mean internet: the NIC is user,restrict=on (no route out, no route
to the host LAN) with one guestfwd to whatever command the overlay
configures — a filtering proxy in practice. Keeps 'user code never gets
a raw socket outside' true by construction.
A forgotten tab pinned a QEMU process and its RAM for as long as the
browser stayed open. Instances now shut themselves down after
MAX_SESSION_SECONDS (2 h default, env-tunable) and say so on the serial
line instead of vanishing.
A profile's extra_drive is the same file for everyone; an overlay may
need a disk built for THIS session (what the project declared). New
seam set_pi_extra_drive_resolver(fn) receives the client id, the board
and the start_pi payload and returns raw images, mounted read-only after
the profile's own. The WS route forwards msg_data and the bridge gained
startPayload so a client can declare it. Generic: no package manager,
no OS knowledge in the OSS tree.
Generic seam only — no board, OS or runtime specifics in the OSS tree:
an overlay may register an engine that decides, per board, whether a run
needs the Linux guest or can happen in the browser. The decision is data
(engine + reason + where) so the UI can explain a 90 s boot instead of
just taking it, and BoardInstance carries engineMode / enginePinned so
the user's choice is predictable and the terminal panel knows whether an
interactive shell exists. Nothing registers in OSS: the QEMU path is
untouched.
The netlist collector only understood GPIO<n>/GP<n>/bare digits, so a
wire on a QEMU-Linux board's P24 Gravity pad never got its V-source
stamped and the LED stayed dark while the guest toggled the pin.
Keeping Play enabled while the guest ran broke the convention every
other board follows (owner report). Run now disables as usual; RESET on
a booted guest re-uploads the edited files and re-runs the script
without the ~45 s reboot, matching Reset's restart-the-program meaning.
The generic arm64 image prints another product's banner/motd/login line
during boot, before guestSetup can re-brand the guest. Boards with
quietBoot show a neutral '[Velxio] Booting <label> (Linux guest)...'
progress line (dots every 4 s) while boot detection and the prompt-gated
upload still run underneath; the shell is revealed (already re-branded)
right before the auto-run command, so the user's first visible output is
their own program.
With the bare boardId as client_id, two tabs (or two users) on the same
example shared one QEMU instance: serial output went to whichever socket
connected last, keystrokes interleaved and one tab's stop killed the
other's guest. Suffix the tab session id so each tab gets its own
instance (stopped on its own disconnect, like the ESP32 workers).
QEMU-Linux boards used three competing file surfaces (workspace group
with a meaningless sketch.ino/libraries.json, the VFS panel with its
Upload button, and the Pi workspace's own editor). Now they behave like
every other board:
- the editor file group defaults to script.py for ANY Pi-family kind
(kind-based check via isPiBoardKind, not the old raspberry-pi- string)
- the libraries.json manifest row is hidden for Pi boards
- EditorPage always renders Monaco; the RaspberryPiWorkspace swap is
gone
- the bottom serial panel renders the interactive xterm (PiTerminal,
now seeded with session history) for running Pi boards
- example vfsFiles load into the editor group (single source of truth);
the run path uploads the group into the guest home
- Run on a booted guest re-runs without the 45 s reboot (Ctrl-C +
re-upload + run); starting a Pi board pops the terminal open
- piSyncAndRunScript/piRerunScript exported from the store; auto-run
now applies to the whole family (guestHome/autoRun overridable)
- ProBoardDef.autoRun: after boot (+guestSetup) the VFS uploads itself
and the command runs, so a single Run click boots, uploads and starts
the user's script (same UX as compiled boards)
- ProBoardDef.guestHome: VFS home dir override ('/root' for guests that
log in as root); those boards drop the historic hello.sh sample
- upload sequence extracted to utils/piUpload (shared by the VFS panel
button and autoRun)
- serial monitor strips DEL/C0 control echoes (backspace showed tofu)
El caso ownsPointer retornaba sin stopPropagation y el mousedown llegaba al
fondo del canvas, cuyo convenio arrastre-izquierdo-panea movia el mundo
entero bajo el dedo a mitad de swipe (solo el tap funcionaba). El modelo
tactil escucha POINTER events — stream aparte — asi que cortar el mousedown
(y el touchstart movil) no le quita nada. Los knobs wokwi conservan el
pass-through de siempre.
The boards array is replaced on every serial batch, so the attach effect
detached/re-attached built-in peripherals at up to 60 Hz while output
flowed; events landing inside the 500 ms re-attach window were lost.
One-shot streams (a guest display list) never recover — the repainting
SPI LCD decoders masked this for ESP32 boards.
isBoardSeated lee pinInfo/boardSocket del DOM; en el primer render de un
ejemplo que ABRE con la placa ya posada ni la placa ni el zocalo estan
montados, el memo devolvia 'no posada' y no recalculaba nunca (z=0 pese a
asiento exacto medido). Re-chequeo al siguiente frame y a los 400ms.
La whitelist de tags que poseen el puntero durante la simulacion gana una
salida generica regla-6a: el elemento declara `get ownsPointer() { return
true }` y el wrapper no inicia el drag mientras corre. El cristal del Round
Display pintaba el punto verde Y arrastraba el shield por el canvas a la vez.
El bump global a zIndex 3 (efecto iman del Round Display) puso TODAS las
placas por encima de TODOS los componentes: una resistencia al lado de un
Arduino quedaba oculta tras la placa en cualquier ejemplo normal (se veian
los cables, no el cuerpo). Ahora isBoardSeated (misma matematica del snap,
umbral 0.5px) decide: posada -> z3 encima de su zocalo, como el XIAO fisico
apilado en el shield; libre -> z0 debajo de los componentes, como siempre.
El iman coloca la placa exactamente en el asiento, asi que al capturarla
salta al frente y al arrancarla vuelve abajo.
- profile key extra_drive: optional read-only second virtio-blk so an
overlay can ship guest-side shim libraries (/dev/vdb)
- SENS <name> protocol op: canvas-fed named values (built-in sensors /
buttons) served from PiInstance.sensor_state, pushed by the frontend
via the new pi_sensor_state WS message
- DISP <b64> protocol op: guest display commands forwarded to the
frontend as 'display' events (built-in screens)
- RaspberryPi3Bridge: onDisplay / onGpioPwm callbacks + setSensorState
- SimulatorCanvas hands piFamily boards their Pi bridge in
attachBuiltins (was ESP32-only)
Overlay QEMU-Linux boards are not Raspberry Pis: ProBoardDef.guestSetup
lets a board send one shell line at the boot prompt (hostname/PS1/clear)
to de-brand the generic image, sent before piBooted flips so uploads
cannot interleave; the workspace start button and power-on title carry
the board's own label for non raspberry-pi kinds; the compile console
line uses the board label instead of 'Raspberry Pi 3B'.
The QEMU branch of handleRun required compiledProgram, but Pi-family
boards never have one (handleCompile early-returns 'no compilation
needed' without producing firmware), so the toolbar Run always surfaced
'Compilation produced no firmware' instead of powering the board on.
Start them directly, same as the workspace Start button.
add_reader on the proto FIFO armed epoll but never delivered callbacks
under uvloop when the fd number recycled a just-closed socket fd
(nondeterministic per instance): the guest's GPIO lines sat unread in
the pipe and canvas LEDs stayed dark while serial kept flowing. Pump
the FIFO from a worker thread (select + os.read, like _watch_stderr)
and schedule _handle_gpio_line back onto the loop.
registerPiFamilyKind + ProBoardDef.piFamily route an overlay board through
the existing Raspberry Pi bridge path (WebSocket qemu, VFS panel, boot
terminal); register_pi_board_profile lets the overlay add the matching
PI_CONFIGS entry (cpu/image_set/...) at registration time.
Un crash entre ambos (el NameError de board_fqbn) dejaba defaults==rendered
con un sdkconfig stale, y el re-seed de kconfig no disparaba nunca mas en ese
dir persistente — medido: CONSOLE_SECONDARY quedo en usb_serial_jtag pese a
los defaults nuevos. El bloque de velxio_board.cmake pasa a despues.
El bloque de velxio_board.cmake usaba board_fqbn, que vivia en compile() y no
en la firma de _compile_in_dir: parametro nuevo con default None, propagado en
los dos call sites, y guard para el camino sin fqbn (tests directos).
Tres piezas:
- _arduino_board_flags lee <id>.build.board y <id>.build.cdc_on_boot de
boards.txt (cache, mismo patron que _arduino_variant).
- Cada compile escribe velxio_board.cmake (incluido OPTIONAL por el
CMakeLists ANTES de project(), asi los defines llegan a TODOS los
componentes — que Serial sea el CDC se decide dentro del core Arduino):
ARDUINO_<BOARD> siempre que boards.txt lo nombre (los sketches de vendor
hacen #ifdef ARDUINO_XIAO_ESP32S3), y ARDUINO_USB_CDC_ON_BOOT=1 +
ARDUINO_USB_MODE=1 cuando cdc_on_boot=1. USB_MODE va FORZADO a 1 (HWCDC
por USB-Serial-JTAG, la opcion de menu USBMode=hwcdc): el motor modela esa
consola, no la pila OTG/TinyUSB. Ir por archivo y no por env hace el flag
dependencia de configure: CMake reconfigura al cambiar.
- CONFIG_ESP_CONSOLE_SECONDARY_NONE en el sdkconfig: sin ella el ROM/IDF
espejan cada byte de log al USB y el monitor unico del emulador (que
mezcla ambos streams) mostraria el log entero dos veces en builds CDC.
setAdcVoltage/setAdcWaveform solo conocian el mapa del ESP32 clasico
(GPIO32..39): en un S3 devolvian false para TODOS los pines y el part no
podia alimentar el ADC. Ahora el shim consulta bridge.adcChannelForGpio si
existe (los puentes js exponen el mapa de su chip) y cae al mapa clasico
solo si no.
_normalize_options rellena arduinoVariant con _arduino_variant(board_fqbn),
que ya leia boards.txt; la plantilla ya consumia la clave. Sin esto todas las
placas S3 compilaban como esp32s3 generico: sin nombres Dx del XIAO y con
Serial en UART0, cuyo RX por defecto es GPIO44 = D7 del XIAO — un
pinMode(D7) tardio desinstala el driver y silencia los prints del loop
(medido en el banco con el ejemplo del Round Display).
El adaptador SPI del Esp32BridgeShim descartaba el MISO de los parts ('el worker
lo lleva por _spi_response'), cierto solo en la era QEMU: cualquier part SPI que
RESPONDE (una SD contestando CMD0) hablaba con nadie en modo js — medido como
SD.begin()=0 con sd_diskio reintentando CMD0 para siempre en el Round Display.
Ahora reenvia a bridge.setSpiResponse, que todos los puentes tienen: el de QEMU
lo manda al worker, y los motores js fijan el byte que su SpiForwarder devuelve
para ESTA transferencia (toda la cadena onByte corre sincrona dentro del
transfer del motor). El reposo lo restaura el decodificador compartido, que ya
completa cada byte ajeno con 0xff.
El boton conservaba animation:'none' inline fuera del estado requesting, y el
estilo inline gana a la clase: .velxio-ants quedaba con animationName none —
medido con getComputedStyle en staging. Inline solo mientras solicita permiso;
en el resto, undefined para que la clase anime.
El modulo es retrato con la botonera de pines en el borde INFERIOR y el sketch
dibuja en setRotation(1): segun el mapeo MADCTL del propio modelo (writePixel:
el borde superior del contenido rot-1 cae sobre el borde IZQUIERDO del canvas),
verlo derecho exige girar el modulo un cuarto horario — igual que en el banco
fisico. El mismo giro lleva los pines al borde izquierdo, de cara a la placa,
asi que los cables van rectos entre ambos en vez de por encima del cristal.
Cuatro tiras de gradiente repetido, una por borde, deslizando un periodo de
guion por ciclo — fondo y no border porque un borde CSS no puede animar su
dash-offset. currentColor hereda el color de estado (gris en reposo, rojo en
error). Se apaga en streaming/solicitando, y con prefers-reduced-motion.
Ojo del gato encerrado: el estilo inline usaba el shorthand background, que
pisa el background-image de la clase — pasa a backgroundColor.
Un sketch de ESP32-CAM existe para capturar, asi que esperar el click en el
toggle se lee como 'la camara esta rota': el guest arranca, esp_camera_init
completa contra el OV2640 modelado, y cam_hal agota el tiempo eternamente
esperando fotogramas que nadie envia — sin pedir siquiera el permiso.
Ahora la webcam se solicita al iniciar la ejecucion; el prompt del navegador ES
el consentimiento, y el toggle queda como apagador manual. Solo una vez por
ejecucion: parar el stream a mano no lo re-dispara.
Estaban a zIndex 0 con los componentes a 1: cualquier solape las escondia. El
caso que lo decide es el apilado — una XIAO posada en el zocalo del Round
Display debe ser lo que se ve, como la placa recien soltada queda arriba del
monton. Los montajes lado a lado no solapan, asi que nada mas cambia.
Como el iman de la breadboard pero para PLACAS: un componente puede declarar en
su elemento (estilo regla 6a, igual que pinInfo) que lleva un zocalo:
get boardSocket(): { anchorPin, accepts }
y una placa cuyo boardKind case con accepts, arrastrada cerca, se posa de golpe
con su pad anchorPin sobre el homonimo del zocalo. Un solo ancla basta porque
ambas rejillas comparten paso — esa es la gracia de un zocalo. Arrastrarla mas
alla de la tolerancia la suelta, sin estado que recordar.
Enganchado en los dos caminos de arrastre de placas (raton y tactil). El
overlay privado declara el zocalo sin que este repo sepa que existe.
Sin esta linea la variante se queda en el nombre del chip, asi que TODA placa
compila como dev-kit generico y variants/<VARIANTE>/pins_arduino.h nunca entra
en el include path: los nombres propios de la placa (D0..D10 en una XIAO, la
serigrafia de las M5) sencillamente no existen. Justo los sketches que la gente
copia de la wiki del fabricante.
El helper que deriva la variante del FQBN leyendo boards.txt ya estaba; faltaba
la plantilla que la consume. Sigue pendiente rellenar 'arduinoVariant' en
normalized para que surta efecto (ver F7.6 en velxio-prod).
Los dos ejemplos de camara (esp32cam-lcd-preview y esp32cam-webcam-demo) morian
con "esp_camera.h: No such file or directory".
No es que el ejemplo estuviera mal: es una regresion de la migracion. El arbol
viejo /opt/arduino-esp32 (2.x) traia un SDK precompilado con esp32-camera dentro,
en tools/sdk/<target>/include/esp32-camera/, para esp32, c3, s2 y s3. El
componente 3.x que usamos ahora NO lo trae, y su idf_component.yml tampoco
depende de el, asi que el header simplemente dejo de existir. Por eso funcionaba
antes y dejo de funcionar sin que nadie tocara el ejemplo.
Mezclar los headers de la 2.x en este build no es opcion — vienen con su SDK
precompilado y aqui compilamos contra IDF 5. Lo correcto en el mundo 3.x es
pedirle el componente al gestor: la plantilla no traia idf_component.yml, asi que
todo lo gestionado llegaba de rebote por el manifiesto de arduino-esp32, y lo que
el no arrastra hay que pedirlo explicitamente.
_detect_camera_usage() ve el include y _add_managed_components() escribe
main/idf_component.yml con espressif/esp32-camera. Verificado en el contenedor
sobre el mismo directorio que fallaba: el gestor descarga 2.1.7, el configure
termina con esp32-camera en la lista de componentes, y sketch.ino.cpp compila.
Tres cosas que salieron al tirar del hilo de los pull-ups.
1) ESP32_ADC_PIN_MAP era la tabla del ESP32 CLASICO aplicada a toda la familia, y
fallaba de dos maneras:
- S3: sus pines ADC son GPIO1..20, ninguno esta en esa tabla, asi que la
busqueda devolvia undefined y el listener del potenciometro no llegaba a
engancharse. El mando no hacia nada.
- C3/C6: GPIO0 SI esta en la tabla clasica, como ADC2_CH1 -> canal 9. Pero en
esos chips GPIO0 es ADC1_CH0 y sus motores toman un indice de canal de ADC1
(0..4). El valor se empujaba al canal 9, fuera de rango, descartado en
silencio mientras el firmware leia el 0. Una respuesta equivocada en vez de
ninguna, que es peor.
adcPinMapFor(boardKind) devuelve la tabla del chip.
2) El ejemplo de las gafas OLED en MicroPython declaraba sus botones con Pin.IN a
secas. Van entre 3V3 y el pin, asi que sin pull-down el pin queda FLOTANDO con
el boton abierto. El propio ejemplo ya lo avisaba en un comentario ("Add
Pin.PULL_DOWN if the pin floats") — ahora lo hace. Es un fallo de hardware de
verdad, no un artefacto del emulador.
3) gallery-run-gate.audit.test.ts: EditorToolbar bloquea el Run cuando el
verificador de circuito saca errores, asi que un circuito invalido no es
cosmetico, es la diferencia entre un ejemplo que arranca y uno que parece
muerto. Y esos defectos se esconden hasta que el circuito resuelve DE PUNTA A
PUNTA: c3-button llevaba un LED de 506 mA que nadie veia porque sus cables
apuntaban a pines inexistentes.
El test reproduce la puerta del Run tal cual (mismo snapshot de peor caso,
mismo buildInputFromStore, mismo verifyCircuit, ngspice de verdad) sobre los
227 ejemplos. Encontro tres mas sin resistencia en serie —nano-button-led,
mega-led-chase y mega-serial-control, 17 LEDs en total— y ahora quedan 0.
A partir de aqui, un ejemplo nuevo con un LED colgado del GPIO salta en CI.
Los dos colgaban el LED directo del GPIO. Con los cables del pulsador arreglados
el circuito por fin resuelve entero, y el verificador saca lo que llevaba ahi
desde siempre: 506,9 mA por un LED de 20 mA. Como un error de circuito bloquea la
ejecucion (EditorToolbar checkOrBlock devuelve false y saca el modal de
"ejecutar de todos modos"), el ejemplo se quedaba sin arrancar.
O sea que no era un fallo nuevo, era uno viejo que hasta ahora nadie podia ver
porque el circuito no llegaba a resolverse. 220 ohm en serie, igual que el resto
de ejemplos con LED (esp32-blink-led y esp32-doom ya lo hacian bien).
El canvas reenviaba la pulsacion de un pulsador cableado como
sendPinEvent(gpio, true), o sea "pulsado = ALTO". Para el idiom canonico de
Arduino — pin -> pulsador -> GND con INPUT_PULLUP, activo a nivel BAJO, que usan
13 de los 18 ejemplos con entrada de usuario — eso esta al reves: pulsar conducia
el pin a su nivel de REPOSO y soltarlo al ACTIVO. Ademas escribia directamente en
el latch de entrada del emulador por detras de connectDigitalInputsToMcu,
desincronizando la cache lastLevel de la que ese fichero se declara unico
escritor. La victima visible era esp32-doom: cuatro botones que hacian lo
contrario de lo que pulsabas.
No lo sustituyo por la polaridad opuesta, que seria la misma adivinanza al reves:
cuando el simulador resuelve entradas por el circuito (spiceDrivenInputs), el
nivel sale del propio cableado. El INPUT_PULLUP del guest se reporta ahora como
gpio_pull, estampa una resistencia de 45k al riel de 3V3, y cerrar el pulsador
cortocircuita el nodo contra la pata que tenga al otro lado. Sale bien tanto para
un boton a GND como para uno a 3V3, sin que nadie asuma nada. El atajo se queda
solo para simuladores que se salgan del modelo electrico.
Aparte, dos arreglos de cableado:
- boardPinToNumber devuelve -1 (no null) para pines de alimentacion y masa, y
la guarda comprobaba `=== null`. Asi que la pata de GND de cada boton colaba
y registraba un SEGUNDO par de listeners apuntando al pin -1.
- c3-button y pico-button-led cableaban sus pulsadores a los pines '1a' y '1b',
que NO EXISTEN: el wokwi-pushbutton expone 1.l, 2.l, 1.r y 2.r. Esos dos
cables llevaban colgando desde siempre, asi que esos botones no han
funcionado nunca. Pasan a 1.l / 2.l, como el resto de ejemplos.
boardPinToNumber no tenia rama para 'cardputer-adv', asi que caia al return
null del final: cada cable a su cabecera EXT o al Grove Port A se conectaba a
nada. La pieza quedaba en el lienzo, el sketch leia un pin muerto y nadie
avisaba de nada.
La rama compartida de esp32 tampoco habria servido: recorta en 39 y este board
saca G40 en la cabecera. El S3 tiene 48 GPIOs.
Arduino trata igual #include "Lib.h" que #include <Lib.h> para librerias, y
los ejemplos de los propios fabricantes usan la forma con comillas: los de
M5Stack para el Cardputer abren con #include "M5Cardputer.h". Al escanear solo
la forma con angulos, ese sketch no llegaba siquiera al resolutor de librerias y
moria con 'fatal error: M5Cardputer.h: No such file', mientras el mismo sketch
con angulos compilaba sin problema. Cualquiera que pegue un ejemplo oficial se
daba de bruces con esto.
Se pasan ademas los nombres de los ficheros del propio sketch, para que un
#include "Common.h" local siga siendo una cabecera del proyecto y no se
confunda con una libreria.
test/backend/unit/test_espidf_compiler.py cubre ambas formas, el espaciado, las
cabeceras internas de IDF y la cabecera propia del proyecto.
Se anclaba a la esquina inferior derecha del lienzo y tapaba una parte del area
util del circuito, que es justo donde se trabaja. Quien pana y hace zoom no lo
necesitaba para orientarse, asi que se retira entero en vez de esconderlo tras
un interruptor mas.
Se van el componente y su CSS (no los usaba nadie mas) y la linea del listado de
novedades que lo anunciaba, que ya no seria cierta.