diff --git a/DUTs/ST_TechnosoftMotionError.TcDUT b/DUTs/ST_TechnosoftMotionError.TcDUT
new file mode 100644
index 00000000..18d9a89e
--- /dev/null
+++ b/DUTs/ST_TechnosoftMotionError.TcDUT
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/I_SafetyProvider.TcIO b/DUTs/Safety/I_SafetyProvider.TcIO
new file mode 100644
index 00000000..6216f7fc
--- /dev/null
+++ b/DUTs/Safety/I_SafetyProvider.TcIO
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_LEDState.TcDUT b/DUTs/Safety/PNOZ/ST_LEDState.TcDUT
new file mode 100644
index 00000000..a897264a
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_LEDState.TcDUT
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_MCCStatusIn.TcDUT b/DUTs/Safety/PNOZ/ST_MCCStatusIn.TcDUT
new file mode 100644
index 00000000..5c8f54e5
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_MCCStatusIn.TcDUT
@@ -0,0 +1,27 @@
+
+
+
+ IPC, will not change from cabinet to cabinet
+TYPE ST_MCCStatusIn :
+STRUCT
+ bHeartbeat: BIT; //The loop-back signal of the heartbeat signal from the PLC-to-PNOZ-to-PLC.
+ bEStopOK: BIT; //If no E-Stop connected to this cabinet is pressed, this bit is TRUE. When pressed, it is FALSE.
+ bRemote: BIT; //When remote mode is active, this allows remote resetting of E-Stop zones from EPICS/NICOS. Remote is active when the physical doors/access into said instruments are locked.
+ bFault: BIT; //Feedback to safety controller that fault has been acknowledged
+ bReserved4: BIT;
+ bReserved5: BIT;
+ bReserved6: BIT;
+ bReserved7: BIT;
+ bReserved8: BIT;
+ bReserved9: BIT;
+ bReserved10: BIT;
+ bReserved11: BIT;
+ bReserved12: BIT;
+ bReserved13: BIT;
+ bReserved14: BIT;
+ bReserved15: BIT;
+END_STRUCT
+END_TYPE
+]]>
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_MCCStatusOut.TcDUT b/DUTs/Safety/PNOZ/ST_MCCStatusOut.TcDUT
new file mode 100644
index 00000000..ef8a4697
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_MCCStatusOut.TcDUT
@@ -0,0 +1,27 @@
+
+
+
+ PNOZ, will not change from cabinet to cabinet
+TYPE ST_MCCStatusOut :
+STRUCT
+ bHeartbeat: BIT; //Pulsed every ~200ms by the PLC-to-PNOZ, the PNOZ is expected to loop back this signal.
+ bReserved1: BIT;
+ bReserved2: BIT;
+ bErrAck: BIT; //If there is a fault requiring intervention (from cabinet), CabAck will clear it
+ bReserved4: BIT;
+ bReserved5: BIT;
+ bSTOFbk: BIT;
+ bSTO_OK: BIT;
+ bReserved8: BIT;
+ bReserved9: BIT;
+ bReserved10: BIT;
+ bReserved11: BIT;
+ bReserved12: BIT;
+ bReserved13: BIT;
+ bReserved14: BIT;
+ bReserved15: BIT;
+END_STRUCT
+END_TYPE
+]]>
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_PNOZCommDiag.TcDUT b/DUTs/Safety/PNOZ/ST_PNOZCommDiag.TcDUT
new file mode 100644
index 00000000..4c2dc0df
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_PNOZCommDiag.TcDUT
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_PNOZEtherCATIn.TcDUT b/DUTs/Safety/PNOZ/ST_PNOZEtherCATIn.TcDUT
new file mode 100644
index 00000000..abbef8e8
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_PNOZEtherCATIn.TcDUT
@@ -0,0 +1,15 @@
+
+
+
+ PLC EtherCAT input data copied from the raw %I* process image.
+TYPE ST_PNOZEtherCATIn :
+STRUCT
+ stMCCStatusIn: ST_MCCStatusIn; //Input Byte 00..01
+ stMCC_CustomStatusIn: ST_MCC_CustomStatusIn; //Input Byte 02..03
+ astSafetyFunctionIn: ARRAY[1..GVL_APP.nNUM_OF_SAFETY_FUNCTIONS] OF ST_SafetyFunctionIn; //Input Byte 04..15
+ stLEDState: ST_LEDState; //Input Byte 16
+END_STRUCT
+END_TYPE
+]]>
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_PNOZEtherCATOut.TcDUT b/DUTs/Safety/PNOZ/ST_PNOZEtherCATOut.TcDUT
new file mode 100644
index 00000000..df215d62
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_PNOZEtherCATOut.TcDUT
@@ -0,0 +1,14 @@
+
+
+
+ PNOZ EtherCAT output data mapped to the raw %Q* process image.
+TYPE ST_PNOZEtherCATOut :
+STRUCT
+ stMCCStatusOut: ST_MCCStatusOut; //Output Byte 00..01
+ stMCC_CustomStatusOut: ST_MCC_CustomStatusOut; //Output Byte 02..03
+ astSafetyFunctionOut: ARRAY[1..GVL_APP.nNUM_OF_SAFETY_FUNCTIONS] OF ST_SafetyFunctionOut; //Output Byte 04..15
+END_STRUCT
+END_TYPE
+]]>
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_SafetyFunctionIn.TcDUT b/DUTs/Safety/PNOZ/ST_SafetyFunctionIn.TcDUT
new file mode 100644
index 00000000..335a30a9
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_SafetyFunctionIn.TcDUT
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/PNOZ/ST_SafetyFunctionOut.TcDUT b/DUTs/Safety/PNOZ/ST_SafetyFunctionOut.TcDUT
new file mode 100644
index 00000000..f72d9d6c
--- /dev/null
+++ b/DUTs/Safety/PNOZ/ST_SafetyFunctionOut.TcDUT
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/ST_Safety.TcDUT b/DUTs/Safety/ST_Safety.TcDUT
new file mode 100644
index 00000000..09f0993a
--- /dev/null
+++ b/DUTs/Safety/ST_Safety.TcDUT
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/ST_SafetyIn.TcDUT b/DUTs/Safety/ST_SafetyIn.TcDUT
new file mode 100644
index 00000000..21ac9854
--- /dev/null
+++ b/DUTs/Safety/ST_SafetyIn.TcDUT
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DUTs/Safety/ST_SafetyOut.TcDUT b/DUTs/Safety/ST_SafetyOut.TcDUT
new file mode 100644
index 00000000..2850524d
--- /dev/null
+++ b/DUTs/Safety/ST_SafetyOut.TcDUT
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GVLs/GVL_Safety.TcGVL b/GVLs/GVL_Safety.TcGVL
new file mode 100644
index 00000000..8430e5a8
--- /dev/null
+++ b/GVLs/GVL_Safety.TcGVL
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/POUs/FB_PulseGenerator.TcPOU b/POUs/FB_PulseGenerator.TcPOU
new file mode 100644
index 00000000..d316525e
--- /dev/null
+++ b/POUs/FB_PulseGenerator.TcPOU
@@ -0,0 +1,97 @@
+
+
+
+
+
+ tUsedFlashTime THEN
+ tGapTime := tFlashPeriod - tUsedFlashTime;
+ELSE
+ tGapTime := tOffTime; //Fallback gap if configured period is too short
+END_IF
+
+//Select active timer phase
+IF bInGap THEN
+ tActiveTime := tGapTime;
+ bOut := FALSE;
+ELSIF bOut THEN
+ tActiveTime := tOnTime;
+ELSE
+ tActiveTime := tOffTime;
+END_IF
+
+//Start new timer phase
+IF NOT bTimerRun THEN
+ bTimerRun := TRUE;
+ tonTimer(IN := FALSE, PT := tActiveTime);
+END_IF
+
+//Run timer
+tonTimer(IN := TRUE, PT := tActiveTime);
+
+//Phase complete
+IF tonTimer.Q THEN
+ tonTimer(IN := FALSE, PT := tActiveTime);
+ bTimerRun := FALSE;
+
+ IF bInGap THEN
+ bInGap := FALSE;
+ nFlashCounter := 0;
+ bOut := TRUE; //Start next flash group directly after gap
+ ELSE
+ IF bOut THEN
+ bOut := FALSE;
+ nFlashCounter := nFlashCounter + 1;
+
+ IF nFlashCounter >= nFlashCount THEN
+ bInGap := TRUE;
+ END_IF
+ ELSE
+ bOut := TRUE;
+ END_IF
+ END_IF
+END_IF
+]]>
+
+
+
\ No newline at end of file
diff --git a/POUs/Monitoring/FB_ZoneMonitoring.TcPOU b/POUs/Monitoring/FB_ZoneMonitoring.TcPOU
new file mode 100644
index 00000000..c8965d4f
--- /dev/null
+++ b/POUs/Monitoring/FB_ZoneMonitoring.TcPOU
@@ -0,0 +1,327 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 AND_THEN iSafetyProvider.ReadFromSafetyController THEN
+ _EStopReleased := iSafetyProvider.EStopOK;
+ _STOReleased := iSafetyProvider.STO_OK;
+END_IF;
+
+EStopReleased := _EStopReleased;
+]]>
+
+
+
+
+
+ aExtraMessageOld[i]);
+
+ //Set the alarm LEDs according to first occurrence
+ IF NOT bLedActive
+ AND aAlarmList[i].bLedActive THEN
+ bLedActive := TRUE;
+ stMonitorLed.eErrorLED := aAlarmList[i].eErrorLED;
+ stMonitorLed.eStatusLED := aAlarmList[i].eStatusLED;
+ END_IF
+
+ //If any error exists requiring an acknowledge, highlight Acknowledge LED.
+ IF aAlarmList[i].bLedActive AND NOT aAlarmList[i].bLedAutoReset THEN
+ stMonitorLed.eErrorAcknowledgeLED := aAlarmList[i].eErrorAcknowledgeLED;
+ END_IF
+END_FOR
+
+aExtraMessageOld := aExtraMessage; //Save last messages for next iteration
+
+//Copy to output variables
+aErrorID := aTempErrorID;
+aErrorMsg := aTempErrorMsg;
+nAlarmGrpBitNumber := nAlarmBitNumber;
+
+//Handle LED blinking
+fbFastBlinkOn(IN:=NOT fbFastBlinkOff.Q, PT:=T#250MS);
+fbFastBlinkOff(IN:=fbFastBlinkOn.Q, PT:=T#250MS);
+
+fbSlowBlinkOn(IN:=NOT fbSlowBlinkOff.Q, PT:=T#750MS);
+fbSlowBlinkOff(IN:=fbSlowBlinkOn.Q, PT:=T#750MS);
+
+_bSafetyAcknowledgeLED :=
+ (stMonitorLed.eErrorAcknowledgeLED = E_MonitorLed.eSteady)
+ OR (stMonitorLed.eErrorAcknowledgeLED = E_MonitorLed.eFast AND fbFastBlinkOn.Q)
+ OR (stMonitorLed.eErrorAcknowledgeLED = E_MonitorLed.eSlow AND fbSlowBlinkOn.Q);
+
+_bEStopLED := aAlarmList[4].bLedActive;
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/POUs/Safety/FB_MotionSafety.TcPOU b/POUs/Safety/FB_MotionSafety.TcPOU
new file mode 100644
index 00000000..18fa7bc4
--- /dev/null
+++ b/POUs/Safety/FB_MotionSafety.TcPOU
@@ -0,0 +1,393 @@
+
+
+
+
+
+ stSafety.stSafetyOut.stMCCStatusOut.bHeartbeat,
+ bWatchdogFault => bMCCWatchdogFault
+ ); //Reset heartbeat generation and watchdog memory
+
+ stSafety.stSafetyOut.stMCCStatusOut.bErrAck := FALSE; //Clear fault-acknowledge command
+
+ fbSafetyAckLedPulse(
+ bEnable := FALSE,
+ bOut => bSafetyAcknowledgeLED
+ ); //Reset pulse generator and physical LED
+
+ mHandleMotionPermission(); //Process falling edge of motion permission
+ RETURN;
+END_IF;
+
+mReadInputs(stSafetyData := stSafety);
+bDataValid := TRUE;
+
+mMCCStatus(stSafetyData := stSafety); //Heartbeat generation and MCC watchdog check
+mTechnosoftStatus(stSafetyData := stSafety);
+
+mUpdateFaultFeedback(stSafetyData := stSafety); //Fault/Err Ack. handshake
+mHandleMotionPermission();
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100.0 THEN
+ fPWClamped := 100.0;
+END_IF
+
+//Convert heartbeat period and pulse width into ON time
+tHeartbeatOnTime := LREAL_TO_TIME(TIME_TO_LREAL(tHeartbeatPeriod) * fPWClamped / 100.0);
+
+//Generate heartbeat output
+tHeartbeatElapsed := tHeartbeatElapsed + tTaskCycleTime;
+
+IF tHeartbeatElapsed >= tHeartbeatPeriod THEN
+ tHeartbeatElapsed := tHeartbeatElapsed - tHeartbeatPeriod;
+END_IF
+
+bHeartbeatOut := tHeartbeatElapsed < tHeartbeatOnTime;
+
+//Initialize watchdog memory on first enabled scan
+IF NOT bMCCHeartbeatInit THEN
+ bMCCHeartbeatInOld := bHeartbeatIn;
+ tMCCWatchdogElapsed := T#0MS;
+ bMCCHeartbeatInit := TRUE;
+END_IF
+
+//Watch incoming heartbeat for any change, rising or falling
+IF bHeartbeatIn <> bMCCHeartbeatInOld THEN
+ bMCCHeartbeatInOld := bHeartbeatIn;
+ tMCCWatchdogElapsed := T#0MS;
+ELSE
+ IF tMCCWatchdogElapsed < tWatchdogTime THEN
+ tMCCWatchdogElapsed := tMCCWatchdogElapsed + tTaskCycleTime;
+ END_IF
+END_IF
+
+//Watchdog result
+bWatchdogFault := tMCCWatchdogElapsed >= tWatchdogTime;
+
+mCheckMCCHeartbeat := NOT bWatchdogFault;
+]]>
+
+
+
+
+
+
+
+
+
+
+
+ stSafetyData.stSafetyOut.stMCCStatusOut.bHeartbeat,
+ bWatchdogFault => bMCCWatchdogFault
+);
+
+bAxisEnableAllowed :=
+ stSafetyData.stSafetyIn.stMCCStatusIn.bEStopOK
+ AND bMCC_OK;
+]]>
+
+
+
+
+
+ 0 THEN
+ FOR i := 1 TO GVL_APP.nNUM_OF_TECHNOSOFT DO
+ stSafetyData.stSafetyIn.astTechnosoftMotionError[i] :=
+ astTechnosoftMotionError[i];
+ END_FOR
+END_IF
+
+_bEStopOK := stSafetyData.stSafetyIn.stMCCStatusIn.bEStopOK;
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+ 0 THEN
+ FOR nAxes := 1 TO GVL_APP.nNUM_OF_TECHNOSOFT DO
+ stSafetyData.stSafetyOut.bSTOActive :=
+ stSafetyData.stSafetyOut.bSTOActive
+ OR stSafetyData.stSafetyIn.astTechnosoftMotionError[nAxes].bSTOActive;
+
+ stSafetyData.stSafetyOut.bTechnosoftMotionErrors :=
+ stSafetyData.stSafetyOut.bTechnosoftMotionErrors
+ OR F_TechnosoftErrorActive(stError := stSafetyData.stSafetyIn.astTechnosoftMotionError[nAxes]);
+ END_FOR
+END_IF
+
+IF GVL_APP.nNUM_OF_TECHNOSOFT <> 0 THEN
+ stSafetyData.stSafetyOut.stMCCStatusOut.bSTOFbk :=
+ stSafetyData.stSafetyOut.bSTOActive; //TRUE when any Technosoft drive reports STO active
+
+ stSafetyData.stSafetyOut.stMCCStatusOut.bSTO_OK :=
+ NOT stSafetyData.stSafetyOut.bTechnosoftMotionErrors; //Preserve the existing no-motion-error feedback
+END_IF
+]]>
+
+
+
+
+
+ bSafetyAcknowledgeLED);
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/POUs/Safety/F_TechnosoftErrorActive.TcPOU b/POUs/Safety/F_TechnosoftErrorActive.TcPOU
new file mode 100644
index 00000000..d6bef7ac
--- /dev/null
+++ b/POUs/Safety/F_TechnosoftErrorActive.TcPOU
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file