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