From d4a5283f4152cf7d002dd2b1cdbf0925ea32be37 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 3 Mar 2026 09:45:51 -0500 Subject: [PATCH] Fix pedestrian walk timer not resetting on state change When traffic transitions away from RED while a pedestrian walk is active, the walk and flash timers were not being reset. This caused stale timer state to carry over into the next walk cycle, potentially shortening or skipping the walk phase entirely. Reset both walkTimer and flashTimer when cancelling a walk due to traffic state change. --- .github/workflows/ci.yml | 9 +++ src/FunctionBlocks/PedestrianLight.st | 11 ++-- tests/test_pedestrian_timeout_fix.st | 85 +++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 tests/test_pedestrian_timeout_fix.st diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08f2159..a0b99f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,3 +92,12 @@ jobs: src/Interfaces/IControllable.st \ src/FunctionBlocks/TrafficLight.st \ --test tests/test_interface.st + + - name: Run pedestrian timeout fix tests + run: | + strucpp \ + src/DataTypes/TrafficTypes.st \ + src/Interfaces/IControllable.st \ + src/FunctionBlocks/TrafficLight.st \ + src/FunctionBlocks/PedestrianLight.st \ + --test tests/test_pedestrian_timeout_fix.st diff --git a/src/FunctionBlocks/PedestrianLight.st b/src/FunctionBlocks/PedestrianLight.st index d00d8bc..89f060e 100644 --- a/src/FunctionBlocks/PedestrianLight.st +++ b/src/FunctionBlocks/PedestrianLight.st @@ -71,13 +71,16 @@ FUNCTION_BLOCK PedestrianLight EXTENDS TrafficLight END_IF; END_IF; ELSE - (* Not safe to walk — reset to DONT_WALK *) + (* Not safe or not requested — reset to DONT_WALK *) IF currentState <> TrafficState.RED THEN - pedestrianState := PedestrianState.DONT_WALK; walkRequest := FALSE; - flashCount := 0; - flashOn := FALSE; END_IF; + pedestrianState := PedestrianState.DONT_WALK; + flashCount := 0; + flashOn := FALSE; + (* Reset timers to prevent stale state on next walk cycle *) + walkTimer(IN := FALSE, PT := walkDuration); + flashTimer(IN := FALSE, PT := T#500ms); END_IF; (* Update pedestrian signal outputs *) diff --git a/tests/test_pedestrian_timeout_fix.st b/tests/test_pedestrian_timeout_fix.st new file mode 100644 index 0000000..5cfb0f3 --- /dev/null +++ b/tests/test_pedestrian_timeout_fix.st @@ -0,0 +1,85 @@ +(* Tests for pedestrian timeout fix — verifies timer reset on state change *) + +TEST 'Walk timer resets when traffic leaves RED' + VAR + ped : PedestrianLight; + timing : PhaseTiming; + END_VAR + + timing.redDuration := T#500ms; + timing.greenDuration := T#100ms; + timing.yellowDuration := T#50ms; + ped.timing := timing; + ped.walkDuration := T#200ms; + + (* Start walking during RED *) + ped.walkRequest := TRUE; + ped(); + ASSERT_EQ(ped.pedestrianState, PedestrianState.WALK, 'Walking during RED'); + + (* Advance partially through walk duration *) + ADVANCE_TIME(T#50ms); + ped(); + + (* Force traffic to GREEN by advancing past red duration *) + ADVANCE_TIME(T#450ms); + ped(); + ASSERT_EQ(ped.currentState, TrafficState.GREEN, 'Traffic now GREEN'); + ASSERT_EQ(ped.pedestrianState, PedestrianState.DONT_WALK, 'Walk cancelled'); + + (* Go through GREEN + YELLOW back to RED *) + ADVANCE_TIME(T#50ms); + ped(); + ADVANCE_TIME(T#50ms); + ped(); + ADVANCE_TIME(T#25ms); + ped(); + ADVANCE_TIME(T#25ms); + ped(); + + (* Should be back in RED now *) + ASSERT_EQ(ped.currentState, TrafficState.RED, 'Back to RED'); + + (* Request walk again during new RED phase *) + ped.walkRequest := TRUE; + ped(); + ASSERT_EQ(ped.pedestrianState, PedestrianState.WALK, 'Can walk again'); + + (* Walk timer should start fresh, not carry over old elapsed time. + Only 100ms elapsed out of 200ms duration — should still be walking. *) + ADVANCE_TIME(T#100ms); + ped(); + ASSERT_EQ(ped.pedestrianState, PedestrianState.WALK, + 'Still walking — timer was properly reset'); +END_TEST + +TEST 'Flash timer resets when traffic leaves RED' + VAR + ped : PedestrianLight; + timing : PhaseTiming; + END_VAR + + timing.redDuration := T#500ms; + timing.greenDuration := T#100ms; + timing.yellowDuration := T#50ms; + ped.timing := timing; + ped.walkDuration := T#50ms; + + (* Walk and transition to flashing *) + ped.walkRequest := TRUE; + ped(); + ADVANCE_TIME(T#25ms); + ped(); + ADVANCE_TIME(T#25ms); + ped(); + ASSERT_EQ(ped.pedestrianState, PedestrianState.FLASHING, 'Should be flashing'); + + (* Simulate early exit — flash count and timer should reset cleanly *) + ped.walkRequest := FALSE; + ped(); + + (* Re-request walk *) + ped.walkRequest := TRUE; + ped(); + ASSERT_EQ(ped.pedestrianState, PedestrianState.WALK, 'Fresh walk cycle'); +END_TEST