FUNCTION_BLOCK "FB_StorageTaskScheduler"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      xReset : Bool;
      nInMaxEmptyBLK : Int;
      nInMaxEmptyLine : 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;
      "////////////" : Bool;
      nState : Int;
      nCurrentTask : Int;
      sCurrentTask : Int;
      tOnTimeOutEmptyInbound {InstructionName := 'TON_TIME'; LibVersion := '1.0'} : TON_TIME;
      xSetTimeoutEmptyInbound : Bool;
      tOnTimeOutEmptyF2H {InstructionName := 'TON_TIME'; LibVersion := '1.0'} : TON_TIME;
      xSetTimeoutEmptyF2H : Bool;
      tTestTimeOutEmptyInbound : Time := T#20m;
      tTestTimeOutEmptyF2H : Time := T#20m;
   END_VAR

   VAR_TEMP 
      nResetIdx : Int;
   END_VAR

   VAR CONSTANT 
      c000_Idle : Int := 0;
      c100_SelectJob : Int := 100;
      c1000_CheckEmptyJobInbound : Int := 1000;
      c1200_StartEmptyJobInbound : Int := 1200;
      c1300_WaitEmptyJobFinishInbound : Int := 1300;
      c2000_CheckEmptyJobF2H : Int := 2000;
      c2200_StartEmptyJobF2H : Int := 2200;
      c2300_WaitEmptyJobFinishF2H : Int := 2300;
      cTask_F2HEmpty : Int := 1;
      cTask_InboundEmpty : Int := 2;
      cTask_AMRLoaded : Int := 3;
      cTask_F2HLoaded : Int := 4;
   END_VAR


BEGIN
	REGION Info
	(* Company: Schönenberger Systeme GmbH / (C) Copyright (2022). All rights reserved.
	-----------------------------------------------------------------------------------------
	Title: FB_StorageTaskScheduler
	Comment/Function: Coordinate storage blocks releases
	Library/Family: -
	Author: department: "Automatisierung" ; person in charge / contact: C.Nottelmann (Automated Intelligence), Yüksel
	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
	
	// Der Baustein soll prüfen, welche Aufgaben im Lager anstehen und diese koordinieren
	// Aktuell wird nur ein Auftrag gleichzeitig zugelassen, da der Level 1 Verteilkreisel eine begrenzte Kapazität hat 
	// Man könnte den Füllgrad auf dem Level 1 Verteilkreisel überwachen und Folgeaufträge zügiger starten 
	
	
	//Aufgaben/Jobs
	//  - Leere MT zu Inbound (UH01 meldet Bedarf)
	//  - Leere MT zu F2H (UH05 meldet Bedarf)
	//  - Volle MT zu AMR (Lagerblöcke sind fertig mit Umlagern bzw. Ready für Auslagern)
	//  - Volle MT zu F2H (Lagerblöcke sind fertig mit Umlagern bzw. Ready für Auslagern)
	//  
	//Hier könnne verschiedene Prinzipien zum Einsatz kommen
	//Strikte Prio für gewisse Jobs z.B. bei hohem Bedarf an Leercarriern,
	//Round-Robin für Fairness, damit ein Job nicht ständig die Anlage belegt
	//Unter den Lagerblöcken muss auch nochmal eine Auswahl stattfinden
	
	#tTestTimeOutEmptyInbound := t#30m;
	
	REGION Scheduler
	    CASE #nState OF
	        #c000_Idle:
	            
	            REGION 000: Idle
	                #nState := #c100_SelectJob;
	            END_REGION
	            
	        #c100_SelectJob:
	            REGION 100: Select Job                
	                IF  #udt_In_CarrierNeeded.xInboundEmptyNeeded AND NOT #xSetTimeoutEmptyInbound AND "IBN".xEnableInboundEmpty THEN
	                    #nState := #c1000_CheckEmptyJobInbound;
	                ELSIF #udt_In_CarrierNeeded.xF2HEmptyNeeded AND NOT #xSetTimeoutEmptyF2H AND "IBN".xEnableF2HEmpty THEN
	                    #nState := #c2000_CheckEmptyJobF2H;
	                END_IF;
	            END_REGION
	            
	        #"c1000_CheckEmptyJobInbound":
	            REGION 1000: Check Job: Empty to Inbound
	                // Finde einen Kandidaten
	                
	                #nCandLager := #nInMaxEmptyBLK;    // 1..12
	                #nCandLine := #nInMaxEmptyLine;   // 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;
	                        #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.xEmptyJob1 := TRUE; //Inbound Empty
	                    END_IF;
	                END_IF;
	                
	                IF #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xEmptyBusy THEN
	                    #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.xEmptyReq := FALSE;
	                    #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.xEmptyJob1 := FALSE; //Inbound Empty
	                    #nState := #c1200_StartEmptyJobInbound;
	                END_IF;
	            END_REGION
	            
	        #c1200_StartEmptyJobInbound:
	            REGION 1200: Wait Empty Job Finish Inbound
	                IF #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xEmptyDone THEN
	                    #xSetTimeoutEmptyInbound := TRUE;
	                    #nState := #c000_Idle;
	                END_IF;
	            END_REGION
	            
	        #c2000_CheckEmptyJobF2H:
	            REGION 2000: Check Job: Empty to F2H
	                // Finde einen Kandidaten
	                
	                #nCandLager := #nInMaxEmptyBLK;    // 1..12
	                #nCandLine := #nInMaxEmptyLine;   // 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;
	                        #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.xEmptyJob2 := TRUE; //F2H Empty
	                    END_IF;
	                END_IF;
	                
	                IF #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xEmptyBusy THEN
	                    #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.xEmptyReq := FALSE;
	                    #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageCtrl.xEmptyJob2 := FALSE; //F2H Empty
	                    #nState := #c2200_StartEmptyJobF2H;
	                END_IF;
	            END_REGION
	            
	        #c2200_StartEmptyJobF2H:
	            REGION 2200: Wait Empty Job Finish F2H
	                IF #udt_InOutStorageSite.arrudt_Storage[#nCandLager].udt_RearrangeStorageState.xEmptyDone THEN
	                    #xSetTimeoutEmptyF2H := TRUE;
	                    #nState := #c000_Idle;
	                END_IF;
	            END_REGION
	
	            
	    END_CASE;
	END_REGION
	
	REGION Timer
	    #tOnTimeOutEmptyInbound(IN:=#xSetTimeoutEmptyInbound,
	                            PT:= #tTestTimeOutEmptyInbound);
	    
	    IF #tOnTimeOutEmptyInbound.Q THEN
	        #xSetTimeoutEmptyInbound := FALSE;
	    END_IF;
	    
	    #tOnTimeOutEmptyF2H(IN := #xSetTimeoutEmptyF2H,
	                        PT := #tTestTimeOutEmptyF2H);
	    
	    IF #tOnTimeOutEmptyF2H.Q THEN
	        #xSetTimeoutEmptyF2H := FALSE;
	    END_IF;
	    
	END_REGION
END_FUNCTION_BLOCK

