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