Merge pull request #192 from davidmonterocrespo24/fix/spice-led-pipeline

Fix/spice led pipeline
This commit is contained in:
David Montero Crespo 2026-05-18 22:48:21 -03:00 committed by GitHub
commit b98b1bf1df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 6 deletions

View File

@ -369,11 +369,29 @@ export const DynamicComponent: React.FC<DynamicComponentProps> = ({
// Helper to find Arduino pin connected to a component pin.
// Traces through electrically-transparent passive components so that a
// circuit like LED-cathode → resistor → GND returns -1 (GND) instead
// of null. Delegates to the module-level `traceDetailed` (shared with
// getPinResolver).
const getArduinoPin = (componentPinName: string): number | null => {
// of null. Delegates to the module-level `traceDetailed`.
//
// Two call shapes are supported because this same function is passed
// BOTH to PartSimulationRegistry handlers (which call it as
// `getArduinoPin(componentPinName)`) AND to `createDefaultPinResolver`
// as a `PinTracer` (which calls it as `tracePin(componentId,
// componentPinName)`). When the second arg is present we treat the
// first as a componentId override; otherwise we use the closure-
// captured component id. The previous single-arg signature silently
// matched the PinTracer 2-arg call as `(componentId, undefined)` —
// traceDetailed then looked up a pin literally named "rgb-led-1" on
// component "rgb-led-1", got null, and the PinResolver reported
// FLOATING forever (the canonical "wokwi-rgb-led never lights up
// even though SPICE is driving R/G/B" symptom).
const getArduinoPin = (
componentIdOrPin: string,
maybePinName?: string,
): number | null => {
const state = useSimulatorStore.getState();
return traceDetailed(state, id, componentPinName, 0).arduinoPin;
const componentId = maybePinName !== undefined ? componentIdOrPin : id;
const componentPinName =
maybePinName !== undefined ? maybePinName : componentIdOrPin;
return traceDetailed(state, componentId, componentPinName, 0).arduinoPin;
};
// PinResolver factory — Phase 0 of the mixed-mode simulator project

View File

@ -48,7 +48,11 @@ const DEFAULT_SUITE = [
expectGradient: true,
note: 'Brightness must hit 3+ distinct non-zero values (smooth fade)' },
{ slug: 'rgb-led', label: 'RGB 3-PWM driven', simulateMs: 12000,
leafCheck: 'pwmActive' },
leafCheck: 'rgbLed',
note: 'wokwi-rgb-led ledRed/Green/Blue MUST cycle — catches PinTracer signature bug' },
{ slug: 'uno-7segment', label: '7-segment counter', simulateMs: 8000,
leafCheck: 'sevenSegment',
note: 'wokwi-7segment.values MUST hit ≥4 distinct digit patterns — catches the PinResolver-floating bug for any handler that subscribes per pin' },
];
// ── CDP plumbing ──────────────────────────────────────────────────────────
@ -181,6 +185,57 @@ async function runOne(cdp, ex) {
return result;
}
if (ex.leafCheck === 'rgbLed') {
// wokwi-rgb-led exposes ledRed/Green/Blue (0-255). Visual correctness
// requires each channel to take ≥2 distinct values during the cycle.
// Without that, the SPICE side may be driving the pins but the visual
// is stuck (the canonical PinTracer-signature bug).
const rgbSamples = await cdp.eval(`
(async () => {
const out = [];
for (let i = 0; i < 16; i++) {
const el = document.querySelector('wokwi-rgb-led');
out.push({ R: el?.ledRed ?? null, G: el?.ledGreen ?? null, B: el?.ledBlue ?? null });
await new Promise(r => setTimeout(r, 400));
}
return out;
})()
`, { awaitPromise: true });
if (!rgbSamples || rgbSamples[0].R === null) {
result.fail = 'no wokwi-rgb-led element found';
return result;
}
const rs = new Set(rgbSamples.map(s => s.R));
const gs = new Set(rgbSamples.map(s => s.G));
const bs = new Set(rgbSamples.map(s => s.B));
result.distinctR = rs.size; result.distinctG = gs.size; result.distinctB = bs.size;
if (rs.size < 2 || gs.size < 2 || bs.size < 2) {
result.fail = `RGB channel(s) stuck — distinct R=${rs.size} G=${gs.size} B=${bs.size} (each MUST be ≥2)`;
}
return result;
}
if (ex.leafCheck === 'sevenSegment') {
// wokwi-7segment exposes `values` (length-8 array, segments a..g + dp).
// A working counter must hit ≥4 distinct patterns during the run.
const patterns = await cdp.eval(`
(async () => {
const out = new Set();
for (let i = 0; i < 12; i++) {
const el = document.querySelector('wokwi-7segment');
if (el?.values) out.add(Array.from(el.values).join(','));
await new Promise(r => setTimeout(r, 500));
}
return [...out];
})()
`, { awaitPromise: true });
result.distinctPatterns = patterns?.length ?? 0;
if (!patterns || patterns.length < 4) {
result.fail = `7-seg only ${patterns?.length ?? 0} distinct pattern(s) — display stuck or not driving segments`;
}
return result;
}
// Standard LED assertions.
if (samples[0].leds.length === 0) {
// No wokwi-led on canvas — only validate SPICE pin activity.
@ -271,7 +326,11 @@ async function main() {
failed++;
} else {
let detail;
if (r.outputPinCount != null && r.maxBrightness == null) {
if (r.distinctR != null) {
detail = `RGB channels distinct R=${r.distinctR} G=${r.distinctG} B=${r.distinctB}`;
} else if (r.distinctPatterns != null) {
detail = `7-seg patterns=${r.distinctPatterns}`;
} else if (r.outputPinCount != null && r.maxBrightness == null) {
detail = `no canvas LED, ${r.outputPinCount} pin(s) driven`;
} else if (r.outputPinCount != null) {
detail = `${r.outputPinCount} pin(s) driven, max=${r.maxBrightness} min=${r.minBrightness}`;