(* * 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