fix(stop): preserve display state on Stop, only blank on Reset
Reporter feedback after 7aca3db: pressing Stop on the uno-7segment
example turned the 7-segment off, and pressing Start again left
random segments lit / no number at all. The previous fix made
resetPinStates() notify every listener with (pin, false) on both
Stop and Reset, which was right for Reset (full reboot) but wrong
for Stop:
- On Stop the AVR CPU is just paused. Internally it still has
PORTD=0xFF (or whatever the last drive was).
- resetPinStates blanked the pinStates cache + fan-out LOW
notifications. Display turns off, fine.
- On Start the CPU resumes from where it paused. avr8js's port
listener fires only for bits that CHANGED relative to its OWN
oldValue (which still holds the pre-stop value). If oldValue
matches the live register, no pinChange event fires for that
bit, and the display has no signal telling it to come back on.
Split the API into two methods:
resetPinStates() — soft cleanup, drops outputPins only. Used by
stopBoard. Cached pinStates and visual state
stay so the resume picks up where it left off.
hardResetPinStates() — full cleanup, drops outputPins + pinStates
and fan-outs (pin, false) to listeners.
Used by resetBoard (CPU starts at PC=0,
firmware re-drives every pin from setup()).
Updated the test helper clearAllPinManagerState to call
hardResetPinStates between tests so the same-state short-circuit in
triggerPinChange doesn't suppress fresh events.
All 32 vitest tests pass (AVRSimulator, interconnect-routing,
dual-arduino-software-serial, pin-position-rotation).
This commit is contained in:
parent
ddafe34fd9
commit
b2474bf5d1
|
|
@ -44,6 +44,11 @@ export function resetStore(useSimulatorStore: any): void {
|
|||
* current board list. Required between tests so that the same-state
|
||||
* short-circuit in PinManager.triggerPinChange doesn't suppress fresh
|
||||
* events.
|
||||
*
|
||||
* Uses hardResetPinStates (clear cache + classifications), not the
|
||||
* stopBoard-flavored resetPinStates (classifications only) which
|
||||
* leaves cached pin states intact so the display can resume after a
|
||||
* pause.
|
||||
*/
|
||||
export function clearAllPinManagerState(
|
||||
useSimulatorStore: any,
|
||||
|
|
@ -52,7 +57,11 @@ export function clearAllPinManagerState(
|
|||
const state = useSimulatorStore.getState();
|
||||
for (const b of state.boards ?? []) {
|
||||
const pm = getBoardPinManager(b.id);
|
||||
pm?.resetPinStates?.();
|
||||
if (pm?.hardResetPinStates) {
|
||||
pm.hardResetPinStates();
|
||||
} else {
|
||||
pm?.resetPinStates?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,22 +139,30 @@ export class PinManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clear cached pin states + output-pin classifications. Called by
|
||||
* stopBoard / resetBoard so the next Run starts without stale output
|
||||
* classifications from a previous session forcing premature V-source
|
||||
* emission.
|
||||
*
|
||||
* Also notifies every listener that its pin returned to LOW when the
|
||||
* previously-cached state was HIGH. Without this notification,
|
||||
* stateful display components (7-segment, dot-matrix, NeoPixel, LCD
|
||||
* backlight) keep the last lit pattern frozen on screen after the
|
||||
* user presses Reset / Stop — their visual state only updates when a
|
||||
* pinChange callback fires, and resetPinStates was clearing the
|
||||
* cache silently. Listeners that ignore the synthetic LOW (analog
|
||||
* sensors, buttons) are unaffected; their next external write
|
||||
* overrides the false anyway.
|
||||
* Soft cleanup for stopBoard: drop the MCU-output classification so
|
||||
* the next Run doesn't keep emitting stale V-sources on the SPICE
|
||||
* side from a pin the previous program had driven. The cached pin
|
||||
* STATES (`pinStates`) stay put — components that hold visual state
|
||||
* (7-segment, NeoPixel, LCD, dot-matrix) keep showing the last
|
||||
* pattern, which is what users expect from a pause/stop. On resume,
|
||||
* avr8js's port-listener fires only for bits that CHANGED relative
|
||||
* to its internal oldValue, so blanking the cache here would race:
|
||||
* if the sketch's port register matches what it was pre-stop, no
|
||||
* pinChange fires and the display would never recover.
|
||||
*/
|
||||
resetPinStates(): void {
|
||||
this.outputPins.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard reset for resetBoard / firmware reload: wipe every cached
|
||||
* state AND notify listeners that previously-HIGH pins are now LOW,
|
||||
* so stateful displays redraw cleanly to all-off. Reset implies the
|
||||
* MCU is restarting from 0 — there's no "resume" race to worry
|
||||
* about; the firmware will re-drive every pin from setup() once it
|
||||
* boots.
|
||||
*/
|
||||
hardResetPinStates(): void {
|
||||
const wereHigh: number[] = [];
|
||||
for (const [pin, state] of this.pinStates) {
|
||||
if (state) wereHigh.push(pin);
|
||||
|
|
|
|||
|
|
@ -1629,14 +1629,16 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
const sim = getBoardSimulator(boardId);
|
||||
if (sim) {
|
||||
sim.reset();
|
||||
// Drop MCU-output classification so the new program starts
|
||||
// clean — no stale V-sources from the previous run. The
|
||||
// resetPinStates call also notifies listeners that pins which
|
||||
// were HIGH are now LOW, so visual components (7-segment,
|
||||
// NeoPixel, LCD backlight) clear their stale state instead of
|
||||
// freezing on whatever pattern they had at the moment of
|
||||
// Reset.
|
||||
getBoardPinManager(boardId)?.resetPinStates();
|
||||
// Reset is a hard reboot — the CPU starts at PC=0, every pin
|
||||
// goes back to floating, every output classification is
|
||||
// dropped. The hardResetPinStates call also notifies
|
||||
// listeners that previously-HIGH pins are now LOW, so visual
|
||||
// components (7-segment, NeoPixel, LCD) clear their stale
|
||||
// pattern instead of freezing on whatever was lit at the
|
||||
// moment of Reset. The Stop path uses the lighter
|
||||
// resetPinStates() that ONLY drops the output classification
|
||||
// (preserves the display so it can resume) — see PinManager.
|
||||
getBoardPinManager(boardId)?.hardResetPinStates();
|
||||
// NOTE: do NOT reassign sim.onSerialData here. sim.reset()
|
||||
// recreates the USART but the new usart.onByteTransmit
|
||||
// already chains through `this.onSerialData`, which is the
|
||||
|
|
|
|||
Loading…
Reference in New Issue