FUNCTION_BLOCK "FB_SsccBarcodeReader"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      stInSettings : "UDT_SettingsBarcodeReader";
      xInBorner : Bool;   // NIU
      xInStartReading : Bool;   // Starte Lesevorgang
      xInHandshakeDone : Bool;   // Lesevorgang beendet Handshake rein
      xInErrQuit : Bool;   // Fehler rücksetzen
      xInReset : Bool;   // Baustein zurücksetzen 
      xInHWError : Bool;   // Baugruppen Fehler 
      tInPlcTime {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;   // SPS Zeit (z.B. für Zeitstempel)
   END_VAR

   VAR_OUTPUT 
      xOutReadingDone : Bool;   // Lesevorgang abgeschlossen / Handshake raus
      xOutReadyForNewScan : Bool;   // Reader ist bereit für Leseanforderung
      nOutBarcode : LInt;   // Letzter gelesener Barcode
      sOutBarcode : String[20];   // Letzter gelesener Barcode (als Zeichenkette)
      wOutErrorWord : Word;   // Fehlercode
      xOutCodeGood : Bool;   // NIU
      xOutCodeBad : Bool;   // NIU
   END_VAR

   VAR_IN_OUT 
      stInOutInterface : "stCognexInterface";
      stInOutControlUnitCabinet : "UDT_MainState";
      stInOutHMI : "UDT_BarcodeReader";
   END_VAR

   VAR 
      GETIO_PART {InstructionName := 'GETIO_PART'; LibVersion := '1.2'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : GETIO_PART;
      xStartReading : Bool;
      xTrigger : Bool;
      xToggleBitFlg : Bool;
      nBarcode : LInt;
      sBarcodeString : String[20] := 'none';
      SETIO_PART {InstructionName := 'SETIO_PART'; LibVersion := '1.2'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : SETIO_PART;
      stCtrlBitsIn : Struct
         D0_DeviceReady : Bool;
         D1_SystemReady : Bool;
         D2_GoodRead : Bool;
         D3_NoRead : Bool;
         D4_ExtOut1 : Bool;
         D5_ExtOut2 : Bool;
         D6_Result1 : Bool;
         D7_Result2 : Bool;
         D8_ExtIn1 : Bool;
         D9_ExtIn2 : Bool;
         D10_Sensor1 : Bool;
         D11_Sensor2 : Bool;
         D12_Res : Bool;
         D13_Res : Bool;
         D14_Res : Bool;
         D15_Toggle : Bool;
      END_STRUCT;
      nState : Int;
      nLastStep { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int;
      arStepHistory : Array[0..10] of Struct
         StepNum : Int;
         TimeStamp {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
      END_STRUCT;
      nResultID : Int;
      nResultCode : Int;
      nResultExtended : Int;
      "64Byte_IN_Data" { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Array[0..63] of Byte;
      tOnMonitoring {InstructionName := 'TON_TIME'; LibVersion := '1.0'} : TON_TIME;
      stHMI : "stHMI_BRCognexDM";
      fbMonitoringCamGeneralFault : "FB_Monitoring";
      fbMonitoringCamRuntimeFail : "FB_Monitoring";
      stStatistics : Struct
         nReads : UDInt;
         nGoodRead : UDInt;
         nBadRead : UDInt;
         nGoodReadBarcodeChanged : UDInt;
         nBadReadBarcodeChanged : UDInt;
         tLastBarcodeRecv {InstructionName := 'DTL'; LibVersion := '1.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DTL;
         tLastNoReadRecv {InstructionName := 'DTL'; LibVersion := '1.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DTL;
         tLastReset {InstructionName := 'DTL'; LibVersion := '1.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DTL;
      END_STRUCT;
      tPLCTime {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
   END_VAR
   VAR RETAIN
      xReInit : Bool;
   END_VAR
   VAR 
      xCodeGood : Bool;
      xCodeBad : Bool;
      nBarcodeTarget : DInt;
      sBarcodeTarget : String;
      stTest : Struct
         nGoodRead : Int;
         nBadRead : Int;
         nGoodReadBarcodeChanged : Int;
         nBadReadBarcodeChanged : Int;
      END_STRUCT;
   END_VAR

   VAR_TEMP 
      tReadIO_Status : DWord;
      tReadIO_Error : Bool;
      tWriteIO_Status_01 : DWord;
      tWriteIO_Error_01 : Bool;
      t_BC_iO : Bool;
      t_BC_niO : Bool;
      t_Format : Word;
      t_Status : Int;
      t_Time : Int;
      xTemp : Bool;
   END_VAR

   VAR CONSTANT 
      "E_DeviceState.eInit2" : Int := 100;
      "E_DeviceState.eInit1" : Int := 0;
      "E_DeviceState.eNoJob" : Int := 200;
      "E_DeviceState.eWaitReaderReady" : Int := 300;
      "E_DeviceState.eWaitTriggerAck" : Int := 400;
      "E_DeviceState.eGetResult" : Int := 500;
      "E_DeviceState.eClearResult" : Int := 600;
      "E_DeviceState.eOutput" : Int := 700;
      "E_DeviceState.eHandshake" : Int := 800;
   END_VAR


BEGIN
	REGION Block Header
	    //=================================================================================
	    // Schönenberger Systeme GmbH / (c)Copyright 2025
	    //---------------------------------------------------------------------------------
	    // Title:            FB_BarcodeReaderCognex
	    // Comment/Function: Cognex Barcode Reader 
	    // Library/Family:   BaseSystemLib/PROFINET/Devices
	    // Author:           E.Yüksel
	    // Tested with:      CPU 1515F-2 PN FW:V3.0
	    // Engineering:      TIA Portal V20
	    // Restrictions:     This function block ONLY works with Cognex Dataman Readers and PROFINET protocol
	    //                   Currently ONLY designed for Cognex trigger mode: Continuous   
	    // Requirements:     PLC (S7-1500)
	    //---------------------------------------------------------------------------------
	    // Change log table:
	    // Version  | Date       | Expert in charge       | Changes applied
	    //----------|------------|------------------------|--------------------------------
	    // 01.00.00 | 25.04.2024 | Yüksel, Molina         | First released version
	    // 02.00.00 | 20.11.2024 | Yüksel                 | Prepare for library integration
	    // 03.00.00 | 31.01.2025 | Yüksel                 | Refactoring, no functionality changes
	    //=================================================================================
	END_REGION
	
	REGION Info
	(*
	 Errors: 
	            00  Internal Error / Stepfailure
	            01  Results available before trigger
	            02  Runtime failure
	            03  Barcode ASCI failure
	            04  Res 4
	            05  ReadIO_Error
	            06  Res 6
	            07  Res 7
	            08  Res 8
	            09  Res 9
	            10  Res 10
	            11  Res 11
	            12  Res 12
	            13  Res 13
	            14  Camera not ready / General Fault
	            15  Modul reset
	*)
	END_REGION
	
	REGION INIT
	    REGION First Scan
	        IF "FirstScan" THEN
	            #nState := #"E_DeviceState.eInit1";
	        END_IF;
	    END_REGION
	    
	    REGION Reset statistics
	        IF NOT #xReInit AND NOT "FirstScan" AND #tInPlcTime.YEAR > 1970 THEN
	            #stStatistics.nReads := 0;
	            #stStatistics.nGoodRead := 0;
	            #stStatistics.nBadRead := 0;
	            #stStatistics.nGoodReadBarcodeChanged := 0;
	            #stStatistics.nBadReadBarcodeChanged := 0;
	            #stStatistics.tLastBarcodeRecv := DTL#1970-01-01-00:00:00;
	            #stStatistics.tLastNoReadRecv := DTL#1970-01-01-00:00:00;
	            #stStatistics.tLastReset := DTL#1970-01-01-00:00:00;
	            
	            #xReInit := TRUE;
	            #stStatistics.tLastReset := #tInPlcTime;
	        END_IF;
	    END_REGION
	    
	END_REGION
	
	REGION ErrorQuitt
	    IF #xInErrQuit THEN
	        #wOutErrorWord := 0;
	    END_IF;
	END_REGION
	
	REGION reset step sequence 
	    IF #xInReset THEN
	        #nState := #"E_DeviceState.eInit1";
	        #wOutErrorWord := 0;
	        #wOutErrorWord.%X15 := TRUE;
	    END_IF;
	END_REGION
	
	REGION Err check camera ready    
	    #wOutErrorWord.%X14 := #stInOutInterface.#xInCamGeneralFault;
	END_REGION
	REGION stepsequence
	    
	    CASE #nState OF
	        #"E_DeviceState.eInit1":
	            REGION STEP 000 - INIT 1
	                #xOutReadyForNewScan := FALSE;
	                #xCodeBad := FALSE;
	                #xCodeGood := FALSE;
	                #xOutReadingDone := FALSE;
	                #stInOutInterface.xOutCamTriggerEnable := FALSE;
	                #stInOutInterface.xOutCamTrigger := FALSE;
	                #stInOutInterface.xOutCamResultsAck := FALSE;
	                #nOutBarcode := #nBarcode := 0;
	                #sOutBarcode := #sBarcodeString := '';
	                
	                #wOutErrorWord := 0;                  // no Error
	                
	                #nState := #"E_DeviceState.eInit2";       //Lade aktuelle Schrittnummer 
	            END_REGION
	        #"E_DeviceState.eInit2":
	            REGION STEP 100 - INIT 2
	                #xOutReadyForNewScan := FALSE;
	                #xCodeBad := FALSE;
	                #xCodeGood := FALSE;
	                #xOutReadingDone := FALSE;
	                #stInOutInterface.xOutCamTriggerEnable := FALSE;
	                #stInOutInterface.xOutCamTrigger := FALSE;
	                #stInOutInterface.xOutCamResultsAck := TRUE; // Reader Ergebnisse quittieren
	                #nOutBarcode := #nBarcode := 0;
	                #sOutBarcode := #sBarcodeString := '';
	                
	                //Transition
	                IF
	                    NOT #stInOutInterface.xInCamResultsAvailable
	                THEN
	                    #nState := #"E_DeviceState.eNoJob";       //Lade aktuelle Schrittnummer 
	                END_IF;
	            END_REGION
	        #"E_DeviceState.eNoJob":
	            REGION STEP 200 - IDLE
	                #xCodeBad := FALSE;
	                #xCodeGood := FALSE;
	                IF #stInOutInterface.xInCamResultsAvailable OR #xInErrQuit THEN
	                    #stInOutInterface.xOutCamResultsAck := TRUE;
	                ELSE
	                    #stInOutInterface.xOutCamResultsAck := FALSE;
	                END_IF;
	                
	                //#stInOutInterface.xOutCamResultsAck := #xInErrQuit;
	                #stInOutInterface.xOutCamTriggerEnable := FALSE;
	                #stInOutInterface.xOutCamTrigger := FALSE;
	                #xOutReadingDone := FALSE;
	                
	                #nBarcode := #nOutBarcode := 0;
	                
	                #xOutReadyForNewScan := TRUE;
	                
	                //Transition
	                IF #xInStartReading THEN    // Leseanforderung
	                    #nState := #"E_DeviceState.eWaitReaderReady";       //Lade aktuelle Schrittnummer 
	                END_IF;
	            END_REGION
	        #"E_DeviceState.eWaitReaderReady":
	            REGION STEP 300 - WAIT READER READY
	                #xOutReadyForNewScan := FALSE;
	                #stInOutInterface.xOutCamTriggerEnable := TRUE; // Freigabe an Barcode Reader
	                #stInOutInterface.xOutCamResultsAck := FALSE;
	                
	                #wOutErrorWord.%X1 := #stInOutInterface.xInCamResultsAvailable; // Fehler wenn es schon Ergebnisse gibt
	                
	                //Transition
	                IF #stInOutInterface.xInCamTriggerReady THEN // Barcode Reader meldet bereit
	                    #stInOutInterface.xOutCamTrigger := TRUE; // Barcode Reader triggern
	                    #nState := #"E_DeviceState.eWaitTriggerAck";
	                END_IF;
	            END_REGION
	        #"E_DeviceState.eWaitTriggerAck":
	            REGION STEP 400 - WAIT TRIGGER ACK
	                #xOutReadyForNewScan := FALSE;
	                #stInOutInterface.xOutCamTrigger := TRUE;
	                
	                //Transition
	                IF #stInOutInterface.xInCamTriggerAck THEN
	                    #nState := #"E_DeviceState.eGetResult";
	                END_IF;
	            END_REGION
	        #"E_DeviceState.eGetResult":
	            REGION STEP 500 - GET RESULT
	                // INFO:
	                // Beim Continous-Trigger (Modus der am Barcode Reader eingestellt wird) muss das Trigger-Signal im Gegensatz zum Single-Trigger immer high bleiben.
	                // Der Reader macht kontinuierlich Bilder und hört erst auf, wenn etwas gelesen wurde oder man den Trigger wegnimmt (z.B. nach Timeout)
	                //     #stInOutInterface.xOutCamTrigger := #xInStartReading;
	                
	                
	                // Auf Ergebnis warten...
	                IF #stInOutInterface.xInCamResultsAvailable THEN // Ergebnis ist da
	                    #stInOutInterface.xOutCamTrigger := FALSE; // Trigger wegnehmen
	                    
	                    REGION Read data
	                        #GETIO_PART(ID := #stInOutInterface.hwInCamInspectionResults,        //Einlesen IN 64 Byte Daten
	                                    OFFSET := 0,
	                                    LEN := 64,
	                                    STATUS => #tReadIO_Status,
	                                    ERROR => #wOutErrorWord.%X5,
	                                    INPUTS := #"64Byte_IN_Data");
	                        
	                        Chars_TO_Strg(Chars := #"64Byte_IN_Data",
	                                      pChars := 8, //Zeichen ab 8.Byte InData 
	                                      Cnt := 20,    //10 Zeichen
	                                      Strg => #sBarcodeString);
	                    END_REGION
	                    
	                    REGION Evaluation (+ optional check Ascii)
	                        // ok - ASCI Zeichenprüfung 0 = 48 ... 9 = 57
	                        IF NOT #stInSettings.xActivateCheckASCI OR        // next Step wenn ASCI Prüfung nicht aktiv
	                            (#"64Byte_IN_Data"[08] >= 48 AND    //Prüfung 1. Zeichen auf Zahlen größer/gleich 0 
	                            #"64Byte_IN_Data"[08] <= 57 AND     //Prüfung 1. Zeichen auf Zahlen kleiner/gleich 9 
	                            
	                            #"64Byte_IN_Data"[15] >= 48 AND     //Prüfung 10. Zeichen auf Zahlen kleiner/gleich 0 
	                            #"64Byte_IN_Data"[15] <= 57)        //Prüfung 10. Zeichen auf Zahlen kleiner/gleich 9 
	                        THEN
	                            REGION SUCCESS
	                                #stStatistics.tLastBarcodeRecv := #tInPlcTime;
	                                #stStatistics.nReads += 1;
	                                #stStatistics.nGoodRead += 1;
	                                #xCodeGood := TRUE;
	                                #wOutErrorWord.%X3 := FALSE;
	                                REGION Convert to Integer
	                                    STRG_VAL(IN := #sBarcodeString,      //Barcode nach DWord
	                                             FORMAT := w#16#0,
	                                             P := 1,                        //Ab Zeichen 1 bis zu Ungültigem wert umwandeln // 
	                                             OUT => #nBarcode);
	                                END_REGION
	                            END_REGION
	                            
	                        ELSE
	                            REGION FAIL / BAD
	                                #stStatistics.nReads += 1;
	                                #stStatistics.nBadRead += 1;
	                                #stStatistics.tLastNoReadRecv := #tInPlcTime;
	                                #wOutErrorWord.%X3 := TRUE;     // Fehler ASCI Prüfung! zb NoRead von Cam
	                                #xCodeBad := TRUE;
	                                #nBarcode := -1;
	                                #sBarcodeString := 'NOREAD'; // NEU
	                            END_REGION
	                            
	                        END_IF;
	                        
	                    END_REGION
	                    
	                    REGION Acknowledge & Transition
	                        //Transition
	                        #stInOutInterface.xOutCamResultsAck := TRUE;
	                        #nState := #"E_DeviceState.eClearResult";
	                    END_REGION
	                    
	                END_IF;
	            END_REGION
	            
	        #"E_DeviceState.eClearResult":
	            REGION STEP 600 - CLEAR RESULT
	                #stInOutInterface.xOutCamResultsAck := TRUE; //Leseergebnis quittieren
	                
	                //Transition
	                IF
	                    NOT #stInOutInterface.xInCamResultsAvailable // Auf Handshake warten
	                THEN
	                    #nState := #"E_DeviceState.eOutput"; // Nächster Schritt
	                END_IF;
	            END_REGION
	            
	        #"E_DeviceState.eOutput":
	            REGION STEP 700 - WRITE OUTPUT
	                #xOutReadyForNewScan := FALSE;
	                #stInOutInterface.xOutCamTriggerEnable := FALSE; // Freigabe wegnehmen
	                #stInOutInterface.xOutCamTrigger := FALSE;
	                
	                #nOutBarcode := #nBarcode;
	                #sOutBarcode := #sBarcodeString;
	                
	                #xOutReadingDone := TRUE;
	                
	                //Transition
	                #nState := #"E_DeviceState.eHandshake";
	                
	            END_REGION
	        #"E_DeviceState.eHandshake":
	            REGION STEP 800 - HANDSHAKE
	                #xOutReadyForNewScan := FALSE;
	                #xOutReadingDone := TRUE;
	                #stInOutInterface.xOutCamTriggerEnable := FALSE; // Freigabe wegnehmen
	                #stInOutInterface.xOutCamTrigger := FALSE;
	                IF #xInHandshakeDone THEN // Handshake
	                    #nState := #"E_DeviceState.eNoJob";
	                END_IF;
	            END_REGION
	        ELSE    //keine gültige Schrittnummer vorhanden
	            #wOutErrorWord.%X0 := true;
	    END_CASE;
	    
	    REGION Step history
	        IF
	            #nState <> #nLastStep
	        THEN
	            #t_Status := MOVE_BLK_VARIANT(SRC := #arStepHistory,
	                                          COUNT := 10,
	                                          SRC_INDEX := 0,
	                                          DEST_INDEX := 1,
	                                          DEST => #arStepHistory);
	            #t_Status := RD_LOC_T(#arStepHistory[0].TimeStamp); //Zeitstempel
	            #arStepHistory[0].StepNum := #nState;
	            #nLastStep := #nState;
	        END_IF;
	    END_REGION
	END_REGION
	
	REGION Output
	    #xOutCodeGood := #xCodeGood;
	    #xOutCodeBad := #xCodeBad;
	END_REGION
	
	REGION Runtime monitoring
	    IF #tOnMonitoring.Q THEN // TIMEOUT
	        #stStatistics.nReads += 1;
	        #stStatistics.nBadRead += 1;
	        #stStatistics.tLastNoReadRecv := #tInPlcTime;
	        #xCodeBad := TRUE;
	        #wOutErrorWord.%X2 := TRUE;     // Runtime failure
	        #nBarcode := -1;
	        #sBarcodeString := 'NOREAD';
	        #nState := #"E_DeviceState.eOutput";
	    END_IF;
	END_REGION
	
	REGION HMI / Visu
	    
	    #stHMI.In16.%X0 := #stInOutInterface.xInCamTriggerReady;
	    #stHMI.In16.%X1 := #stInOutInterface.xInCamTriggerAck;
	    #stHMI.In16.%X2 := #stInOutInterface.xInCamResultsAvailable;
	    #stHMI.In16.%X3 := #stInOutInterface.xInCamGeneralFault;
	    #stHMI.BarcodeString := #sBarcodeString;
	    #stHMI.Barcode := #nBarcode;
	    #stHMI.ResultID := #nResultID;
	    #stHMI.ErrorWord := #wOutErrorWord;
	    
	    IF #stInOutInterface.xInCamGeneralFault OR #xInHWError THEN // Kamera nicht bereit
	        #stInOutHMI.bStatus := 2; // ERROR
	    ELSE
	        CASE #nState OF
	            #"E_DeviceState.eInit1", #"E_DeviceState.eInit2":
	                #stInOutHMI.bStatus := 0; // NOT_AVAILABLE
	            #"E_DeviceState.eNoJob", #"E_DeviceState.eWaitReaderReady", #"E_DeviceState.eWaitTriggerAck",
	            #"E_DeviceState.eGetResult", #"E_DeviceState.eClearResult", #"E_DeviceState.eOutput",
	            #"E_DeviceState.eHandshake":
	                #stInOutHMI.bStatus := 1; // AVAILABLE
	            ELSE  //Fallback
	                #stInOutHMI.bStatus := 0; // NOT AVAILABLE
	        END_CASE;
	    END_IF;
	    
	    #stInOutHMI.nMode := 2; // Continuous
	    #stInOutHMI.stBRInterface := #stHMI;
	    #stInOutHMI.nNumberScans := #stStatistics.nReads;
	    #stInOutHMI.nNumberNoReads := #stStatistics.nBadRead;
	    #stInOutHMI.nNoReadPercentage := REAL_TO_USINT((UDINT_TO_REAL(#stStatistics.nBadRead) / UDINT_TO_REAL(#stStatistics.nReads)) * 100);
	    #stInOutHMI.tLastBarcodeRecv := #stStatistics.tLastBarcodeRecv;
	    #stInOutHMI.tLastNoReadRecv := #stStatistics.tLastNoReadRecv;
	    #stInOutHMI.tLastReset := #stStatistics.tLastReset;
	    
	END_REGION
	
	REGION Timer
	    #tOnMonitoring(IN := #stInOutInterface.xOutCamTriggerEnable,
	                   PT := #stInSettings.tTimeout);
	END_REGION
	
	REGION Monitoring
	    
	    #fbMonitoringCamRuntimeFail.stSettings.nErrorType := 2;
	    #fbMonitoringCamRuntimeFail.stSettings.xErrLedIfMachineIsOff := TRUE;
	    #fbMonitoringCamRuntimeFail.stSettings.xSelfQuit := TRUE;
	    #fbMonitoringCamRuntimeFail(sInFctnName := CONCAT_STRING(IN1 := 'Barcode Reader: ', IN2 := #stInOutHMI.sLocation),
	                                sInPrefix := #stInOutHMI.sUnit,
	                                sInSuffix := #stInOutHMI.sIpAdress,
	                                InOutMachineState := #stInOutControlUnitCabinet,
	                                xInQuitError := #stInOutControlUnitCabinet.xQuitError,
	                                xInSignal := NOT #tOnMonitoring.Q,
	                                sInAlarmMessage := 'Laufzeitfehler',
	                                wInTextListId_Function := 1,
	                                wInTextListId_Message := 1);
	    
	    #fbMonitoringCamGeneralFault.stSettings.nErrorType := 1;
	    #fbMonitoringCamGeneralFault.stSettings.xErrLedIfMachineIsOff := TRUE;
	    #fbMonitoringCamGeneralFault.stSettings.xSelfQuit := TRUE;
	    #fbMonitoringCamGeneralFault(sInFctnName := CONCAT_STRING(IN1 := 'Barcode Reader: ', IN2 := #stInOutHMI.sLocation),
	                                 sInPrefix := #stInOutHMI.sUnit,
	                                 sInSuffix := #stInOutHMI.sIpAdress,
	                                 InOutMachineState := #stInOutControlUnitCabinet,
	                                 xInQuitError := #stInOutControlUnitCabinet.xQuitError,
	                                 xInSignal := NOT #stInOutInterface.xInCamGeneralFault AND NOT #xInHWError,
	                                 sInAlarmMessage := 'Barcode Reader ist nicht bereit.',
	                                 wInTextListId_Function := 1,
	                                 wInTextListId_Message := 1);
	    
	END_REGION
	
	REGION Event Log
	    // Pro Event wird ein Aufruf benötigt
	    //TODO
	END_REGION
END_FUNCTION_BLOCK

