From 69ad0084d8666824b5859c1a530e7733e8722766 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 3 Mar 2026 09:44:51 -0500 Subject: [PATCH] Add TurnSignalLight with dedicated turn arrow - EXTENDS TrafficLight with turn signal arrow control - Activates turn arrow on RED-to-GREEN transition - Configurable turn duration with TON timer - SetLeftTurn, SetRightTurn, IsTurnActive methods - Auto-deactivates when leaving GREEN phase - Tests for direction setting, activation, and timeout --- .github/workflows/ci.yml | 10 ++++ src/FunctionBlocks/TurnSignalLight.st | 60 +++++++++++++++++++ tests/test_turn_signal.st | 86 +++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 src/FunctionBlocks/TurnSignalLight.st create mode 100644 tests/test_turn_signal.st diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08f2159..acc2c20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: src/Functions/TimingUtils.st \ src/FunctionBlocks/TrafficLight.st \ src/FunctionBlocks/PedestrianLight.st \ + src/FunctionBlocks/TurnSignalLight.st \ src/FunctionBlocks/VehicleDetector.st \ src/FunctionBlocks/TrafficLogger.st \ src/Programs/IntersectionController.st \ @@ -92,3 +93,12 @@ jobs: src/Interfaces/IControllable.st \ src/FunctionBlocks/TrafficLight.st \ --test tests/test_interface.st + + - name: Run turn signal tests + run: | + strucpp \ + src/DataTypes/TrafficTypes.st \ + src/Interfaces/IControllable.st \ + src/FunctionBlocks/TrafficLight.st \ + src/FunctionBlocks/TurnSignalLight.st \ + --test tests/test_turn_signal.st diff --git a/src/FunctionBlocks/TurnSignalLight.st b/src/FunctionBlocks/TurnSignalLight.st new file mode 100644 index 0000000..c0c2ad2 --- /dev/null +++ b/src/FunctionBlocks/TurnSignalLight.st @@ -0,0 +1,60 @@ +(* TurnSignalLight Function Block + Extends TrafficLight with a dedicated turn signal arrow. + The turn arrow activates during the RED-to-GREEN transition. *) + +FUNCTION_BLOCK TurnSignalLight EXTENDS TrafficLight + VAR_INPUT + turnEnabled : BOOL := TRUE; + turnDuration : TIME := T#10s; + END_VAR + + VAR_OUTPUT + turnArrowOn : BOOL := FALSE; + turnDirection : INT := 0; (* 0=none, 1=left, 2=right *) + END_VAR + + VAR + turnTimer : TON; + turnActive : BOOL := FALSE; + prevState : TrafficState := TrafficState.RED; + END_VAR + + METHOD PUBLIC SetLeftTurn : BOOL + turnDirection := 1; + SetLeftTurn := TRUE; + END_METHOD + + METHOD PUBLIC SetRightTurn : BOOL + turnDirection := 2; + SetRightTurn := TRUE; + END_METHOD + + METHOD PUBLIC IsTurnActive : BOOL + IsTurnActive := turnArrowOn; + END_METHOD + + (* Run parent TrafficLight state machine first *) + SUPER^(); + + (* Detect RED-to-GREEN transition to activate turn signal *) + IF prevState = TrafficState.RED AND currentState = TrafficState.GREEN THEN + IF turnEnabled AND turnDirection > 0 THEN + turnActive := TRUE; + END_IF; + END_IF; + + (* Run turn signal timer *) + turnTimer(IN := turnActive, PT := turnDuration); + IF turnTimer.Q THEN + turnActive := FALSE; + turnTimer(IN := FALSE, PT := turnDuration); + END_IF; + + (* Turn off arrow when not in GREEN phase *) + IF currentState <> TrafficState.GREEN THEN + turnActive := FALSE; + END_IF; + + turnArrowOn := turnActive; + prevState := currentState; +END_FUNCTION_BLOCK diff --git a/tests/test_turn_signal.st b/tests/test_turn_signal.st new file mode 100644 index 0000000..5f50d3b --- /dev/null +++ b/tests/test_turn_signal.st @@ -0,0 +1,86 @@ +(* Tests for the TurnSignalLight function block *) + +TEST 'TurnSignalLight starts with arrow off' + VAR ts : TurnSignalLight; END_VAR + + ts(); + ASSERT_FALSE(ts.turnArrowOn, 'Arrow should be off initially'); + ASSERT_EQ(ts.turnDirection, 0, 'No direction set'); +END_TEST + +TEST 'TurnSignalLight SetLeftTurn sets direction' + VAR ts : TurnSignalLight; END_VAR + + ts.SetLeftTurn(); + ASSERT_EQ(ts.turnDirection, 1, 'Should be left turn'); +END_TEST + +TEST 'TurnSignalLight SetRightTurn sets direction' + VAR ts : TurnSignalLight; END_VAR + + ts.SetRightTurn(); + ASSERT_EQ(ts.turnDirection, 2, 'Should be right turn'); +END_TEST + +TEST 'TurnSignalLight activates on RED to GREEN transition' + VAR + ts : TurnSignalLight; + timing : PhaseTiming; + END_VAR + + timing.redDuration := T#100ms; + timing.greenDuration := T#200ms; + timing.yellowDuration := T#50ms; + ts.timing := timing; + ts.turnDuration := T#80ms; + ts.SetLeftTurn(); + + (* Start in RED *) + ts(); + ASSERT_EQ(ts.currentState, TrafficState.RED, 'Start in RED'); + ASSERT_FALSE(ts.turnArrowOn, 'Arrow off during RED'); + + (* Transition to GREEN *) + ADVANCE_TIME(T#50ms); + ts(); + ADVANCE_TIME(T#50ms); + ts(); + ASSERT_EQ(ts.currentState, TrafficState.GREEN, 'Now GREEN'); + ASSERT_TRUE(ts.turnArrowOn, 'Arrow on during GREEN after RED'); +END_TEST + +TEST 'TurnSignalLight arrow turns off after duration' + VAR + ts : TurnSignalLight; + timing : PhaseTiming; + END_VAR + + timing.redDuration := T#100ms; + timing.greenDuration := T#300ms; + timing.yellowDuration := T#50ms; + ts.timing := timing; + ts.turnDuration := T#80ms; + ts.SetLeftTurn(); + + (* Get to GREEN *) + ts(); + ADVANCE_TIME(T#50ms); + ts(); + ADVANCE_TIME(T#50ms); + ts(); + ASSERT_TRUE(ts.turnArrowOn, 'Arrow should be on'); + + (* Wait for turn duration to expire *) + ADVANCE_TIME(T#40ms); + ts(); + ADVANCE_TIME(T#40ms); + ts(); + ASSERT_FALSE(ts.turnArrowOn, 'Arrow should be off after duration'); +END_TEST + +TEST 'TurnSignalLight IsTurnActive method' + VAR ts : TurnSignalLight; END_VAR + + ts(); + ASSERT_FALSE(ts.IsTurnActive(), 'Not active initially'); +END_TEST