Added JobScheduler and recipe data for stations

This commit is contained in:
2026-02-24 18:28:00 +01:00
parent c4044be7bd
commit 46e294d991
33 changed files with 1454 additions and 2837 deletions

View File

@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
<POU Name="FB_BaseStation" Id="{e987477c-14c3-4a7d-9e57-317d0837328f}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION_BLOCK FB_BaseStation IMPLEMENTS I_Station
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
fbFlowRecHandler : FB_FlowRecHandler;
END_VAR
VAR
// Current loaded product flow recipe
// Index should also be used to access
// additional recipe parameters in designated pools
_iFlowRecIdx : INT := -1;
// Product available for pickup
_xProdAvail : BOOL;
// No product in station
_xEmpty : BOOL;
// Station busy
_xBusy : BOOL;
// Station done
_xDone : BOOL;
// Station has error
_xError : BOOL;
// Station id which was assigned by the scheduler
_uiStationID : UINT := 0;
// Station capabilities
_dwCapabilities : DWORD;
// Reserve token handle
_xReserved : BOOL;
_uiCurrHandle : UINT;
_uiHandleCounter : UINT := 1;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[]]></ST>
</Implementation>
<Method Name="M_HasCapabilty" Id="{d05e7240-ab9b-469b-8867-87a70dbef84e}">
<Declaration><![CDATA[METHOD M_HasCapabilty : BOOL
VAR_INPUT
dwReqCap : DWORD;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[IF (dwReqCap AND _dwCapabilities) = dwReqCap THEN
M_HasCapabilty := TRUE;
ELSE
M_HasCapabilty := FALSE;
END_IF]]></ST>
</Implementation>
</Method>
<Method Name="M_InsertProduct" Id="{d323f806-9a97-49f2-9986-a997415883bf}">
<Declaration><![CDATA[METHOD M_InsertProduct : BOOL;
VAR_INPUT
iFlowRecIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Cant insert product if there is still one
// in the station, also dont insert invalid product index
IF (_iFlowRecIdx <> -1) OR (iFlowRecIdx = -1) THEN
M_InsertProduct := FALSE;
END_IF
// Save product index
_iFlowRecIdx := iFlowRecIdx;
// Advance one step in the node control flow
fbFlowRecHandler.M_AdvJob(iIdx := iFlowRecIdx);
// Report success
M_InsertProduct := TRUE;]]></ST>
</Implementation>
</Method>
<Method Name="M_Release" Id="{924bffc5-44cc-4d02-a67d-fb0f3ab51cc2}">
<Declaration><![CDATA[METHOD M_Release : BOOL
VAR_INPUT
uiHandle : UINT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[// Check if we are reserved and the handle to release is corrcet
IF _xReserved AND (uiHandle = _uiCurrHandle) THEN
_xReserved := FALSE;
_uiCurrHandle := 0;
M_Release := TRUE;
ELSE
M_Release := FALSE;
END_IF]]></ST>
</Implementation>
</Method>
<Method Name="M_RemoveProduct" Id="{e5d01ee0-a885-4496-b522-46d093416622}">
<Declaration><![CDATA[METHOD M_RemoveProduct : INT
]]></Declaration>
<Implementation>
<ST><![CDATA[// Return index of product in machine
M_RemoveProduct := _iFlowRecIdx;
// Set product in machine invalid
_iFlowRecIdx := -1;
// There is no more a product to be ready for pickup
_xProdAvail := FALSE;
]]></ST>
</Implementation>
</Method>
<Method Name="M_Reserve" Id="{42a69458-4b78-4d35-bd61-3bb1ee3cafec}">
<Declaration><![CDATA[METHOD M_Reserve : UINT
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check if we are not already reserved
IF (NOT _xReserved) THEN
_xReserved := TRUE;
_uiCurrHandle := _uiHandleCounter;
// Increment handle
_uiHandleCounter := _uiHandleCounter + 1;
// Prevent invalid token when overflowing
IF _uiHandleCounter = 0 THEN
_uiHandleCounter := 1;
END_IF
// Return handle
M_Reserve := _uiCurrHandle;
ELSE
M_Reserve := 0;
END_IF]]></ST>
</Implementation>
</Method>
<Property Name="P_Available" Id="{44abe6d9-f47e-47fa-8e00-c1822708d7bb}">
<Declaration><![CDATA[PROPERTY P_Available : BOOL
]]></Declaration>
<Get Name="Get" Id="{3bc760b0-1ae6-43b4-920e-8dd9987fe3f7}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_Available := (NOT _xReserved) // Station not reserver
AND (_iFlowRecIdx = -1) // No product in station
AND (NOT _xBusy) // Station not busy
AND (NOT _xError); // Station no error]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_Busy" Id="{5b025893-318c-4480-ab72-6ffb8bedb715}">
<Declaration><![CDATA[PROPERTY P_Busy : BOOL
]]></Declaration>
<Get Name="Get" Id="{596ebd24-6d36-4141-9b46-0ba6929a7256}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_Busy := _xBusy;]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_Capabilities" Id="{3e87d0bb-3b77-4aa6-bc89-587ee89028c2}">
<Declaration><![CDATA[PROPERTY P_Capabilities : DWORD
]]></Declaration>
<Get Name="Get" Id="{4acbb119-eda4-4b57-8a60-16f1e982c21f}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_Capabilities := _dwCapabilities;]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_CurrFlowRecIdx" Id="{d0f37c45-4a5c-4efc-a8ff-f73e6ce76964}">
<Declaration><![CDATA[PROPERTY P_CurrFlowRecIdx : INT
]]></Declaration>
<Get Name="Get" Id="{d05b9f0f-e7a4-4e88-83d5-c67bbca6f978}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_CurrFlowRecIdx := _iFlowRecIdx;]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_HasError" Id="{1126bae7-5dd1-4592-8f95-62ea6e252ed0}">
<Declaration><![CDATA[PROPERTY P_HasError : BOOL
]]></Declaration>
<Get Name="Get" Id="{3b3e65f8-9d92-48aa-9006-6a688edebb28}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_HasError := _xError;]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_HasProduct" Id="{8b974b49-9c67-4d5f-9d0f-defc53b608cd}">
<Declaration><![CDATA[PROPERTY P_HasProduct : BOOL]]></Declaration>
<Get Name="Get" Id="{b6ba2262-99e3-4626-8d27-178b09d53a84}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_HasProduct := (_iFlowRecIdx <> -1);]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_ProdAvail" Id="{47526379-0732-4031-8d2b-4380310ef64c}">
<Declaration><![CDATA[PROPERTY P_ProdAvail : BOOL
]]></Declaration>
<Get Name="Get" Id="{c498f522-3394-4950-acf2-2e6ab02869cd}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_ProdAvail := _xProdAvail;]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_StationID" Id="{6655f302-05b0-4a57-b6f3-aa70ddd6a9f3}">
<Declaration><![CDATA[PROPERTY P_StationID : UINT
]]></Declaration>
<Get Name="Get" Id="{06281909-79f6-4e9b-a8d9-9b210420e132}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_StationID := _uiStationID;]]></ST>
</Implementation>
</Get>
<Set Name="Set" Id="{2aae20dc-be6d-4d77-adda-bb622f240eb6}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_uiStationID := P_StationID;]]></ST>
</Implementation>
</Set>
</Property>
</POU>
</TcPlcObject>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
<POU Name="FB_BaseTransport" Id="{27acb860-a7d2-4ad3-adc1-3999bf7b2d0f}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION_BLOCK FB_BaseTransport IMPLEMENTS I_Transport
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
fbJobQueue : FB_JobQueue;
END_VAR
VAR
_stTransJon : ST_TransJob;
_xAvailable : BOOL := TRUE;
_xError : BOOL;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[]]></ST>
</Implementation>
<Property Name="P_Available" Id="{b86daa99-acd7-4a83-9de5-8155146759bd}">
<Declaration><![CDATA[PROPERTY P_Available : BOOL
]]></Declaration>
<Get Name="Get" Id="{b5275c3b-e101-4786-a7c8-b2f25babf8a7}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_Available := _xAvailable;]]></ST>
</Implementation>
</Get>
</Property>
<Property Name="P_HasError" Id="{fc4055b6-6553-4a3a-9b04-c5bb792578a6}">
<Declaration><![CDATA[PROPERTY P_HasError : BOOL
]]></Declaration>
<Get Name="Get" Id="{da490431-bb00-4006-9c7f-3c5d4dab3bd2}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[P_HasError := _xError;]]></ST>
</Implementation>
</Get>
</Property>
</POU>
</TcPlcObject>

View File

@@ -0,0 +1,263 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
<POU Name="FB_FlowRecHandler" Id="{3b3f8fad-c882-4122-b8a4-83e042a5706a}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION_BLOCK FB_FlowRecHandler IMPLEMENTS I_FlowRecHandler
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
_astFlowRecPool : ARRAY[0..(POOL_SIZE - 1)] OF ST_FlowRecipe;
_iFlowRecCnt : INT := 0;
// Variable used to safely delete an entry in the pool
_stDefaultRecipe : ST_FlowRecipe;
END_VAR
VAR CONSTANT
POOL_SIZE : INT := 100;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[]]></ST>
</Implementation>
<Method Name="M_AddFlowRec" Id="{f9093d26-eb70-4f53-b224-c0a4e1b5746e}">
<Declaration><![CDATA[METHOD M_AddFlowRec : INT
VAR_INPUT
stFlowRecipe : ST_FlowRecipe;
END_VAR
VAR
i : INT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[// Check size
IF _iFlowRecCnt >= POOL_SIZE THEN
M_AddFlowRec := -1;
RETURN;
END_IF
// Find a free slot in the pool
FOR i := 0 TO (POOL_SIZE - 1) DO
// If slot is free, set the flow recipe data
IF (_astFlowRecPool[i].iProdIdx = -1) THEN
_astFlowRecPool[i] := stFlowRecipe;
_astFlowRecPool[i].iProdIdx := i;
// Increment number of recipes in the pool
_iFlowRecCnt := _iFlowRecCnt + 1;
// Return index of the added flow recipe
M_AddFlowRec := i;
RETURN;
END_IF
END_FOR]]></ST>
</Implementation>
</Method>
<Method Name="M_AdvJob" Id="{896adff3-f1cf-4fbe-80a0-e0d546958fea}">
<Declaration><![CDATA[METHOD M_AdvJob : BOOL
VAR_INPUT
iIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[IF (NOT M_CheckPoolBounds(iIdx := iIdx)) THEN
M_AdvJob := FALSE;
RETURN;
END_IF
// Advance one step throught the flow recipe
IF (NOT M_CheckNextNodeIdx(iCurrNodeIdx := iIdx, iNextNodeIdx := _astFlowRecPool[iIdx].iNextNode)) THEN
M_AdvJob := FALSE;
ELSE
_astFlowRecPool[iIdx].iCurrNode := _astFlowRecPool[iIdx].iNextNode;
END_IF
]]></ST>
</Implementation>
</Method>
<Method Name="M_CheckNextNodeIdx" Id="{01c36d2f-aaf6-49a5-816f-6f12c6f68b2a}">
<Declaration><![CDATA[METHOD PRIVATE M_CheckNextNodeIdx : BOOL
VAR_INPUT
iCurrNodeIdx : INT;
iNextNodeIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[IF (NOT M_CheckPoolBounds(iIdx := iCurrNodeIdx)) THEN
M_CheckNextNodeIdx := FALSE;
RETURN;
END_IF
IF (iNextNodeIdx) < 0 OR (iNextNodeIdx > (_astFlowRecPool[iCurrNodeIdx].uiNodeCnt - 1)) THEN
M_CheckNextNodeIdx := FALSE;
ELSE
M_CheckNextNodeIdx := TRUE;
END_IF]]></ST>
</Implementation>
</Method>
<Method Name="M_CheckPoolBounds" Id="{fce02a57-832f-4eaf-9235-5af81ab0f23b}">
<Declaration><![CDATA[METHOD PRIVATE M_CheckPoolBounds : BOOL
VAR_INPUT
iIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check index boundaries
IF (iIdx < 0) OR (iIdx > (POOL_SIZE - 1)) THEN
M_CheckPoolBounds := FALSE;
ELSE
M_CheckPoolBounds := TRUE;
END_IF]]></ST>
</Implementation>
</Method>
<Method Name="M_GetFlowRec" Id="{6c6a6f91-e451-4cac-88ce-a7a62b58d715}">
<Declaration><![CDATA[METHOD M_GetFlowRec : REFERENCE TO ST_FlowRecipe
VAR_INPUT
iIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check index boundaries
IF (NOT M_CheckPoolBounds(iIdx)) THEN
RETURN;
END_IF
// Return product data from pool
M_GetFlowRec REF= _astFlowRecPool[iIdx];]]></ST>
</Implementation>
</Method>
<Method Name="M_GetNextPrio" Id="{11bba164-4c45-4988-8c5e-3a922df5613f}">
<Declaration><![CDATA[METHOD M_GetNextPrio : UINT
VAR_INPUT
(* Flow recipe pool index*)
iIdx : INT;
END_VAR
VAR
_iNextNodeIdx : INT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[IF (NOT M_CheckPoolBounds(iIdx)) THEN
M_GetNextPrio := 0;
RETURN;
END_IF
// Get next node index
_iNextNodeIdx := _astFlowRecPool[iIdx].iNextNode;
// Check for next node bounds
IF (NOT M_CheckNextNodeIdx(iCurrNodeIdx := iIdx, iNextNodeIdx := _iNextNodeIdx)) THEN
M_GetNextPrio := 0;
RETURN;
END_IF
// Return next priority
M_GetNextPrio := _astFlowRecPool[iIdx].astNodes[_iNextNodeIdx].uiPriority;]]></ST>
</Implementation>
</Method>
<Method Name="M_GetNextProcReq" Id="{c0f8e1bf-d696-4712-b3aa-63621a5789cf}">
<Declaration><![CDATA[METHOD M_GetNextProcReq : BOOL
VAR_INPUT
(* Flow recipe pool index*)
iIdx : INT;
END_VAR
VAR_OUTPUT
(* Process requirements bitmask*)
dwProcReq : DWORD;
END_VAR
VAR
_iNextNodeIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check index boundaries
IF (NOT M_CheckPoolBounds(iIdx)) THEN
dwProcReq := 0;
M_GetNextProcReq := FALSE;
RETURN;
END_IF
// Return product data from pool
_iNextNodeIdx := _astFlowRecPool[iIdx].iNextNode;
// Check for valid next node
IF (NOT M_CheckNextNodeIdx(iCurrNodeIdx := iIdx, iNextNodeIdx := _iNextNodeIdx)) THEN
dwProcReq := 0;
M_GetNextProcReq := FALSE;
RETURN;
END_IF
dwProcReq := _astFlowRecPool[iIdx].astNodes[_iNextNodeIdx].dwReqCap;
M_GetNextProcReq := TRUE;]]></ST>
</Implementation>
</Method>
<Method Name="M_RemFlowRec" Id="{4ca29998-c702-42b0-a307-733174741ab8}">
<Declaration><![CDATA[METHOD M_RemFlowRec : BOOL
VAR_INPUT
iIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check index boundaries
IF (NOT M_CheckPoolBounds(iIdx)) THEN
M_RemFlowRec := FALSE;
END_IF
// Reset flow recipe in pool slot to default recipe
_astFlowRecPool[iIdx] := _stDefaultRecipe;
// Return success
M_RemFlowRec := TRUE;]]></ST>
</Implementation>
</Method>
<Method Name="M_ReportResult" Id="{16053eca-a7a6-4800-b521-1a15f3dfce4f}">
<Declaration><![CDATA[// Sets the result of the current node operation
// and updates the reference to the next node id
// depending on the reported result
METHOD M_ReportResult : BOOL
VAR_INPUT
iIdx : INT;
xResult : BOOL;
END_VAR
VAR
_iCurrNodeIdx : INT;
_iNextNodeIdx : INT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[// Check index boundaries
IF (NOT M_CheckPoolBounds(iIdx)) THEN
M_ReportResult := FALSE;
RETURN;
END_IF
// Set current flow node result flag
_iCurrNodeIdx := _astFlowRecPool[iIdx].iCurrNode;
_astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].xSuccess := xResult;
// Update ref for the next node
IF xResult THEN
_iNextNodeIdx := _astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].iNextNodeSuccess;
ELSE
// Check if we have retries left
IF _astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].uiCurrRetries < _astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].uiMaxRetries THEN
// Increment retry counter
_astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].uiCurrRetries := _astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].uiCurrRetries + 1;
// Get next retry node
_iNextNodeIdx := _astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].iNextNodeRetry;
ELSE
// Get next fail node
_iNextNodeIdx := _astFlowRecPool[iIdx].astNodes[_iCurrNodeIdx].iNextNodeFail;
END_IF
END_IF
// Check index boundaries
IF (_iNextNodeIdx < 0) OR (_iNextNodeIdx > (_astFlowRecPool[iIdx].uiNodeCnt - 1)) THEN
// If there is no next node set to -1
// to indicate, that this was the last node in the
// flow recipe
_astFlowRecPool[iIdx].iNextNode := -1;
ELSE
_astFlowRecPool[iIdx].iNextNode := _iNextNodeIdx;
END_IF
// Return success
M_ReportResult := TRUE;]]></ST>
</Implementation>
</Method>
</POU>
</TcPlcObject>

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
<POU Name="FB_JobQueue" Id="{b05c10aa-a132-426c-9adb-9c3d92e348cd}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION_BLOCK FB_JobQueue
VAR_INPUT
xEnableAging : BOOL := FALSE;
END_VAR
VAR_OUTPUT
END_VAR
VAR
_astJobQueue : ARRAY[0..(GVL_Scheduler.MAX_JOBS_IN_QUEUE - 1)] OF ST_TransJob;
_uiJobCount : UINT := 0;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// If aging is enable recalculate priorities
IF xEnableAging THEN
M_CalcAgingPrio();
END_IF
]]></ST>
</Implementation>
<Method Name="FB_Init" Id="{59b2fc0f-4c7e-4fd5-97a2-d54f83d37be4}">
<Declaration><![CDATA[//FB_Init is always available implicitly and it is used primarily for initialization.
//The return value is not evaluated. For a specific influence, you can also declare the
//methods explicitly and provide additional code there with the standard initialization
//code. You can evaluate the return value.
METHOD FB_Init: BOOL
VAR_INPUT
bInitRetains: BOOL; // TRUE: the retain variables are initialized (reset warm / reset cold)
bInCopyCode: BOOL; // TRUE: the instance will be copied to the copy code afterward (online change)
END_VAR
VAR
_uiCnt : UINT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[// No jobs in queue
_uiJobCount := 0;]]></ST>
</Implementation>
</Method>
<Method Name="M_AddJob" Id="{c7959fb9-7c57-4429-b989-c0befdd4ac38}">
<Declaration><![CDATA[METHOD M_AddJob : BOOL
VAR_INPUT
stJob : ST_TransJob;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check if queue is full
IF _uiJobCount >= GVL_Scheduler.MAX_JOBS_IN_QUEUE THEN
M_AddJob := FALSE;
RETURN;
END_IF
_astJobQueue[_uiJobCount] := stJob;
_uiJobCount := _uiJobCount + 1;
M_AddJob := TRUE;]]></ST>
</Implementation>
</Method>
<Method Name="M_CalcAgingPrio" Id="{7ee1cc23-b7ac-4255-862a-74b0b696f2cb}">
<Declaration><![CDATA[METHOD PRIVATE M_CalcAgingPrio
VAR
i : UINT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[// Calculate ageing priority with overflow check
FOR i := 0 TO _uiJobCount DO
IF _astJobQueue[i].uiPrio < GVL_Scheduler.MAX_PRIORITY THEN
IF _astJobQueue[i].uiPrio > (GVL_Scheduler.MAX_PRIORITY - GVL_Scheduler.AGING_STEP) THEN
_astJobQueue[i].uiPrio := GVL_Scheduler.MAX_PRIORITY;
ELSE
_astJobQueue[i].uiPrio := _astJobQueue[i].uiPrio + GVL_Scheduler.AGING_STEP;
END_IF
END_IF
END_FOR]]></ST>
</Implementation>
</Method>
<Method Name="M_GetHighest" Id="{26fa0379-d921-41a3-a15c-b3ff597c1d37}">
<Declaration><![CDATA[METHOD M_GetHighest : BOOL
VAR_OUTPUT
stJob : ST_TransJob;
END_VAR
VAR
_uiIndex : UINT := 0;
_rDynPrio : REAL;
_uiCnt : UINT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[// No jobs to return
IF _uiJobCount = 0 THEN
M_GetHighest := FALSE;
RETURN;
END_IF
// Find job with highest priority also update dyn prio
FOR _uiCnt := 0 TO (_uiJobCount-1) DO
// Check for highest priority
IF _astJobQueue[_uiCnt].uiPrio > _astJobQueue[_uiIndex].uiPrio THEN
_uiIndex := _uiCnt;
END_IF
END_FOR
// Output job
stJob := _astJobQueue[_uiIndex];
// Close gap with last element
_astJobQueue[_uiIndex] := _astJobQueue[_uiJobCount - 1];
// Adjust job number in queue
_uiJobCount := _uiJobCount - 1;
// Repost success
M_GetHighest := TRUE;]]></ST>
</Implementation>
</Method>
</POU>
</TcPlcObject>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
<POU Name="FB_Scheduler" Id="{af4eba28-c46c-4c30-8885-00e91057da4d}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION_BLOCK FB_Scheduler
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
fbFlowRecHandler : FB_FlowRecHandler;
fbJobQueue : FB_JobQueue;
END_VAR
VAR
// Array needs to start at 1 so that 0 can be an invalid station
// Maybe later change this to int ant -1
_aiStations : ARRAY[1..(GVL_Scheduler.MAX_STATIONS)] OF I_Station;
_uiStationCount : UINT := 0;
_fbTransport : I_Transport;
_uiCnt : UINT;
_uiNextAvailStation : UINT;
_iFlowRecIdx : INT;
_dwNextProcReq : DWORD;
_stTmpJob : ST_TransJob;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[// Call job queue
fbJobQueue(xEnableAging := FALSE);
// Avoid invalid interface refs
IF (_uiStationCount = 0) OR (_fbTransport = 0) THEN
RETURN;
END_IF
IF (NOT _fbTransport.P_Available) THEN
// No need to check the stations if we don't have a transport ready
RETURN;
END_IF
FOR _uiCnt := 1 TO _uiStationCount DO
IF _aiStations[_uiCnt].P_ProdAvail THEN
// Get flow recipe index
_iFlowRecIdx := _aiStations[_uiCnt].P_CurrFlowRecIdx;
// Get next process requirements
IF (NOT fbFlowRecHandler.M_GetNextProcReq(iIdx := _iFlowRecIdx, dwProcReq => _dwNextProcReq)) THEN
CONTINUE;
END_IF
// Find next available station according to recipe and available station
_uiNextAvailStation := M_FindNextAvailStation(_dwNextProcReq);
// Check if there is a station available
IF _uiNextAvailStation <> 0 THEN
// Reserve source station
_stTmpJob.uiFromStationHandle := _aiStations[_uiCnt].M_Reserve();
// Reserve target station
_stTmpJob.uiToStationHandle := _aiStations[_uiNextAvailStation].M_Reserve();
// Check if we could reserve the stations
IF (_stTmpJob.uiToStationHandle <> 0) AND (_stTmpJob.uiFromStationHandle <> 0) THEN
// Create transport job
_stTmpJob.uiFromStation := _uiCnt;
_stTmpJob.uiToStation := _uiNextAvailStation;
_stTmpJob.uiPrio := fbFlowRecHandler.M_GetNextPrio(iIdx := _iFlowRecIdx);
// Add job to job queue
fbJobQueue.M_AddJob(stJob := _stTmpJob);
ELSE
// Remove registrations from stations
_aiStations[_uiCnt].M_Release(_stTmpJob.uiFromStationHandle);
_aiStations[_uiNextAvailStation].M_Release(_stTmpJob.uiToStationHandle);
END_IF
END_IF
END_IF
END_FOR]]></ST>
</Implementation>
<Method Name="M_ClearStations" Id="{7c667e8a-38f2-48a9-afff-834e87e4d3f6}">
<Declaration><![CDATA[METHOD M_ClearStations
VAR_INPUT
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Remove all registered stations from array
MEMSET(destAddr := ADR(_aiStations), fillByte := 0, n := SIZEOF(_aiStations));
// Reset number of registered stations
_uiStationCount := 0;]]></ST>
</Implementation>
</Method>
<Method Name="M_FindNextAvailStation" Id="{75382296-32be-4724-b020-39273aa8358c}">
<Declaration><![CDATA[METHOD PRIVATE M_FindNextAvailStation : UINT
VAR_INPUT
dwProcReq : DWORD;
END_VAR
VAR
_uiCnt : UINT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[FOR _uiCnt := 1 TO _uiStationCount DO
IF _aiStations[_uiCnt].M_HasCapabilty(dwProcReq) AND _aiStations[_uiCnt].P_Available THEN
M_FindNextAvailStation := _uiCnt;
RETURN;
END_IF
END_FOR
M_FindNextAvailStation := 0;]]></ST>
</Implementation>
</Method>
<Method Name="M_GetStation" Id="{e8451683-3cd1-4d6f-99dd-76f33ecd008d}">
<Declaration><![CDATA[METHOD M_GetStation : I_Station
VAR_INPUT
iStationIdx : INT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[IF iStationIdx < 1 OR iStationIdx > (GVL_Scheduler.MAX_STATIONS - 1) THEN
M_GetStation := 0;
RETURN;
END_IF
M_GetStation := _aiStations[iStationIdx];]]></ST>
</Implementation>
</Method>
<Method Name="M_Register" Id="{6a14034a-d5c0-46c1-a13b-418885a02563}">
<Declaration><![CDATA[METHOD M_Register
VAR_INPUT
fbStation : I_Station;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check if station is valid interface
IF fbStation = 0 THEN
RETURN;
END_IF
// Check if we have free slots
IF _uiStationCount < GVL_Scheduler.MAX_STATIONS THEN
_uiStationCount := _uiStationCount + 1;
_aiStations[_uiStationCount] := fbStation;
_aiStations[_uiStationCount].P_StationID := _uiStationCount;
END_IF]]></ST>
</Implementation>
</Method>
<Method Name="M_RegisterTransport" Id="{a85ebd03-27a5-40c5-bd8f-ade9f2eac962}">
<Declaration><![CDATA[METHOD M_RegisterTransport
VAR_INPUT
fbTransport : I_Transport;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Check if transport is valid interface
IF fbTransport = 0 THEN
RETURN;
END_IF
// Set transport interface
_fbTransport := fbTransport;
]]></ST>
</Implementation>
</Method>
</POU>
</TcPlcObject>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
<GVL Name="GVL_Test" Id="{795c0f41-8233-4565-bfb5-a9d7fd82a697}">
<Declaration><![CDATA[{attribute 'qualified_only'}
VAR_GLOBAL
END_VAR]]></Declaration>
</GVL>
</TcPlcObject>