FUNCTION_BLOCK "LGF_FIFO"
TITLE = LGF_FIFO
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : Siemens_Industry_Support
FAMILY : LGF
NAME : LGF_FIFO
//FIFO (First-In First-Out / Queue / Ringspeicher)  
//Diese Funktion speichert eingehende Daten und gibt die ältesten noch nicht abgearbeiteten Daten aus.
//
   VAR_INPUT 
      enqueue : Bool := false;   // Element einreihen in den Puffer (Enqueue)
      dequeue : Bool := false;   // Element aus dem Puffer löschen und an `item` zurückgeben (Dequeue)
      reset : Bool;   // Puffer initialisieren (Index und Zähler zurücksetzen)
      clear : Bool;   // Puffer leeren und mit Anfangswert `initialItem` initialisieren (Index und Zähler zurücksetzen).
      initialItem : Variant;   // Wert mit dem das Array des Puffer initialisiert wird (meistens: `0` / default wert)
      maxElementCount : UInt;   // Maximale Anzahl an Elementen im FIFO
   END_VAR

   VAR_OUTPUT 
      error { ExternalWritable := 'False'} : Bool;   // FALSE: Kein Fehler  TRUE: Während der Ausführung des FB ist ein Fehler aufgetreten
      status { ExternalWritable := 'False'} : Word;   // 16#0000-16#7FFF: Status des FB  16#8000-16#FFFF: Fehleridentifikation (siehe folgende Tabelle)
      subFunctionStatus { ExternalWritable := 'False'} : Word;   // Status oder Rückgabewert von aufgerufenen FB's / FC's und Systemfunktionen
   END_VAR
   VAR_OUTPUT RETAIN
      elementCount { ExternalWritable := 'False'} : USInt := 0;   // Anzahl der Elemente im Puffer
      isEmpty { ExternalWritable := 'False'} : Bool := TRUE;   // TRUE: Puffer ist leer
      isFull : Bool;
   END_VAR
   VAR_OUTPUT 
      oldestElement : Int;
      enqueueDone : Bool;
      dequeueDone : Bool;
   END_VAR

   VAR_IN_OUT 
      itemIn : Variant;   // Eintrag der aus dem Puffer zurückgegeben wird oder in den Puffer geschrieben werden soll
      buffer : Variant;   // Das ARRAY welches als Puffer genutzt wird. (Array of … )
      itemOut : Variant;
   END_VAR

   VAR RETAIN
      statEdgesMem { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Struct   // Flanken speicher zum speichern des vorherigen Zustands und Flankendetektion
         enqueue { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;   // enqueue Flanke
         dequeue { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;   // dequeue Flanke
         clear { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;   // clearing Flanke
      END_STRUCT;
      statFirstItemIndex { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int := -1;   // Index des ältesten Eintrags im Puffer
      statNextEmptyItemIndex { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int := 0;   // Index für den nächsten freien Platz im Puffer
      statElementCount { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;   // Anzahl der Elemente im Puffer
   END_VAR

   VAR_TEMP 
      tempEdges : Struct   // Flanken speicher zum speichern der Flanken
         enqueue : Bool;   // enqueue Flanke
         dequeue : Bool;   // dequeue Flanke
         clear : Bool;   // clearing Flanke
      END_STRUCT;
      tempInternalError : Int;   // Fehler Information für interne Funktionsaufrufe und Rückgabewerte
      tempNewFirstItemIndex : Int;   // Variable Index
      tempNewNextEmptyItemIndex : Int;   // Variable Index
      tempBufferSize : UDInt;   // Anzahl der Elemente im Puffer Array / Puffer Größe
      tempCounter : Int;   // Zählvariable für die Iteration durch den Puffer
   END_VAR

   VAR CONSTANT 
      BUFFER_IS_EMPTY : Int := -1;   // Puffer ist leer
      NO_INTERNAL_ERROR : Int := 0;   // Kein interner Fehler
      BUFFER_INITIALIZED : Int := -1;   // Puffer initialisieren
      EMPTY_INITIALIZED : Int := 0;   // Leeres Element Index = 0
      INDEX_BEGINNING : Int := 0;   // Startwert für die Indizierung des Arrays
      COUNT_ELEMENTS : UDInt := 1;   // Anzahl der zu kopierenden Elemente = 1
      INCREMENT : Int := 1;   // Inkrement Wert EINS
      BUFFER_SIZE_CORRECTION : UDInt := 1;   // Puffer Größen Korrekturfaktor (0-basierte Zählung)
      COUNTER_LOWER_LIMIT : Int := 0;   // Zähler untere Grenze
      ZERO_ELEMENTS : DInt := 0;   // Keine Elemente vorhanden
      STATUS_NO_ERROR : Word := 16#0000;   // Status: Abarbeitung ohne Fehler beendet
      STATUS_NO_CURRENT_JOBS : Word := 16#7000;   // Status: Keine aktuellen Aufträge, Initial State
      ERR_BUFFER_EMPTY : Word := 16#8001;   // Fehler: Der Puffer ist leer
      ERR_BUFFER_FULL : Word := 16#8002;   // Fehler: Der Puffer ist voll
      ERR_NO_ARRAY : Word := 16#8200;   // Fehler: Am Eingang `buffer` liegt kein Array an.
      ERR_WRONG_TYPE_ITEM : Word := 16#8201;   // Fehler: Der Datentyp des InOut-Parameters `item` entspricht nicht dem Datentyp der Array-Elemente von dem Eingang `buffer`.
      ERR_WRONG_TYPE_INITIAL_ITEM : Word := 16#8202;   // Fehler: Der Datentyp des Eingangs `initialItem` entspricht nicht dem Datentyp des InOut-Parameters `item`.
      ERR_INDEX_IN_ARRAY_LIMITS_1 : Word := 16#8601;   // Fehler: Die Variable `statNextEmptyItemIndex` liegt ausserhalb der Array Grenzen
      ERR_INDEX_IN_ARRAY_LIMITS_2 : Word := 16#8602;   // Fehler: Die Variable `statFirstItemIndex` liegt ausserhalb der Array Grenzen
      ERR_CLEAR_BUFFER : Word := 16#8610;   // Fehler: Während des Ablöschen des Puffer in Funktion `MOVE_BLK_VARIANT` - weitere Infos in `subFunctionStatus`
      ERR_RETURN_FIRST_ENTRY : Word := 16#8611;   // Fehler: Während der Rückgabe des ersten Elements aus dem Puffer in Funktion `MOVE_BLK_VARIANT` - weitere Infos in `subFunctionStatus`
      ERR_REPLACE_ITEM_BY_INIT_VALUE : Word := 16#8612;   // Fehler: Während des Überschreibens des Elements mit dem Initialwort in Funktion `MOVE_BLK_VARIANT` - weitere Infos in `subFunctionStatus
      ERR_WRITE_ENTRY : Word := 16#8613;   // Fehler: Während des Schreibens eines Elements in den Puffer in Funktion `MOVE_BLK_VARIANT` - weitere Infos in `subFunctionStatus`
   END_VAR


BEGIN
	REGION Block info header
	    //===============================================================================
	    // SIEMENS AG / (c)Copyright 2019
	    //-------------------------------------------------------------------------------
	    // Title:            LGF_FIFO
	    // Comment/Function: FIFO (First In First Out)
	    //                   Queue / ring buffer memory
	    // Library/Family:   LGF (Library General Functions)
	    // Author:           Siemens Digital Industry Support
	    // Tested with:      CPU 1515F-2 PN FW:V2.6
	    // Engineering:      TIA Portal V15.1 Update 2
	    // Restrictions:     ENO disabled - error handling done with error and status
	    // Requirements:     PLC (S7-1200 / S7-1500)
	    //-------------------------------------------------------------------------------
	    // Change log table:
	    // Version  | Date       | Expert in charge       | Changes applied
	    //----------|------------|------------------------|------------------------------
	    // 01.00.00 19.08.2015  Siemens Industry Online Support
	    //                      First released version
	    // 01.00.01 16.11.2015  Siemens Industry Online Support
	    //                      Bug fix resetBuffer
	    // 01.00.02 02.01.2017  Siemens Industry Online Support
	    //                      Upgrade: TIA Portal V14 Update 1
	    // 01.00.03 17.08.2018  Siemens Industry Online Support
	    //                      Upgrade: TIA V15 Update 2
	    // 01.00.04 23.11.2018  Siemens Industry Online Support
	    //                      Upgrade: TIA V15.1
	    // 02.00.00 29.01.2019  Siemens Industry Online Support
	    //                      Output "done" removed (not necessary, because block works synchronous)
	    // 03.00.00 22.10.2019  Simatic Systems Support
	    //                      Code refactoring, comments added
	    //                      Interface change (enqueue, dequeue etc.)
	    //                      Set version to V3.0.0, harmonize the version of the whole library
	    // 03.00.01 15.02.2021  Simatic Systems Support
	    //                      Insert documentation
	    //=============================================================================
	END_REGION Block info header
	
	REGION DESCRIPTION
	    (/*
	Hinweis
	:    In `subFunctionStatus` wird der Status von aufgerufenen Anweisungen ausgegeben. Der Ausgangswert in `status` gibt in diesem Fall an, welche Anweisung den Fehler verursacht hat. Holen Sie sich in diesem Fall die Informationen aus der TIA Portal Online Hilfe zu den jeweiligen Anweisungen.
	
	---
	
	Hinweis
	:    Die Warteschlange (Queue) in der Informatik beruht ebenfalls auf dem FIFO-Prinzip.
	
	---
	
	Mit dem Eingang `enqueue` wird ein neues Element vom InOut-Parameter `item` an der nächsten freien Stelle im Puffer gespeichert. Der Ausgang `elementCount` wird um eins inkrementiert.
	
	Mit dem Eingang `dequeue` ausgegeben und dieses Feld im Puffer durch den Wert am Parameter `initialItem` ersetzt. Der Ausgang `elementCount` wird um eins dekrementiert.
	
	Mit dem Eingang `reset` wird der Puffer initialisiert, Index und Zähler werden zurückgesetzt. Der Ausgang `elementCount` wird auf null und der Ausgang `isEmpty` wird auf TRUE gesetzt.
	
	Mit dem Eingang `clear` wird der Puffer geleert und mit Anfangswert `initialItem` initialisiert. Index und Zähler werden zurückgesetzt. Der Ausgang `elementCount` wird auf null, der Ausgang `isEmpty` auf TRUE gesetzt.
	*/)
	END_REGION DESCRIPTION
	
	REGION Block execution control
	    
	    #enqueueDone := FALSE;
	    #dequeueDone := FALSE;
	    // collect edges
	    #tempEdges.dequeue := #dequeue AND NOT #statEdgesMem.dequeue;
	    #tempEdges.clear := #clear AND NOT #statEdgesMem.clear;
	    // store values for edge detection
	    IF NOT #dequeue THEN
	        #tempEdges.enqueue := #enqueue AND NOT #statEdgesMem.enqueue;
	        #statEdgesMem.enqueue := #enqueue;
	    END_IF;
	    #statEdgesMem.dequeue := #dequeue;
	    #statEdgesMem.clear := #clear;
	    
	    // This program code section is only executed if no trigger input is active
	    IF NOT (#enqueue OR #dequeue OR #reset OR #clear) THEN
	        // If an error occurred during program execution,
	        // the status "No Current Job" is used 16#7000 afterwarts when the triggers are reseted
	        #error := false;
	        #status := #STATUS_NO_CURRENT_JOBS;
	        #subFunctionStatus := #STATUS_NO_ERROR;
	        // the program processing OF the FB is terminated
	        RETURN;
	    END_IF;
	END_REGION
	
	REGION Validation of inputs
	    // check whether the ring #buffer is an ARRAY.
	    // IF so, the number OF the ARRAY elements is read out.
	    // IF it is NOT an ARRAY, the program execution is terminated at this point
	    IF IS_ARRAY(#buffer) THEN
	        #tempBufferSize := CountOfElements(#buffer);
	    ELSE
	        #error := true;
	        #status := #ERR_NO_ARRAY;
	        #subFunctionStatus := #STATUS_NO_ERROR;
	        RETURN;
	    END_IF;
	    
	    // check whether the data type OF the ARRAY elements matches
	    // the data type OF the entry (#item). IF the data types DO NOT match,
	    // the program execution is terminated at this point
	    IF (TypeOf(#itemIn) <> TypeOfElements(#buffer)) THEN
	        #error := true;
	        #status := #ERR_WRONG_TYPE_ITEM;
	        #subFunctionStatus := #STATUS_NO_ERROR;
	        RETURN;
	    END_IF;
	    
	    // check whether the initial value OF the ring #buffer
	    // matches the entry (tag #item). IF the data types DO NOT match,
	    // the program execution is terminated at this point
	    IF (TypeOf(#itemIn) <> TypeOf(#initialItem)) THEN
	        #error := true;
	        #status := #ERR_WRONG_TYPE_INITIAL_ITEM;
	        #subFunctionStatus := #STATUS_NO_ERROR;
	        RETURN;
	    END_IF;
	    
	    // check whether the variable indices are within the ARRAY limits.
	    // IF they are NOT, the program execution is terminated at this point
	    IF (#statNextEmptyItemIndex >= #tempBufferSize) THEN
	        #error := true;
	        #status := #ERR_INDEX_IN_ARRAY_LIMITS_1;
	        #subFunctionStatus := #STATUS_NO_ERROR;
	        RETURN;
	    ELSIF (#statFirstItemIndex >= #tempBufferSize) THEN
	        #error := true;
	        #status := #ERR_INDEX_IN_ARRAY_LIMITS_2;
	        #subFunctionStatus := #STATUS_NO_ERROR;
	        RETURN;
	    END_IF;
	    
	    // If resetBuffer is "TRUE", the buffer counters are reset
	    IF #reset THEN
	        #statFirstItemIndex := #BUFFER_INITIALIZED;
	        #statNextEmptyItemIndex := #EMPTY_INITIALIZED;
	        #statElementCount := #EMPTY_INITIALIZED;
	        #elementCount := INT_TO_USINT(#EMPTY_INITIALIZED);
	        #isEmpty := true;
	        RETURN;
	    END_IF;
	    
	    // If clearBuffer has rising edge, the buffer is initialized by initial values
	    IF #tempEdges.clear THEN
	        FOR #tempCounter := #COUNTER_LOWER_LIMIT TO UDINT_TO_INT(#tempBufferSize - #BUFFER_SIZE_CORRECTION) DO
	            
	            #tempInternalError := MOVE_BLK_VARIANT(SRC := #initialItem,
	                                                   COUNT := #COUNT_ELEMENTS,
	                                                   SRC_INDEX := #INDEX_BEGINNING,
	                                                   DEST_INDEX := #tempCounter,
	                                                   DEST => #buffer);
	        END_FOR;
	        
	        // checks whether a local error has occurred.
	        IF (#tempInternalError <> #NO_INTERNAL_ERROR) THEN
	            #error := true;
	            #status := #ERR_CLEAR_BUFFER;
	            #subFunctionStatus := INT_TO_WORD(#tempInternalError);
	        END_IF;
	        
	        // reset counters
	        #statFirstItemIndex := #BUFFER_INITIALIZED;
	        #statNextEmptyItemIndex := #EMPTY_INITIALIZED;
	        #statElementCount := #EMPTY_INITIALIZED;
	        #elementCount := INT_TO_USINT(#EMPTY_INITIALIZED);
	        #isEmpty := true;
	        RETURN;
	    END_IF;
	END_REGION
	
	REGION FIFO algorithm  
	    IF #tempEdges.dequeue THEN
	        REGION dequeue
	            // check whether the ring #buffer is empty
	            // IF this is the CASE, program execution is terminated at this point
	            IF (#statFirstItemIndex = #BUFFER_IS_EMPTY) THEN
	                #error := true;
	                #status := #ERR_BUFFER_EMPTY;
	                #subFunctionStatus := #STATUS_NO_ERROR;
	                RETURN;
	            END_IF;
	            
	            // return the first entry of the ring buffer
	            #tempInternalError := MOVE_BLK_VARIANT(SRC := #buffer,
	                                                   COUNT := #COUNT_ELEMENTS,
	                                                   SRC_INDEX := #statFirstItemIndex,
	                                                   DEST_INDEX := #INDEX_BEGINNING,
	                                                   DEST => #itemOut);
	            
	            // check whether a local error has occurred
	            IF (#tempInternalError <> #NO_INTERNAL_ERROR) THEN
	                #error := true;
	                #status := #ERR_RETURN_FIRST_ENTRY;
	                #subFunctionStatus := INT_TO_WORD(#tempInternalError);
	                RETURN;
	            END_IF;
	            
	            // check whether the ring #buffer contains ARRAY elements
	            // IF it does, the first entry is passed further on and the index is incremented BY 1
	            #tempInternalError := MOVE_BLK_VARIANT(SRC := #initialItem,
	                                                   COUNT := #COUNT_ELEMENTS,
	                                                   SRC_INDEX := #INDEX_BEGINNING,
	                                                   DEST_INDEX := #statFirstItemIndex,
	                                                   DEST => #buffer);
	            
	            // check whether a local error has occurred
	            IF (#tempInternalError <> #NO_INTERNAL_ERROR) THEN
	                #error := true;
	                #status := #ERR_REPLACE_ITEM_BY_INIT_VALUE;
	                #subFunctionStatus := INT_TO_WORD(#tempInternalError);
	                RETURN;
	            END_IF;
	            
	            // calculate the new index of the first entry
	            #tempNewFirstItemIndex := #statFirstItemIndex + #INCREMENT;
	            #tempNewFirstItemIndex := #tempNewFirstItemIndex MOD UDINT_TO_INT(#tempBufferSize);
	            
	            // check whether the ring buffer is empty
	            IF (#statNextEmptyItemIndex = #tempNewFirstItemIndex) THEN
	                // If the ring buffer is empty, the index is set to 0
	                #statFirstItemIndex := #BUFFER_INITIALIZED;
	                #statNextEmptyItemIndex := #EMPTY_INITIALIZED;
	            ELSE
	                // The index of the first entry is changed
	                #statFirstItemIndex := #tempNewFirstItemIndex;
	            END_IF;
	            
	            // Evaluation of the number of elements in the stack
	            #statElementCount -= #INCREMENT;
	            
	            #dequeueDone := TRUE; // !!!!!!!!!!!
	            #statEdgesMem.dequeue := FALSE;
	            
	        END_REGION dequeue
	        
	    ELSIF #tempEdges.enqueue THEN
	        REGION enqueue
	            // check whether the ring #buffer is full
	            // IF this is the CASE, program execution is terminated at this point
	            IF (#statNextEmptyItemIndex = #statFirstItemIndex) THEN
	                #error := true;
	                #status := #ERR_BUFFER_FULL;
	                #subFunctionStatus := #STATUS_NO_ERROR;
	                RETURN;
	            END_IF;
	            
	            // write the entry to the ring buffer
	            #tempInternalError := MOVE_BLK_VARIANT(SRC := #itemIn,
	                                                   COUNT := #COUNT_ELEMENTS,
	                                                   SRC_INDEX := #INDEX_BEGINNING,
	                                                   DEST_INDEX := #statNextEmptyItemIndex,
	                                                   DEST => #buffer);
	            
	            // check whether a local error has occurred
	            IF (#tempInternalError <> #NO_INTERNAL_ERROR) THEN
	                #error := true;
	                #status := #ERR_WRITE_ENTRY;
	                #subFunctionStatus := INT_TO_WORD(#tempInternalError);
	                RETURN;
	            END_IF;
	            
	            // increment the index by 1 and calculates the new empty entry index
	            #tempNewNextEmptyItemIndex := (#statNextEmptyItemIndex + #INCREMENT) MOD UDINT_TO_INT(#tempBufferSize);
	            #statNextEmptyItemIndex := #tempNewNextEmptyItemIndex;
	            
	            // check which index the "#firstItemIndex" tag has
	            // IF the number = -1, the ring buffer is initialized
	            // AND the entry is written TO the ring #buffer
	            // Therefore, "0" must be assigned TO the tag
	            IF (#statFirstItemIndex = #BUFFER_INITIALIZED) THEN
	                #statFirstItemIndex := #INDEX_BEGINNING;
	            END_IF;
	            
	            // Evaluation of the number of elements in the stack
	            #statElementCount += #INCREMENT;
	            #enqueueDone := TRUE;
	            #statEdgesMem.enqueue := FALSE;
	        END_REGION enqueue
	    END_IF;
	END_REGION
	
	REGION Writing to outputs
	    #oldestElement := #statFirstItemIndex;    // HINZUGEFÜGT!!!
	    #elementCount := DINT_TO_USINT(#statElementCount);
	    #isEmpty := #statElementCount <= #ZERO_ELEMENTS;
	    #isFull := #statElementCount >= #maxElementCount;
	    
	    #error := false;
	    #status := #STATUS_NO_ERROR;
	    #subFunctionStatus := #STATUS_NO_ERROR;
	    // no error handling by ENO needed
	    ENO := TRUE;
	END_REGION
END_FUNCTION_BLOCK

