Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 7 additions & 4 deletions src/FunctionBlocks/PedestrianLight.st
Original file line number Diff line number Diff line change
Expand Up @@ -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 *)
Expand Down
85 changes: 85 additions & 0 deletions tests/test_pedestrian_timeout_fix.st
Original file line number Diff line number Diff line change
@@ -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