From 73478b7433cd81d4334d44b69f63fccee9be9c4d Mon Sep 17 00:00:00 2001 From: davidmonterocrespo24 Date: Fri, 15 May 2026 17:39:16 +0200 Subject: [PATCH] =?UTF-8?q?feat(sim):=20Phase=205=20=E2=80=94=20migrate=20?= =?UTF-8?q?7-segment=20handler=20to=20PinResolver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 7-segment display was the canary case for the original problem: multiplexed displays with BJTs driving digit-select pins (COM/DIG) required the `[C, B]` shortcut in PASSIVE_PIN_PAIRS to even discover that the COM was wired to an Arduino pin. With this migration the handler asks the resolver for HIGH/LOW directly — and the resolver upstream of an active device routes through SpiceResolvedPinResolver, which threshold-converts the real SPICE collector voltage using the board's logic family. Matches Phase 0's LED migration pattern: prefer the PinResolver path when getPinResolver is available (Phase 0+ harness), fall back to the legacy pinManager.onPinChange + getArduinoPinHelper for tests / builds without it. Backwards-compatible — both digit-select (COM.1/COM.2 on 1-digit, DIG1..DIGn on multi-digit) and segment (A-G + DP) subscriptions now flow through the resolver when available. Seeds initial state from resolver.getCurrentState() so static-wire topologies (e.g. COM directly to GND) work at sim-start without an explicit edge event. Phase 5 progress: 2 of ~12 *Parts handlers migrated (LED, 7-segment). Remaining handlers (pushbutton, switch, 74HC595, etc.) follow the same pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/simulation/parts/ChipParts.ts | 69 ++++++++++++++++------ 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/frontend/src/simulation/parts/ChipParts.ts b/frontend/src/simulation/parts/ChipParts.ts index 457e33e7..19044b27 100644 --- a/frontend/src/simulation/parts/ChipParts.ts +++ b/frontend/src/simulation/parts/ChipParts.ts @@ -295,30 +295,52 @@ PartSimulationRegistry.register('74hc595', { // ─── 7-segment display (direct-drive, when connected directly to Arduino) ──── PartSimulationRegistry.register('7segment', { - attachEvents: (element, simulator, getArduinoPinHelper) => { + attachEvents: (element, simulator, getArduinoPinHelper, _componentId, getPinResolver) => { const pinManager = (simulator as any).pinManager; if (!pinManager) return () => {}; const unsubscribers: (() => void)[] = []; const s = get7SegState(element); + // Phase 5 migration of the mixed-mode simulator project: prefer the + // PinResolver path so digit-select pins driven through a BJT (the + // classic multiplexed 7-segment topology) get SPICE-aware HIGH/LOW + // detection instead of relying on the `[C, B]` PASSIVE_PIN_PAIRS + // shortcut. Falls back to direct pinManager + getArduinoPinHelper + // for harnesses or builds without Phase 0. + const useResolver = typeof getPinResolver === 'function'; + // Figure out which digit-select pins this display exposes and subscribe - // to whichever ones are actually wired to an Arduino pin. Polarity is - // determined by the helper's HIGH/LOW report (we treat HIGH = enabled). + // to whichever ones are actually wired. Polarity: HIGH = digit enabled. const digitPinNames: string[] = s.digits === 1 ? ['COM.1', 'COM.2'] // 1-digit: both COM pins map to digit 0 : Array.from({ length: s.digits }, (_, i) => `DIG${i + 1}`); // 2/3/4-digit: DIG1..DIGn let digitPinsWired = 0; for (let d = 0; d < digitPinNames.length; d++) { - const pin = getArduinoPinHelper(digitPinNames[d]); - if (pin === null) continue; - digitPinsWired++; + const pinName = digitPinNames[d]; const digitIdx = s.digits === 1 ? 0 : d; - unsubscribers.push( - pinManager.onPinChange(pin, (_: number, state: boolean) => { - handle7SegDigit(element, digitIdx, state); - }), - ); + if (useResolver) { + const resolver = getPinResolver!(pinName); + if (!resolver) continue; + digitPinsWired++; + // Seed initial state — important when the wire is already + // settled at sim start (e.g. examples that hard-wire a COM to GND). + handle7SegDigit(element, digitIdx, resolver.getCurrentState() === 'HIGH'); + unsubscribers.push( + resolver.onChange((state) => { + handle7SegDigit(element, digitIdx, state === 'HIGH'); + }), + ); + } else { + const pin = getArduinoPinHelper(pinName); + if (pin === null) continue; + digitPinsWired++; + unsubscribers.push( + pinManager.onPinChange(pin, (_: number, state: boolean) => { + handle7SegDigit(element, digitIdx, state); + }), + ); + } } // No digit-select pin wired → direct drive (common cathode tied to GND, // or a single display being lit unconditionally). Enable every digit @@ -330,13 +352,24 @@ PartSimulationRegistry.register('7segment', { // Subscribe to A-G + DP segment pins. for (let i = 0; i < SEGMENT_NAMES.length; i++) { const seg = SEGMENT_NAMES[i]; - const arduinoPin = getArduinoPinHelper(seg); - if (arduinoPin === null) continue; - unsubscribers.push( - pinManager.onPinChange(arduinoPin, (_: number, state: boolean) => { - handle7SegSegment(element, i, state); - }), - ); + if (useResolver) { + const resolver = getPinResolver!(seg); + if (!resolver) continue; + handle7SegSegment(element, i, resolver.getCurrentState() === 'HIGH'); + unsubscribers.push( + resolver.onChange((state) => { + handle7SegSegment(element, i, state === 'HIGH'); + }), + ); + } else { + const arduinoPin = getArduinoPinHelper(seg); + if (arduinoPin === null) continue; + unsubscribers.push( + pinManager.onPinChange(arduinoPin, (_: number, state: boolean) => { + handle7SegSegment(element, i, state); + }), + ); + } } return () => unsubscribers.forEach((u) => u());