velxio/test/test_circuit/plan/plan1.md

8.9 KiB

Plan: Simulación Eléctrica Completa con ngspice en Velxio

Contexto

Actualmente los cables de Velxio son puramente visuales, los valores de resistores/capacitores nunca se usan en ningún cálculo, y los LEDs se encienden si anode=HIGH && cathode=LOW sin importar la resistencia en serie. La meta es añadir simulación eléctrica real mediante ngspice corriendo en WASM en el browser.


Stack de librerías a instalar

# En frontend/
npm install eecircuit-engine           # ngspice compilado a WASM (MIT, 40MB, incluye tipos TS)
npm install circuit-json               # Formato JSON para circuitos (MIT, 24.5K descargas/sem)
npm install circuit-json-to-spice      # CircuitJSON → netlist ngspice (MIT, 10K descargas/sem)
npm install circuit-json-to-connectivity-map  # Grafo de conectividad (MIT, 27.8K descargas/sem)
npm install graphology                 # Grafo tipado para nodos eléctricos (MIT, 161K descargas/sem)

Por qué cada una

Librería Rol Alternativa descartada
eecircuit-engine Motor ngspice WASM, ya compilado, API TypeScript wokwi/ngspice-wasm (solo build system)
circuit-json Tipos TypeScript para componentes eléctricos Inventar tipos propios
circuit-json-to-spice Genera netlist SPICE desde grafo Escribir el generador a mano
circuit-json-to-connectivity-map Construye las nets (nodos eléctricos) desde conexiones Union-Find propio
graphology Representación y query del grafo de nodos dagre (solo layout, no análisis)

Arquitectura de la integración

useSimulatorStore (wires[], components[])
        ↓
 NetlistBuilder.ts
        ↓  [Union-Find sobre wires]
 circuit-json (nets + componentes)
        ↓
 circuit-json-to-connectivity-map (grafo de nodos)
        ↓
 circuit-json-to-spice (netlist SPICE string)
        ↓
 SpiceEngine.ts  →  eecircuit-engine (.runSim())
        ↓
 ResultType { variableNames[], data[].values[] }
        ↓
 Inyección en simulación:
   - ADC (potenciómetro, NTC, etc.) via AVRSimulator.setAnalogVoltage()
   - Brillo real de LEDs (corriente calculada)
   - Voltajes visibles en canvas (overlay)

Fase 1 — Grafo eléctrico y análisis DC estático (semana 1-2)

1.1 Construir las nets (nodos eléctricos) desde wires[]

Archivo nuevo: frontend/src/simulation/NetlistBuilder.ts

Algoritmo Union-Find sobre los wires de Velxio:

// Para wires como:
// { start: { componentId: 'arduino-uno', pinName: '13' }, end: { componentId: 'r1', pinName: '1' } }
// { start: { componentId: 'r1', pinName: '2' }, end: { componentId: 'led1', pinName: 'A' } }
// { start: { componentId: 'led1', pinName: 'C' }, end: { componentId: 'arduino-uno', pinName: 'GND' } }

// Genera nets:
// net_0: ['arduino-uno:13', 'r1:1']
// net_1: ['r1:2', 'led1:A']
// net_gnd: ['led1:C', 'arduino-uno:GND']

Reglas especiales:

  • Pin GND / GND.1 / etc. → siempre net 0 (ground)
  • Pin VCC / 5V / 3.3V → net de alimentación fija
  • Componentes pasivos (resistor, resistor-us) → dos terminales, la resistencia se aplica en SPICE

1.2 Generar CircuitJSON desde el estado de Velxio

Mapeo de metadataId → tipo circuit-json:

metadataId Velxio Tipo circuit-json Propiedades
resistor source_simple_resistor resistance: parseFloat(props.resistance)
led source_simple_led color: props.color
capacitor source_simple_capacitor capacitance: parseFloat(props.capacitance)
pushbutton source_simple_push_button
potentiometer source_simple_potentiometer
Arduino GPIO pin source_simple_voltage_source voltage: pinState ? 5 : 0

1.3 Generar netlist SPICE y correr análisis DC

import { circuitJsonToSpice } from 'circuit-json-to-spice'
import { Simulation } from 'eecircuit-engine'

const netlist = circuitJsonToSpice(circuitJson).toSpiceString() + '\n.op\n.end'
const sim = new Simulation()
await sim.start()
sim.setNetList(netlist)
const result = await sim.runSim()
// result.variableNames = ['v(net_0)', 'v(net_1)', 'v(net_gnd)', ...]
// result.data[i].values[0] = DC voltage at that node

1.4 Mostrar voltajes en canvas

Overlay SVG semitransparente sobre cada wire/nodo mostrando la tensión calculada. Activado con un botón de toggle " Voltages" en la toolbar.


Fase 2 — ADC real y brillo real de LEDs (semana 3)

2.1 Inyectar voltajes SPICE en el ADC del AVR

Cuando SPICE termina el análisis DC, los nodos conectados a pines analógicos del Arduino se inyectan:

// En AVRSimulator.ts ya existe:
pinManager.setAnalogVoltage(pin, voltage)

// Nuevo: al final de cada análisis SPICE
for (const analogPin of ['A0','A1','A2','A3','A4','A5']) {
  const netVoltage = spiceResult.getNodeVoltage(`net_${analogPin}`)
  if (netVoltage !== null) {
    pinManager.setAnalogVoltage(boardPinToNumber(analogPin), netVoltage)
  }
}

2.2 Brillo real de LEDs

Calcular corriente a través del LED desde SPICE y mapear a brillo:

// ngspice devuelve i(v_gpio13) — corriente por la fuente
// Corriente típica LED rojo: 10-20mA @ 2V forward
const current = spiceResult.getCurrentThrough('v_gpio13') // mA
const brightness = Math.min(1.0, current / 20)
ledElement.brightness = brightness

2.3 Recalcular SPICE en cada cambio de pin GPIO

Hook en PinManager.onPinChange() → debounce 50ms → recorrer análisis DC:

pinManager.onAnyPinChange(() => {
  clearTimeout(spiceDebounce)
  spiceDebounce = setTimeout(() => runSpiceAnalysis(), 50)
})

Fase 3 — Análisis transitorio para señales dinámicas (semana 4+)

Para señales PWM, filtros RC, circuitos osciladores:

// Reemplazar .op con .tran
const netlist = `...
.tran 1u 10m   // 1µs steps, 10ms total
.end`

// El resultado es una serie de tiempo
result.data[nodeIndex].values  // array de voltajes en t=0, 1µs, 2µs...

Usar un Web Worker para correr .tran sin bloquear la UI. Mostrar formas de onda en el Oscilloscope (ya existe en Velxio).


Archivos a crear / modificar

Archivo Acción Descripción
frontend/src/simulation/SpiceEngine.ts CREAR Wrapper sobre eecircuit-engine. Singleton. Métodos: init(), runDC(netlist), runTran(netlist)
frontend/src/simulation/NetlistBuilder.ts CREAR Convierte wires[] + components[] → CircuitJSON → netlist SPICE
frontend/src/simulation/ElectricalGraph.ts CREAR Grafo graphology de nodos. Métodos: buildFromWires(), getNodeVoltage(), getNet()
frontend/src/store/useSimulatorStore.ts MODIFICAR Añadir spiceVoltages: Record<string, number> al estado; trigger SPICE tras pin changes
frontend/src/simulation/AVRSimulator.ts MODIFICAR En pin change callbacks, llamar spiceEngine.runDC() con estado actual de pines
frontend/src/components/simulator/SimulatorCanvas.tsx MODIFICAR Overlay de voltajes en canvas; botón " Voltages"
frontend/src/simulation/parts/BasicParts.ts MODIFICAR LED usa corriente SPICE para brightness en vez de solo boolean
frontend/package.json MODIFICAR Añadir 5 dependencias

Modelo SPICE para los componentes más importantes

LED (modelo diodo SPICE)

.model LED_RED D(Is=1e-20 N=1.7 Rs=3 Cjo=50p)
D1 net_anode net_cathode LED_RED

Resistor

R1 net_1 net_2 220

Potenciómetro (divisor)

R_POT_A net_vcc wiper {value * pot_max}
R_POT_B wiper net_gnd {(1-value) * pot_max}

Pin GPIO (fuente controlada)

V_GPIO13 net_gpio13 0 DC {pinState ? 5.0 : 0.0}

Limitaciones conocidas

  1. eecircuit-engine pesa ~40 MB — cargar lazy (solo cuando se activa simulación eléctrica)
  2. circuit-json-to-spice no soporta LEDs aún (solo R, C, BJT) → hay que extender con modelos de diodo manuales
  3. Co-simulación AVR ↔ SPICE al mismo tiempo es compleja: se implementa como análisis quasi-estático (no simultáneo ciclo a ciclo)
  4. Componentes sin modelo (servomotor, NeoPixel, LCD) → se ignoran en el análisis SPICE, solo participan los passivos + diodos

Verificación end-to-end

  1. Cargar ejemplo "Blink LED" (Arduino → Resistor 220Ω → LED → GND)
  2. Activar toggle " Voltages"
  3. Cuando pin 13 = HIGH: el canvas debe mostrar ~3.3V en el nodo entre resistor y LED, ~0V en cátodo
  4. Serial monitor debe seguir funcionando (la simulación eléctrica es adicional, no reemplaza el AVR)
  5. Cargar ejemplo "Potenciómetro" → girar el control → analogRead(A0) debe reflejar la tensión del divisor real

Orden de implementación recomendado

  1. SpiceEngine.ts — wrapper básico, test con un netlist hardcodeado
  2. NetlistBuilder.ts — construir nets desde wires, generar CircuitJSON simple
  3. Overlay visual de voltajes en canvas
  4. Integrar en useSimulatorStore con debounce
  5. Inyección ADC
  6. Brillo real de LEDs
  7. Análisis transitorio en Web Worker