/** * partUtils.ts — Shared simulation helpers * * Provides ADC voltage injection utilities used by both ComplexParts and * SensorParts, supporting both AVR (ATmega328p) and RP2040 boards. */ import type { AnySimulator } from './PartSimulationRegistry'; import { RP2040Simulator } from '../RP2040Simulator'; /** DOM event fired when a runtime part mutates a user-facing property. */ export const PROPERTY_CHANGE_EVENT = 'velxio:property-change'; export interface PropertyChangeDetail { componentId: string; propName: string; value: unknown; } /** * Dispatch a property change so the canvas can route it through * `updateComponent()`. Parts call this whenever a DOM / sensor-panel value * mutates so the SPICE netlist memo invalidates and the next `maybeSolve()` * picks up the change. Parts stay decoupled from Zustand — `SimulatorCanvas` * is the single listener that applies the update. */ export function emitPropertyChange(componentId: string, propName: string, value: unknown): void { if (typeof window === 'undefined') return; if (typeof window.dispatchEvent !== 'function' || typeof CustomEvent !== 'function') return; const detail: PropertyChangeDetail = { componentId, propName, value }; window.dispatchEvent(new CustomEvent(PROPERTY_CHANGE_EVENT, { detail })); } /** Read the ADC instance from the simulator (returns null if not initialized) */ export function getADC(avrSimulator: AnySimulator): any | null { return (avrSimulator as any).getADC?.() ?? null; } /** * Supply rail the analog front end of THIS board runs on, in volts. * * A part that builds a resistive divider has to use the rail it is actually * wired to. Assuming 5 V everywhere is not a small error on a 3.3 V board: it * pushes the computed node voltage above the ADC's full scale, so the reading * saturates and a whole stretch of the control slider reports the same value. * (An NTC divider assumed at 5 V reads 1.4 C on an ESP32 when the slider says * 25 C, and pins at full scale for everything below ~11 C — issue #233.) * * The split matches setAdcVoltage's own branches: AVR converts against a 5 V * reference, RP2040 and the ESP32 bridge against 3.3 V. */ export function analogRailVolts(simulator: AnySimulator): number { if (typeof (simulator as any).setAdcVoltage === 'function') return 3.3; // ESP32 bridge if (simulator instanceof RP2040Simulator) return 3.3; return 5.0; // AVR } /** * Write an analog voltage to an ADC channel, supporting AVR, RP2040, and ESP32. * * AVR: pins 14-19 → ADC channels 0-5, voltage stored directly (0-5V) * RP2040: GPIO 26-29 → ADC channels 0-3, converted to 12-bit value (0-4095) * ESP32: GPIO 32-39 → ADC1 channels 4-11, sent via WebSocket bridge * * Returns true if the voltage was successfully injected. */ export function setAdcVoltage(simulator: AnySimulator, pin: number, voltage: number): boolean { // ESP32 BridgeShim: delegate to bridge via WebSocket if (typeof (simulator as any).setAdcVoltage === 'function') { return (simulator as any).setAdcVoltage(pin, voltage); } // RP2040: GPIO26-29 → ADC channels 0-3 if (simulator instanceof RP2040Simulator) { if (pin >= 26 && pin <= 29) { const channel = pin - 26; // RP2040 ADC: 12-bit, 3.3V reference const adcValue = Math.round((voltage / 3.3) * 4095); simulator.setADCValue(channel, adcValue); return true; } console.warn(`[setAdcVoltage] RP2040 pin ${pin} is not an ADC pin (26-29)`); return false; } // AVR: pins 14-19 → ADC channels 0-5 if (pin < 14 || pin > 19) return false; const channel = pin - 14; const adc = getADC(simulator); if (!adc) return false; adc.channelValues[channel] = voltage; return true; }