<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.0">
  <POU Name="FB_LineSim" Id="{83bd0d62-650e-4191-9410-a31c2eb07c02}" SpecialFunc="None">
    <Declaration><![CDATA[FUNCTION_BLOCK FB_LineSim
VAR_IN_OUT
    Line : ST_Line;
END_VAR
VAR
    tick        : UDINT;
    axisSim     : ARRAY[1..3, 1..4] OF FB_AxisSim;
    iStation    : INT;
    iAxis       : INT;
    iAlarm      : INT;
    rotatorBits : DWORD;
END_VAR
]]></Declaration>
    <Implementation>
      <ST><![CDATA[tick := tick + 1;

FOR iStation := 1 TO 3 DO
    Line.Stations[iStation].Online    := TRUE;
    Line.Stations[iStation].Heartbeat := tick;

    // Axes — delegate to FB_AxisSim for per-cycle motion.
    FOR iAxis := 1 TO 4 DO
        axisSim[iStation, iAxis](
            Enable := Line.Running,
            Axis   := Line.Stations[iStation].Axes[iAxis]
        );
    END_FOR

    // Rolling I/O patterns to give subscribers something to watch.
    rotatorBits := SHL(DWORD#1, (tick MOD 32));
    Line.Stations[iStation].IO.LastInputMask  := rotatorBits;
    Line.Stations[iStation].IO.LastOutputMask := NOT rotatorBits;
    Line.Stations[iStation].IO.CycleCounter   := tick;

    // Flip a couple of DI/DOs so BOOL arrays see changes.
    Line.Stations[iStation].IO.DigitalInputs[(tick MOD 32)]  := NOT Line.Stations[iStation].IO.DigitalInputs[(tick MOD 32)];
    Line.Stations[iStation].IO.DigitalOutputs[(tick MOD 32)] := Line.Stations[iStation].IO.DigitalInputs[(tick MOD 32)];

    // Walk an analog channel through a sine.
    Line.Stations[iStation].IO.AnalogInputs[0]  := LREAL_TO_REAL(10.0 + 5.0 * SIN(UDINT_TO_LREAL(tick + UDINT#100 * UINT_TO_UDINT(INT_TO_UINT(iStation))) * 0.05));
    Line.Stations[iStation].IO.AnalogOutputs[0] := Line.Stations[iStation].IO.AnalogInputs[0] * 2.0;

    // Rotate one alarm per station so IAlarmSource has state transitions.
    iAlarm := INT_TO_USINT(1 + (DINT_TO_INT(UDINT_TO_DINT(tick)) MOD 16));
    Line.Stations[iStation].Alarms[iAlarm].Active       := (tick MOD 32) >= 16;
    Line.Stations[iStation].Alarms[iAlarm].Severity     := E_Severity.Warning;
    Line.Stations[iStation].Alarms[iAlarm].Code         := 1000 + DINT(iAlarm);
    Line.Stations[iStation].Alarms[iAlarm].Message      := CONCAT('alarm-', TO_STRING(iAlarm));
    Line.Stations[iStation].Alarms[iAlarm].Source       := CONCAT('Station-', TO_STRING(iStation));
END_FOR

// Stats — monotonic production counters.
Line.Stats.UnitsProduced  := Line.Stats.UnitsProduced + 1;
IF (tick MOD 100) = 0 THEN
    Line.Stats.UnitsRejected := Line.Stats.UnitsRejected + 1;
    Line.Stats.RejectReasons[1 + ((DINT_TO_INT(UDINT_TO_DINT(tick / 100))) MOD 5)] :=
        Line.Stats.RejectReasons[1 + ((DINT_TO_INT(UDINT_TO_DINT(tick / 100))) MOD 5)] + 1;
END_IF
Line.Stats.UpTimeSeconds  := tick / 100;  // 10 ms task tick -> approx seconds
]]></ST>
    </Implementation>
  </POU>
</TcPlcObject>
