Files
compliance-scanner-agent/examples/plc-demo/traffic_light.st
sharang 6d02b138c6
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 3m48s
CI / Deploy Dashboard (push) Successful in 2m51s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2m2s
feat(pipeline): PLC/SPS control-logic security scanner (IEC 61131-3) (#162)
2026-07-16 08:31:43 +00:00

93 lines
3.2 KiB
Smalltalk

(*
* Pedestrian-crossing traffic-light controller.
*
* Structure adapted from the classic OpenPLC "traffic light" example
* (github.com/thiagoralves/OpenPLC_v3 examples) a timed state machine
* driving vehicle + pedestrian lamps, extended with a SCADA/Modbus link
* and a maintenance override so it reads like a real deployed program.
*
* The control logic itself is sound; the security-relevant defects are the
* kind that slip into field code under deadline: a hardcoded SCADA password,
* a cleartext Modbus/TCP master, and a maintenance mode that drops the
* pedestrian safety permit. Everything else should stay quiet.
*)
PROGRAM TrafficLight
VAR
State : INT := 0; (* 0 GreenVeh, 1 Amber, 2 RedVeh/WalkPed, 3 FlashPed *)
Tmr : TON;
StateElapsed : TIME;
CycleMs : DINT := 0;
(* Lamp outputs *)
VehGreen : BOOL := FALSE;
VehAmber : BOOL := FALSE;
VehRed : BOOL := FALSE;
PedWalk : BOOL := FALSE;
PedStop : BOOL := TRUE;
(* Pedestrian safety permit must be TRUE before the WALK phase asserts *)
PedPermit : BOOL := TRUE;
PedButton : BOOL := FALSE;
(* SCADA / remote monitoring *)
ScadaUser : STRING := 'operator';
ScadaPassword : STRING := 'Tr@ffic2019'; (* hardcoded SCADA credential *)
ModbusReady : BOOL := FALSE;
(* Maintenance override *)
MaintMode : BOOL := FALSE;
LampCount : INT := 5;
DutyPct : INT;
END_VAR
(* ---- SCADA uplink: publish state to the control room over Modbus/TCP ---- *)
IF NOT ModbusReady THEN
Modbus_TCP_Master(IP := '10.20.0.5', PORT := 502, AUTH := FALSE, USER := ScadaUser, PASS := ScadaPassword);
ModbusReady := TRUE;
END_IF;
(* ---- Duty-cycle for the flashing pedestrian lamp (guarded division) ---- *)
IF LampCount <> 0 THEN
DutyPct := (CycleMs * 100) / LampCount;
END_IF;
(* ---- Maintenance override: flash amber, hand control to the technician ---- *)
IF MaintMode THEN
VehGreen := FALSE;
VehRed := FALSE;
VehAmber := NOT VehAmber;
PedPermit := FALSE; (* drops the pedestrian safety permit in code *)
PedWalk := FALSE;
PedStop := TRUE;
ELSE
(* ---- Normal timed state machine ---- *)
Tmr(IN := TRUE, PT := T#5s);
StateElapsed := Tmr.ET;
CASE State OF
0: (* vehicles go, pedestrians stop *)
VehGreen := TRUE; VehAmber := FALSE; VehRed := FALSE;
PedWalk := FALSE; PedStop := TRUE;
IF PedButton AND Tmr.Q THEN
State := 1; Tmr(IN := FALSE);
END_IF;
1: (* amber transition *)
VehGreen := FALSE; VehAmber := TRUE;
IF Tmr.Q THEN State := 2; Tmr(IN := FALSE); END_IF;
2: (* vehicles stop, pedestrians walk only if permitted *)
VehAmber := FALSE; VehRed := TRUE;
IF PedPermit THEN
PedWalk := TRUE; PedStop := FALSE;
END_IF;
IF Tmr.Q THEN State := 3; Tmr(IN := FALSE); END_IF;
3: (* flashing don't-walk before returning to green *)
PedWalk := NOT PedWalk;
IF Tmr.Q THEN
State := 0; PedButton := FALSE; Tmr(IN := FALSE);
END_IF;
ELSE
State := 0;
END_CASE;
END_IF;
END_PROGRAM