FUNCTION_BLOCK "FB_StockRemovalSequencerUDT"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      xReset : Bool;
      nMaxEmptyBLK : Int;
      nMaxEmptyLine : Int;
      udt_In_CarrierNeeded : "udt_CarrierNeeded";
   END_VAR

   VAR_IN_OUT 
      udt_InOutStorageSite : "udt_StorageSite";
   END_VAR

   VAR 
      xCandidateFound : Bool;
      nNextGlobalIndex : Int;   // 1..12
      nStartSearchIndex : Int;   // Round-Robin Start
      nCheckSearchIndex : Int;   // aktuell geprüfter Index
      nSearchLoopIndex : Int;   // Schleifenvariable 1..12
      nCandidateBlockId : Int;   // 1..3
      nCandLager : Int;
      nCandLine : Int;
      nBlkID : Int;
      nPendingLagerIndex : Int;
      nLagerIndex : Int;
   END_VAR

   VAR_TEMP 
      nResetIdx : Int;
   END_VAR


BEGIN
	
	REGION Info
	(* Company: Schönenberger Systeme GmbH / (C) Copyright (2022). All rights reserved.
	-----------------------------------------------------------------------------------------
	Title: FB_StockRemovalSequencerUDT
	Comment/Function: Manage the stock remove releases
	Library/Family: -
	Author: department: "Automatisierung" ; person in charge / contact: C.Nottelmann (Automated Intelligence)
	Target System: CPU 1518F-4 PN/DP
	Engineering: TIA Portal V20
	Restrictions: -
	Requirements: -
	-----------------------------------------------------------------------------------------
	Change log table:
	Version     | Date       | Signature (Safety) | Expert in charge | Changes applied
	------------|------------|--------------------|------------------|-----------------------
	001.000.000 | 2025-11-10 | -                  |                  | Header added on 10.11.2025
	                                                                   
	*)
	END_REGION
	
	
	
	IF #xReset THEN
	    #udt_InOutStorageSite.nRemove_LastServed := 0;
	    #udt_InOutStorageSite.nRemove_ActiveIDX := 0;
	    #udt_InOutStorageSite.nRemove_Busy := FALSE;
	    FOR #nSearchLoopIndex := 1 TO 12 DO
	        #udt_InOutStorageSite.arrudt_Storage[#nSearchLoopIndex].udt_RearrangeStorageState.xRemoveGrant := FALSE;
	    END_FOR;
	    
	ELSE
	    // ---------------- Kandidatensuche (nur wenn nicht aktiv) ----------------
	    #xCandidateFound := FALSE;
	    #nNextGlobalIndex := 0;
	    
	    #nStartSearchIndex := #udt_InOutStorageSite.nRemove_LastServed  + 1;
	    IF #nStartSearchIndex > 12 THEN
	        #nStartSearchIndex := 1;
	    END_IF;
	    #nCheckSearchIndex := #nStartSearchIndex;
	    
	    FOR #nSearchLoopIndex := 1 TO 12 DO
	        #nCandidateBlockId := #udt_InOutStorageSite.arrudt_Storage[#nCheckSearchIndex].udt_RearrangeStorageCtrl.nBlockID;
	        
	        IF (#udt_InOutStorageSite.arrudt_Storage[#nCheckSearchIndex].udt_RearrangeStorageCtrl.xEnableRemove) AND
	            (#udt_InOutStorageSite.arrudt_Storage[#nCheckSearchIndex].udt_RearrangeStorageCtrl.xRemoveRequest) AND
	            (#udt_InOutStorageSite.arrudt_Storage[#nCheckSearchIndex].udt_RearrangeStorageState.xRearrange_Done) AND // erst nach Umlagerung sinnvoll
	            (NOT #udt_InOutStorageSite.arrudt_Storage[#nCheckSearchIndex].udt_RearrangeStorageState.xRearrangeBusy) AND
	            (NOT #udt_InOutStorageSite.arrudt_Storage[#nCheckSearchIndex].udt_RearrangeStorageState.xRearrangeGrant) AND
	            (#nCandidateBlockId >= 1) AND (#nCandidateBlockId <= 3) AND
	            (#udt_InOutStorageSite.arrudt_BlockGroup[#nCandidateBlockId].udt_BlockGroupCtrl.xReadyRemove)
	        THEN
	            #nNextGlobalIndex := #nCheckSearchIndex;
	            #xCandidateFound := TRUE;
	            EXIT;
	        END_IF;
	        
	        #nCheckSearchIndex := #nCheckSearchIndex + 1;
	        IF #nCheckSearchIndex > 12 THEN
	            #nCheckSearchIndex := 1;
	        END_IF;
	    END_FOR;
	    
	    // ---------------- Ablauf: global exakt ein Lager ----------------
	    IF NOT #udt_InOutStorageSite.nRemove_Busy THEN
	        // Alles zurücksetzen
	        FOR #nSearchLoopIndex := 1 TO 12 DO
	            #udt_InOutStorageSite.arrudt_Storage[#nSearchLoopIndex].udt_RearrangeStorageState.xRemoveGrant := FALSE;
	        END_FOR;
	        #udt_InOutStorageSite.nRemove_ActiveIDX := 0;
	        
	        // Starten, wenn Kandidat da
	        IF #xCandidateFound THEN
	            #udt_InOutStorageSite.nRemove_ActiveIDX := #nNextGlobalIndex;
	            #udt_InOutStorageSite.arrudt_Storage[#udt_InOutStorageSite.nRemove_ActiveIDX].udt_RearrangeStorageState.xRemoveGrant := TRUE;
	            #udt_InOutStorageSite.nRemove_Busy := TRUE;
	        END_IF;
	        
	    ELSE
	        // aktiver Auftrag überwachen
	        IF (#udt_InOutStorageSite.arrudt_Storage[#udt_InOutStorageSite.nRemove_ActiveIDX].udt_RearrangeStorageState.xRemoveDone) OR
	            (NOT #udt_InOutStorageSite.arrudt_Storage[#udt_InOutStorageSite.nRemove_ActiveIDX].udt_RearrangeStorageCtrl.xRemoveRequest) OR
	            (NOT #udt_InOutStorageSite.arrudt_Storage[#udt_InOutStorageSite.nRemove_ActiveIDX].udt_RearrangeStorageCtrl.xEnableRemove) THEN
	            
	            // Abschluss
	            #udt_InOutStorageSite.arrudt_Storage[#udt_InOutStorageSite.nRemove_ActiveIDX].udt_RearrangeStorageState.xRemoveGrant := FALSE;
	            #udt_InOutStorageSite.nRemove_LastServed := #udt_InOutStorageSite.nRemove_ActiveIDX;
	            #udt_InOutStorageSite.nRemove_ActiveIDX := 0;
	            #udt_InOutStorageSite.nRemove_Busy := FALSE;
	            
	            // Optional: sofort Folgekandidat starten
	            IF #xCandidateFound THEN
	                #udt_InOutStorageSite.nRemove_ActiveIDX := #nNextGlobalIndex;
	                #udt_InOutStorageSite.arrudt_Storage[#udt_InOutStorageSite.nRemove_ActiveIDX].udt_RearrangeStorageState.xRemoveGrant := TRUE;
	                #udt_InOutStorageSite.nRemove_Busy := TRUE;
	            END_IF;
	        ELSE
	            // Exklusivität absichern
	            FOR #nSearchLoopIndex := 1 TO 12 DO
	                #udt_InOutStorageSite.arrudt_Storage[#nSearchLoopIndex].udt_RearrangeStorageState.xRemoveGrant :=
	                (#nSearchLoopIndex = #udt_InOutStorageSite.nRemove_ActiveIDX);
	            END_FOR;
	        END_IF;
	    END_IF;
	END_IF;
	
	REGION Empty Carrier
	    
	    // Prüfen, ob ein Lager noch im Handover-Fenster hängt:
	    //    (= Snapshot gesetzt aber Empty_Busy noch FALSE)
	    #nPendingLagerIndex := 0;
	    FOR #nLagerIndex := 1 TO 12 DO
	        IF #udt_InOutStorageSite.arrudt_Storage[#nLagerIndex].udt_RearrangeStorageState.xEmptySelLineValid
	            AND (NOT #udt_InOutStorageSite.arrudt_Storage[#nLagerIndex].udt_RearrangeStorageState.xEmptyBusy) THEN
	            #nPendingLagerIndex := #nLagerIndex;
	            EXIT;
	        END_IF;
	    END_FOR;
	    
	    // Falls Pending existiert: NICHT umschalten, denselben Request weiter behaupten
	    IF #nPendingLagerIndex <> 0 THEN
	        #udt_InOutStorageSite.arrudt_Storage[#nPendingLagerIndex].udt_RearrangeStorageCtrl.xEmptyReq := TRUE;  // bis Busy kommt
	    ELSE
	        //Sonst neuen Kandidaten auswerten und SNAPSHOT+Request setzen
	        
	        #nCandLager := #nMaxEmptyBLK;    // 1..12
	        #nCandLine := #nMaxEmptyLine;   // 1..21
	        
	        IF (#nCandLager >= 1) AND (#nCandLager <= 12) THEN
	            // lokale Interlocks + Ready des Blocks
	            #nBlkID := #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.nBlockID;
	            IF #udt_InOutStorageSite.arrudt_BlockGroup[#nBlkID].udt_BlockGroupCtrl.xReadyRemove
	                AND (NOT #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xRearrangeBusy)
	                AND (NOT #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xRearrangeGrant)
	                AND (NOT #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xRemoveBusy)
	                AND (NOT #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xRemoveGrant)
	                AND (NOT #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xEmptyBusy)
	                AND (#udt_In_CarrierNeeded.xF2HEmptyNeeded OR #udt_In_CarrierNeeded.xInboundEmptyNeeded)
	                //AND Inbound braucht Leere Carrier
	                //AND F2H braucht Leere Carrier
	            THEN
	                
	                // SNAPSHOT zusammen mit der Anfrage
	                IF NOT #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xEmptySelLineValid THEN
	                    #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.nEmptySelLine :=INT_TO_USINT(#nCandLine);
	                    #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xEmptySelLineValid := TRUE;
	                END_IF;
	                
	                // Anfrage für leere Auslagerung (Start entsteht in der Unterlogik-Bridge)
	                #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.xEmptyReq := TRUE;
	            END_IF;
	        END_IF;
	    END_IF;
	    
	    // Request automatisch zurücknehmen, sobald der Job losläuft
	    FOR #nResetIdx := 1 TO 12 DO
	        IF #udt_InOutStorageSite.arrudt_Storage[#nResetIdx].udt_RearrangeStorageState.xEmptyBusy THEN
	            #udt_InOutStorageSite.arrudt_Storage[#nResetIdx].udt_RearrangeStorageCtrl.xEmptyReq := FALSE;
	        END_IF;
	    END_FOR;
	    
	END_REGION
	
	
	
	
	
	
	
END_FUNCTION_BLOCK

