Загрузка данных
// ============================================================
// Синхронизация планов с физическим состоянием линии
// ============================================================
FOR i := 1 TO 17 DO
IF GVL.vanBusy[i] THEN
GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i],
MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i],
GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i],
MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i],
GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
ELSE
GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.sysTime);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.sysTime);
END_IF
END_FOR
// ============================================================
// Однократная инициализация (firstScan – VAR_STAT в FB)
// ============================================================
IF gvl.firstScan THEN
FOR i := 1 TO 17 DO
GVL.planVatFreeTime[i] := GVL.sysTime;
GVL.planVatBusyUntil[i] := GVL.sysTime;
END_FOR;
GVL.loadPosBusy := FALSE;
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentPos = 0 THEN
GVL.loadPosBusy := TRUE;
EXIT;
END_IF
END_FOR
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
GVL.podveskas[a].bufferEntryTime := GVL.sysTime;
END_IF
END_FOR
RebuildBufferAndLoadStatus();
GVL.aoJobSource := -1;
GVL.aoJobDest := -1;
gvl.firstScan := FALSE;
END_IF
// ============================================================
// Обработка кнопки Reset
// ============================================================
IF GVL.Reset THEN
GVL.Reset := FALSE;
GVL.resetActive := TRUE;
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentStep >= 0 THEN
GVL.podveskas[a].currentStep := -1;
GVL.podveskas[a].xLoaded := FALSE;
GVL.podveskas[a].xUnloaded := FALSE;
END_IF
END_FOR
RebuildBufferAndLoadStatus();
END_IF
IF GVL.resetActive THEN
allEvacuated := TRUE;
FOR a := 1 TO 4 DO
IF NOT ((GVL.podveskas[a].currentPos = 0) OR
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103)) THEN
allEvacuated := FALSE;
EXIT;
END_IF
END_FOR
IF allEvacuated THEN
GVL.resetActive := FALSE;
END_IF
END_IF
// ============================================================
// Основной блок – только когда автооператор свободен
// ============================================================
IF NOT GVL.aoBusy THEN
// Сброс переменных задания перед каждым поиском
RebuildBufferAndLoadStatus();
GVL.aoJobSource := -1;
GVL.aoJobDest := -1;
GVL.candidate := 0;
// ---------- Обработка кнопок "Загружена" / "Разгружена" ----------
IF NOT GVL.resetActive THEN
podAtPos0 := 0;
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentPos = 0 THEN
podAtPos0 := a;
EXIT;
END_IF
END_FOR
IF podAtPos0 > 0 THEN
// ----- На позиции 0 уже стоит подвеска -----
IF GVL.PodLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
IF (Control_HMI.processType = E_RouteType.eTin) OR
(Control_HMI.processType = E_RouteType.eSilver) THEN
GVL.podveskas[podAtPos0].processType := Control_HMI.processType;
GVL.podveskas[podAtPos0].xLoaded := TRUE;
GVL.podveskas[podAtPos0].xUnloaded := FALSE;
GVL.podveskas[podAtPos0].currentStep := 0;
GVL.PodLoaded := FALSE;
GVL.BtnLoaded_Sn_push := FALSE;
GVL.BtnLoaded_Ag_push := FALSE;
// Очищаем старые планы перед новой симуляцией
FOR i := 1 TO 17 DO
GVL.podveskas[podAtPos0].simStartTime[i] := DT#1970-01-01-00:00:00;
//GVL.podveskas[podAtPos0].simFreeTime[i] := DT#1970-01-01-00:00:00;
GVL.podveskas[podAtPos0].simBusyUntil[i] := DT#1970-01-01-00:00:00;
END_FOR;
route := GVL.podveskas[podAtPos0].processType;
firstVat := GetVatFromRecipe(route, 1);
IF firstVat > 0 THEN
IF EvaluatePodveska(podveska := GVL.podveskas[podAtPos0]) THEN
// Симуляция успешна – копируем планы и стартуем в линию
FOR i := 1 TO 17 DO
//GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[podAtPos0].simFreeTime[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[podAtPos0].simBusyUntil[i]);
END_FOR;
GVL.aoJobSource := 0;
GVL.aoJobDest := firstVat;
GVL.candidate := podAtPos0;
GVL.aoBusy := TRUE;
RETURN;
ELSE
// Симуляция неудачна – отправляем в буфер
IF Main.bufferHolds[1] = 0 THEN
GVL.aoJobDest := 101;
ELSIF Main.bufferHolds[2] = 0 THEN
GVL.aoJobDest := 102;
ELSIF Main.bufferHolds[3] = 0 THEN
GVL.aoJobDest := 103;
ELSE
GVL.aoJobDest := -1;
END_IF
IF GVL.aoJobDest > 0 THEN
GVL.aoJobSource := 0;
GVL.candidate := podAtPos0;
GVL.aoBusy := TRUE;
RETURN;
END_IF
// Если все буферы заняты, подвеска остаётся на позиции 0
END_IF
END_IF
ELSE
GVL.PodLoaded := FALSE;
END_IF
END_IF
// *** ИЗМЕНЁННАЯ ОБРАБОТКА КНОПКИ "РАЗГРУЖЕНО" ***
IF GVL.PodUnLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
GVL.podveskas[podAtPos0].xUnloaded := TRUE;
GVL.podveskas[podAtPos0].xLoaded := FALSE;
GVL.PodUnLoaded := FALSE;
GVL.BtnUnLoaded_push := FALSE;
// Если все буферы заняты, немедленно перемещаем разгруженную подвеску в сушку (1)
IF (Main.bufferHolds[1] <> 0) AND (Main.bufferHolds[2] <> 0) AND (Main.bufferHolds[3] <> 0) THEN
IF NOT GVL.vanBusy[1] THEN
GVL.aoJobSource := 0;
GVL.aoJobDest := 1;
GVL.candidate := podAtPos0;
GVL.aoBusy := TRUE;
gvl.AutoLoadFromBuffer := TRUE; // запрос на автоматическую загрузку
RETURN;
END_IF
END_IF
END_IF
ELSE
// ----- Позиция 0 свободна -----
// *** АВТОМАТИЧЕСКИЙ ВЫЗОВ ПОДВЕСКИ ИЗ БУФЕРА ПОСЛЕ ЭКСТРЕННОЙ РАЗГРУЗКИ ***
IF gvl.AutoLoadFromBuffer THEN
bestIdx := 0;
bestTime := DT#2100-01-01-00:00:00;
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentStep = -1) AND
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a].bufferEntryTime;
bestIdx := a;
END_IF
END_IF
END_FOR
IF bestIdx > 0 THEN
GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
GVL.aoJobDest := 0;
GVL.candidate := bestIdx;
gvl.AutoLoadFromBuffer := FALSE;
GVL.podveskas[bestIdx].xLoaded := FALSE;
GVL.podveskas[bestIdx].xUnloaded := FALSE;
GVL.aoBusy := TRUE;
RETURN;
ELSE
gvl.AutoLoadFromBuffer := FALSE;
END_IF
END_IF
IF GVL.PodLoaded THEN
bestIdx := 0;
bestTime := DT#2100-01-01-00:00:00;
// Первый проход: ищем подвески с xUnloaded = FALSE (готовые к загрузке)
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentStep = -1) AND
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) AND
(NOT GVL.podveskas[a].xUnloaded) THEN
IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a].bufferEntryTime;
bestIdx := a;
END_IF
END_IF
END_FOR
// Если не нашли готовых к загрузке, берём любую завершённую (xUnloaded = TRUE)
IF bestIdx = 0 THEN
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentStep = -1) AND
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a].bufferEntryTime;
bestIdx := a;
END_IF
END_IF
END_FOR
END_IF
IF bestIdx > 0 THEN
GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
GVL.aoJobDest := 0;
GVL.candidate := bestIdx;
GVL.PodLoaded := FALSE;
GVL.BtnLoaded_Sn_push := FALSE;
GVL.BtnLoaded_Ag_push := FALSE;
GVL.podveskas[bestIdx].xLoaded := FALSE;
GVL.podveskas[bestIdx].xUnloaded := FALSE;
GVL.aoBusy := TRUE;
RETURN;
ELSE
GVL.PodLoaded := FALSE;
GVL.BtnLoaded_Sn_push := FALSE;
GVL.BtnLoaded_Ag_push := FALSE;
END_IF
END_IF
END_IF
END_IF
// ============================================================
// НАКОПЛЕНИЕ ПЛАНОВ ВСЕХ АКТИВНЫХ ПОДВЕСОК В ГЛОБАЛЬНЫЙ ПЛАН
// ============================================================
// 1. Сброс глобальных планов до базового состояния (только факт. занятость)
FOR i := 1 TO 17 DO
IF GVL.vanBusy[i] THEN
GVL.planVatFreeTime[i] := MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i],
GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0));
GVL.planVatBusyUntil[i] := GVL.planVatFreeTime[i];
ELSE
GVL.planVatFreeTime[i] := GVL.sysTime;
GVL.planVatBusyUntil[i] := GVL.sysTime;
END_IF
END_FOR;
// 2. Последовательное добавление планов активных подвесок
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentStep >= 0 THEN
EvaluatePodveska(podveska := GVL.podveskas[a]);
FOR i := 1 TO 17 DO
GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simBusyUntil[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
END_FOR;
END_IF;
END_FOR;
// ---------- Основной цикл выбора победителя ----------
highestPrio := -201;
candidateLocal := 0;
aoJobSourceLocal := -1;
aoJobDestLocal := -1;
evacuateLeader := FALSE;
FOR a := 1 TO 4 DO
evacuateCandidate := FALSE;
sourcePos := GVL.podveskas[a].currentPos;
route := GVL.podveskas[a].processType;
curStep := GVL.podveskas[a].currentStep;
expectedVat := GetVatFromRecipe(route, curStep);
GetTargetVatForPodveska := 0;
IF curStep >= 0 AND curStep <= 13 THEN
CASE route OF
E_RouteType.eTin:
GetTargetVatForPodveska := TargetVars.recipeTin[curStep + 1].vatNo;
E_RouteType.eSilver:
GetTargetVatForPodveska := TargetVars.recipeSilver[curStep + 1].vatNo;
END_CASE
END_IF
targetVat := GetTargetVatForPodveska;
// ---------- Логика для завершённых подвесок ----------
IF curStep < 0 THEN
IF sourcePos = 0 THEN
IF GVL.podveskas[a].xUnloaded THEN
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
IsPodveskaReadyToUnload := TRUE;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
IsPodveskaReadyToUnload := TRUE;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
IsPodveskaReadyToUnload := TRUE;
ELSE
// Все буферы заняты – пытаемся освободить через ванну 1
IF NOT GVL.vanBusy[1] THEN
bestIdx := 0;
bestTime := DT#2100-01-01-00:00:00;
FOR a2 := 1 TO 4 DO
IF (GVL.podveskas[a2].currentStep = -1) AND
(GVL.podveskas[a2].currentPos >= 101 AND GVL.podveskas[a2].currentPos <= 103) THEN
IF (GVL.podveskas[a2].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a2].bufferEntryTime;
bestIdx := a2;
END_IF
END_IF
END_FOR
IF bestIdx > 0 THEN
candidateLocal := bestIdx;
aoJobSourceLocal := GVL.podveskas[bestIdx].currentPos;
aoJobDestLocal := 1;
evacuateLeader := FALSE;
EXIT;
END_IF
END_IF
IsPodveskaReadyToUnload := FALSE;
END_IF
ELSE
IsPodveskaReadyToUnload := FALSE;
CONTINUE;
END_IF
ELSIF sourcePos >= 101 AND sourcePos <= 103 THEN
CONTINUE;
ELSIF sourcePos >= 1 AND sourcePos <= 17 THEN
IsPodveskaReadyToUnload := TRUE;
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
ELSIF NOT GVL.loadPosBusy THEN
targetVat := 0;
ELSE
targetVat := -1;
END_IF
ELSE
CONTINUE;
END_IF
ELSE
IsPodveskaReadyToUnload := FALSE;
IF sourcePos = 0 OR (sourcePos >= 101 AND sourcePos <= 103) THEN
IsPodveskaReadyToUnload := TRUE;
END_IF
IF sourcePos >= 1 AND sourcePos <= 17 THEN
IF sourcePos <> expectedVat THEN
IsPodveskaReadyToUnload := FALSE;
END_IF
IF GVL.vanEndTime[sourcePos] < GVL.sysTime THEN
IsPodveskaReadyToUnload := TRUE;
END_IF
IF (sourcePos = 4 OR sourcePos = 8 OR sourcePos = 13 OR sourcePos = 16) AND GVL.vanBusy[sourcePos - 1] THEN
IsPodveskaReadyToUnload := FALSE;
END_IF
END_IF
END_IF
IF IsPodveskaReadyToUnload THEN
IF targetVat < 0 THEN
CanLoadTargetVat := FALSE;
ELSIF targetVat = 0 THEN
CanLoadTargetVat := (NOT GVL.loadPosBusy);
ELSIF targetVat >= 1 AND targetVat <= 17 THEN
CanLoadTargetVat := NOT GVL.vanBusy[targetVat];
ELSIF targetVat >= 101 AND targetVat <= 103 THEN
CanLoadTargetVat := (Main.bufferHolds[targetVat - 100] = 0);
ELSE
CanLoadTargetVat := FALSE;
END_IF
IF (sourcePos >= 1 AND sourcePos <= 17) AND (curStep >= 0) AND (targetVat = 0) AND (NOT CanLoadTargetVat) THEN
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
CanLoadTargetVat := TRUE;
evacuateCandidate := TRUE;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
CanLoadTargetVat := TRUE;
evacuateCandidate := TRUE;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
CanLoadTargetVat := TRUE;
evacuateCandidate := TRUE;
END_IF
END_IF
IF (sourcePos = 0) AND (curStep >= 0) AND (NOT CanLoadTargetVat) THEN
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
CanLoadTargetVat := TRUE;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
CanLoadTargetVat := TRUE;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
CanLoadTargetVat := TRUE;
ELSE
CanLoadTargetVat := FALSE;
END_IF
END_IF
IF CanLoadTargetVat THEN
// Сохраняем текущий коллективный план
FOR i := 1 TO 17 DO
savedFree[i] := GVL.planVatFreeTime[i];
savedBusy[i] := GVL.planVatBusyUntil[i];
END_FOR;
// Временно добавляем планы кандидата
FOR i := 1 TO 17 DO
//GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simFreeTime[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
END_FOR;
// Рассчитываем приоритет
prio := 0;
IF (route = E_RouteType.eTin AND curStep = 8 AND sourcePos = 10) OR
(route = E_RouteType.eSilver AND curStep = 8 AND sourcePos = 6) THEN
prio := prio + 1000;
END_IF
IF sourcePos >= 1 AND sourcePos <= 17 AND curStep > 0 THEN
prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.vanEndTime[sourcePos]) / 1000);
END_IF
IF (route = E_RouteType.eTin AND targetVat = 11) OR
(route = E_RouteType.eSilver AND targetVat = 5) THEN
prio := prio + 500;
END_IF
IF ((sourcePos = 0) OR (sourcePos >= 101 AND sourcePos <= 103)) AND (curStep >= 0) THEN
prio := prio + 700;
END_IF
IF sourcePos >= 101 AND sourcePos <= 103 THEN
prio := prio - 200;
prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.podveskas[a].bufferEntryTime) / 1000);
END_IF
IF (sourcePos = 3 OR sourcePos = 7 OR sourcePos = 12 OR sourcePos = 15) THEN
IF GVL.vanBusy[sourcePos + 1] THEN
prio := prio + 600;
END_IF
END_IF
IF (curStep < 0) AND (sourcePos = 0) THEN
prio := prio + 2600;
END_IF
IF (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
prio := prio + 2500;
END_IF
IF GVL.resetActive AND (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
prio := prio + 3000;
END_IF
gvl.testPrio[a] := prio;
IF prio > highestPrio THEN
highestPrio := prio;
candidateLocal := a;
aoJobSourceLocal := sourcePos;
aoJobDestLocal := targetVat;
evacuateLeader := evacuateCandidate;
END_IF;
// Восстанавливаем коллективный план
FOR i := 1 TO 17 DO
GVL.planVatFreeTime[i] := savedFree[i];
GVL.planVatBusyUntil[i] := savedBusy[i];
END_FOR;
END_IF
END_IF
END_FOR
// ----------------------------------------------------------
// Запуск движения для выбранного кандидата
// ----------------------------------------------------------
IF candidateLocal > 0 THEN
GVL.candidate := candidateLocal;
GVL.aoJobSource := aoJobSourceLocal;
GVL.aoJobDest := aoJobDestLocal;
IF evacuateLeader THEN
GVL.podveskas[GVL.candidate].currentStep := -1;
GVL.podveskas[GVL.candidate].xLoaded := FALSE;
END_IF;
// Окончательная фиксация: копируем личные планы победителя в глобальные
FOR i := 1 TO 17 DO
//GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[GVL.candidate].simFreeTime[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[GVL.candidate].simBusyUntil[i]);
END_FOR;
GVL.aoBusy := TRUE;
END_IF;
END_IF; // конец IF NOT GVL.aoBusy
// ============================================================
// Синхронизация планов с физическим состоянием линии
// ============================================================
FOR i := 1 TO 17 DO
IF GVL.vanBusy[i] THEN
GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i],
MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i],
GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i],
MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i],
GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
ELSE
GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.sysTime);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.sysTime);
END_IF
END_FOR
// ============================================================
// Однократная инициализация (firstScan – VAR_STAT в FB)
// ============================================================
IF gvl.firstScan THEN
FOR i := 1 TO 17 DO
GVL.planVatFreeTime[i] := GVL.sysTime;
GVL.planVatBusyUntil[i] := GVL.sysTime;
END_FOR;
GVL.loadPosBusy := FALSE;
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentPos = 0 THEN
GVL.loadPosBusy := TRUE;
EXIT;
END_IF
END_FOR
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
GVL.podveskas[a].bufferEntryTime := GVL.sysTime;
END_IF
END_FOR
RebuildBufferAndLoadStatus();
GVL.aoJobSource := -1;
GVL.aoJobDest := -1;
gvl.firstScan := FALSE;
END_IF
// ============================================================
// Обработка кнопки Reset
// ============================================================
IF GVL.Reset THEN
GVL.Reset := FALSE;
GVL.resetActive := TRUE;
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentStep >= 0 THEN
GVL.podveskas[a].currentStep := -1;
GVL.podveskas[a].xLoaded := FALSE;
GVL.podveskas[a].xUnloaded := FALSE;
END_IF
END_FOR
RebuildBufferAndLoadStatus();
END_IF
IF GVL.resetActive THEN
allEvacuated := TRUE;
FOR a := 1 TO 4 DO
IF NOT ((GVL.podveskas[a].currentPos = 0) OR
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103)) THEN
allEvacuated := FALSE;
EXIT;
END_IF
END_FOR
IF allEvacuated THEN
GVL.resetActive := FALSE;
END_IF
END_IF
// ============================================================
// Основной блок – только когда автооператор свободен
// ============================================================
IF NOT GVL.aoBusy THEN
// Сброс переменных задания перед каждым поиском
RebuildBufferAndLoadStatus();
GVL.aoJobSource := -1;
GVL.aoJobDest := -1;
GVL.candidate := 0;
// ---------- Обработка кнопок "Загружена" / "Разгружена" ----------
IF NOT GVL.resetActive THEN
podAtPos0 := 0;
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentPos = 0 THEN
podAtPos0 := a;
EXIT;
END_IF
END_FOR
IF podAtPos0 > 0 THEN
// ----- На позиции 0 уже стоит подвеска -----
IF GVL.PodLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
IF (Control_HMI.processType = E_RouteType.eTin) OR
(Control_HMI.processType = E_RouteType.eSilver) THEN
GVL.podveskas[podAtPos0].processType := Control_HMI.processType;
GVL.podveskas[podAtPos0].xLoaded := TRUE;
GVL.podveskas[podAtPos0].xUnloaded := FALSE;
GVL.podveskas[podAtPos0].currentStep := 0;
GVL.PodLoaded := FALSE;
GVL.BtnLoaded_Sn_push := FALSE;
GVL.BtnLoaded_Ag_push := FALSE;
// Очищаем старые планы перед новой симуляцией
FOR i := 1 TO 17 DO
GVL.podveskas[podAtPos0].simStartTime[i] := DT#1970-01-01-00:00:00;
//GVL.podveskas[podAtPos0].simFreeTime[i] := DT#1970-01-01-00:00:00;
GVL.podveskas[podAtPos0].simBusyUntil[i] := DT#1970-01-01-00:00:00;
END_FOR;
route := GVL.podveskas[podAtPos0].processType;
firstVat := GetVatFromRecipe(route, 1);
IF firstVat > 0 THEN
IF EvaluatePodveska(podveska := GVL.podveskas[podAtPos0]) THEN
// Симуляция успешна – копируем планы и стартуем в линию
FOR i := 1 TO 17 DO
//GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[podAtPos0].simFreeTime[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[podAtPos0].simBusyUntil[i]);
END_FOR;
GVL.aoJobSource := 0;
GVL.aoJobDest := firstVat;
GVL.candidate := podAtPos0;
GVL.aoBusy := TRUE;
RETURN;
ELSE
// Симуляция неудачна – отправляем в буфер
IF Main.bufferHolds[1] = 0 THEN
GVL.aoJobDest := 101;
ELSIF Main.bufferHolds[2] = 0 THEN
GVL.aoJobDest := 102;
ELSIF Main.bufferHolds[3] = 0 THEN
GVL.aoJobDest := 103;
ELSE
GVL.aoJobDest := -1;
END_IF
IF GVL.aoJobDest > 0 THEN
GVL.aoJobSource := 0;
GVL.candidate := podAtPos0;
GVL.aoBusy := TRUE;
RETURN;
END_IF
// Если все буферы заняты, подвеска остаётся на позиции 0
END_IF
END_IF
ELSE
GVL.PodLoaded := FALSE;
END_IF
END_IF
// *** ИЗМЕНЁННАЯ ОБРАБОТКА КНОПКИ "РАЗГРУЖЕНО" ***
IF GVL.PodUnLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
GVL.podveskas[podAtPos0].xUnloaded := TRUE;
GVL.podveskas[podAtPos0].xLoaded := FALSE;
GVL.PodUnLoaded := FALSE;
GVL.BtnUnLoaded_push := FALSE;
// Если все буферы заняты, немедленно перемещаем разгруженную подвеску в сушку (1)
IF (Main.bufferHolds[1] <> 0) AND (Main.bufferHolds[2] <> 0) AND (Main.bufferHolds[3] <> 0) THEN
IF NOT GVL.vanBusy[1] THEN
GVL.aoJobSource := 0;
GVL.aoJobDest := 1;
GVL.candidate := podAtPos0;
GVL.aoBusy := TRUE;
gvl.AutoLoadFromBuffer := TRUE; // запрос на автоматическую загрузку
RETURN;
END_IF
END_IF
END_IF
ELSE
// ----- Позиция 0 свободна -----
// *** АВТОМАТИЧЕСКИЙ ВЫЗОВ ПОДВЕСКИ ИЗ БУФЕРА ПОСЛЕ ЭКСТРЕННОЙ РАЗГРУЗКИ ***
IF gvl.AutoLoadFromBuffer THEN
bestIdx := 0;
bestTime := DT#2100-01-01-00:00:00;
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentStep = -1) AND
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a].bufferEntryTime;
bestIdx := a;
END_IF
END_IF
END_FOR
IF bestIdx > 0 THEN
GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
GVL.aoJobDest := 0;
GVL.candidate := bestIdx;
gvl.AutoLoadFromBuffer := FALSE;
GVL.podveskas[bestIdx].xLoaded := FALSE;
GVL.podveskas[bestIdx].xUnloaded := FALSE;
GVL.aoBusy := TRUE;
RETURN;
ELSE
gvl.AutoLoadFromBuffer := FALSE;
END_IF
END_IF
IF GVL.PodLoaded THEN
bestIdx := 0;
bestTime := DT#2100-01-01-00:00:00;
// Первый проход: ищем подвески с xUnloaded = FALSE (готовые к загрузке)
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentStep = -1) AND
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) AND
(NOT GVL.podveskas[a].xUnloaded) THEN
IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a].bufferEntryTime;
bestIdx := a;
END_IF
END_IF
END_FOR
// Если не нашли готовых к загрузке, берём любую завершённую (xUnloaded = TRUE)
IF bestIdx = 0 THEN
FOR a := 1 TO 4 DO
IF (GVL.podveskas[a].currentStep = -1) AND
(GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a].bufferEntryTime;
bestIdx := a;
END_IF
END_IF
END_FOR
END_IF
IF bestIdx > 0 THEN
GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
GVL.aoJobDest := 0;
GVL.candidate := bestIdx;
GVL.PodLoaded := FALSE;
GVL.BtnLoaded_Sn_push := FALSE;
GVL.BtnLoaded_Ag_push := FALSE;
GVL.podveskas[bestIdx].xLoaded := FALSE;
GVL.podveskas[bestIdx].xUnloaded := FALSE;
GVL.aoBusy := TRUE;
RETURN;
ELSE
GVL.PodLoaded := FALSE;
GVL.BtnLoaded_Sn_push := FALSE;
GVL.BtnLoaded_Ag_push := FALSE;
END_IF
END_IF
END_IF
END_IF
// ============================================================
// НАКОПЛЕНИЕ ПЛАНОВ ВСЕХ АКТИВНЫХ ПОДВЕСОК В ГЛОБАЛЬНЫЙ ПЛАН
// ============================================================
// 1. Сброс глобальных планов до базового состояния (только факт. занятость)
FOR i := 1 TO 17 DO
IF GVL.vanBusy[i] THEN
GVL.planVatFreeTime[i] := MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i],
GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0));
GVL.planVatBusyUntil[i] := GVL.planVatFreeTime[i];
ELSE
GVL.planVatFreeTime[i] := GVL.sysTime;
GVL.planVatBusyUntil[i] := GVL.sysTime;
END_IF
END_FOR;
// 2. Последовательное добавление планов активных подвесок
FOR a := 1 TO 4 DO
IF GVL.podveskas[a].currentStep >= 0 THEN
EvaluatePodveska(podveska := GVL.podveskas[a]);
FOR i := 1 TO 17 DO
GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simBusyUntil[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
END_FOR;
END_IF;
END_FOR;
// ---------- Основной цикл выбора победителя ----------
highestPrio := -201;
candidateLocal := 0;
aoJobSourceLocal := -1;
aoJobDestLocal := -1;
evacuateLeader := FALSE;
FOR a := 1 TO 4 DO
evacuateCandidate := FALSE;
sourcePos := GVL.podveskas[a].currentPos;
route := GVL.podveskas[a].processType;
curStep := GVL.podveskas[a].currentStep;
expectedVat := GetVatFromRecipe(route, curStep);
GetTargetVatForPodveska := 0;
IF curStep >= 0 AND curStep <= 13 THEN
CASE route OF
E_RouteType.eTin:
GetTargetVatForPodveska := TargetVars.recipeTin[curStep + 1].vatNo;
E_RouteType.eSilver:
GetTargetVatForPodveska := TargetVars.recipeSilver[curStep + 1].vatNo;
END_CASE
END_IF
targetVat := GetTargetVatForPodveska;
// ---------- Логика для завершённых подвесок ----------
IF curStep < 0 THEN
IF sourcePos = 0 THEN
IF GVL.podveskas[a].xUnloaded THEN
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
IsPodveskaReadyToUnload := TRUE;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
IsPodveskaReadyToUnload := TRUE;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
IsPodveskaReadyToUnload := TRUE;
ELSE
// Все буферы заняты – пытаемся освободить через ванну 1
IF NOT GVL.vanBusy[1] THEN
bestIdx := 0;
bestTime := DT#2100-01-01-00:00:00;
FOR a2 := 1 TO 4 DO
IF (GVL.podveskas[a2].currentStep = -1) AND
(GVL.podveskas[a2].currentPos >= 101 AND GVL.podveskas[a2].currentPos <= 103) THEN
IF (GVL.podveskas[a2].bufferEntryTime < bestTime) THEN
bestTime := GVL.podveskas[a2].bufferEntryTime;
bestIdx := a2;
END_IF
END_IF
END_FOR
IF bestIdx > 0 THEN
candidateLocal := bestIdx;
aoJobSourceLocal := GVL.podveskas[bestIdx].currentPos;
aoJobDestLocal := 1;
evacuateLeader := FALSE;
EXIT;
END_IF
END_IF
IsPodveskaReadyToUnload := FALSE;
END_IF
ELSE
IsPodveskaReadyToUnload := FALSE;
CONTINUE;
END_IF
ELSIF sourcePos >= 101 AND sourcePos <= 103 THEN
CONTINUE;
ELSIF sourcePos >= 1 AND sourcePos <= 17 THEN
IsPodveskaReadyToUnload := TRUE;
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
ELSIF NOT GVL.loadPosBusy THEN
targetVat := 0;
ELSE
targetVat := -1;
END_IF
ELSE
CONTINUE;
END_IF
ELSE
IsPodveskaReadyToUnload := FALSE;
IF sourcePos = 0 OR (sourcePos >= 101 AND sourcePos <= 103) THEN
IsPodveskaReadyToUnload := TRUE;
END_IF
IF sourcePos >= 1 AND sourcePos <= 17 THEN
IF sourcePos <> expectedVat THEN
IsPodveskaReadyToUnload := FALSE;
END_IF
IF GVL.vanEndTime[sourcePos] < GVL.sysTime THEN
IsPodveskaReadyToUnload := TRUE;
END_IF
IF (sourcePos = 4 OR sourcePos = 8 OR sourcePos = 13 OR sourcePos = 16) AND GVL.vanBusy[sourcePos - 1] THEN
IsPodveskaReadyToUnload := FALSE;
END_IF
END_IF
END_IF
IF IsPodveskaReadyToUnload THEN
IF targetVat < 0 THEN
CanLoadTargetVat := FALSE;
ELSIF targetVat = 0 THEN
CanLoadTargetVat := (NOT GVL.loadPosBusy);
ELSIF targetVat >= 1 AND targetVat <= 17 THEN
CanLoadTargetVat := NOT GVL.vanBusy[targetVat];
ELSIF targetVat >= 101 AND targetVat <= 103 THEN
CanLoadTargetVat := (Main.bufferHolds[targetVat - 100] = 0);
ELSE
CanLoadTargetVat := FALSE;
END_IF
IF (sourcePos >= 1 AND sourcePos <= 17) AND (curStep >= 0) AND (targetVat = 0) AND (NOT CanLoadTargetVat) THEN
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
CanLoadTargetVat := TRUE;
evacuateCandidate := TRUE;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
CanLoadTargetVat := TRUE;
evacuateCandidate := TRUE;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
CanLoadTargetVat := TRUE;
evacuateCandidate := TRUE;
END_IF
END_IF
IF (sourcePos = 0) AND (curStep >= 0) AND (NOT CanLoadTargetVat) THEN
IF Main.bufferHolds[1] = 0 THEN
targetVat := 101;
CanLoadTargetVat := TRUE;
ELSIF Main.bufferHolds[2] = 0 THEN
targetVat := 102;
CanLoadTargetVat := TRUE;
ELSIF Main.bufferHolds[3] = 0 THEN
targetVat := 103;
CanLoadTargetVat := TRUE;
ELSE
CanLoadTargetVat := FALSE;
END_IF
END_IF
IF CanLoadTargetVat THEN
// Сохраняем текущий коллективный план
FOR i := 1 TO 17 DO
savedFree[i] := GVL.planVatFreeTime[i];
savedBusy[i] := GVL.planVatBusyUntil[i];
END_FOR;
// Временно добавляем планы кандидата
FOR i := 1 TO 17 DO
//GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simFreeTime[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
END_FOR;
// Рассчитываем приоритет
prio := 0;
IF (route = E_RouteType.eTin AND curStep = 8 AND sourcePos = 10) OR
(route = E_RouteType.eSilver AND curStep = 8 AND sourcePos = 6) THEN
prio := prio + 1000;
END_IF
IF sourcePos >= 1 AND sourcePos <= 17 AND curStep > 0 THEN
prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.vanEndTime[sourcePos]) / 1000);
END_IF
IF (route = E_RouteType.eTin AND targetVat = 11) OR
(route = E_RouteType.eSilver AND targetVat = 5) THEN
prio := prio + 500;
END_IF
IF ((sourcePos = 0) OR (sourcePos >= 101 AND sourcePos <= 103)) AND (curStep >= 0) THEN
prio := prio + 700;
END_IF
IF sourcePos >= 101 AND sourcePos <= 103 THEN
prio := prio - 200;
prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.podveskas[a].bufferEntryTime) / 1000);
END_IF
IF (sourcePos = 3 OR sourcePos = 7 OR sourcePos = 12 OR sourcePos = 15) THEN
IF GVL.vanBusy[sourcePos + 1] THEN
prio := prio + 600;
END_IF
END_IF
IF (curStep < 0) AND (sourcePos = 0) THEN
prio := prio + 2600;
END_IF
IF (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
prio := prio + 2500;
END_IF
IF GVL.resetActive AND (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
prio := prio + 3000;
END_IF
gvl.testPrio[a] := prio;
IF prio > highestPrio THEN
highestPrio := prio;
candidateLocal := a;
aoJobSourceLocal := sourcePos;
aoJobDestLocal := targetVat;
evacuateLeader := evacuateCandidate;
END_IF;
// Восстанавливаем коллективный план
FOR i := 1 TO 17 DO
GVL.planVatFreeTime[i] := savedFree[i];
GVL.planVatBusyUntil[i] := savedBusy[i];
END_FOR;
END_IF
END_IF
END_FOR
// ----------------------------------------------------------
// Запуск движения для выбранного кандидата
// ----------------------------------------------------------
IF candidateLocal > 0 THEN
GVL.candidate := candidateLocal;
GVL.aoJobSource := aoJobSourceLocal;
GVL.aoJobDest := aoJobDestLocal;
IF evacuateLeader THEN
GVL.podveskas[GVL.candidate].currentStep := -1;
GVL.podveskas[GVL.candidate].xLoaded := FALSE;
END_IF;
// Окончательная фиксация: копируем личные планы победителя в глобальные
FOR i := 1 TO 17 DO
//GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[GVL.candidate].simFreeTime[i]);
GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[GVL.candidate].simBusyUntil[i]);
END_FOR;
GVL.aoBusy := TRUE;
END_IF;
END_IF; // конец IF NOT GVL.aoBusy
METHOD EvaluatePodveska : BOOL
VAR_IN_OUT
podveska : ST_Podveska; // Модифицируем оригинал
END_VAR
VAR
arrivalTime : DT;
prevVat : INT;
step : INT;
vat : INT;
techTime : TIME;
transitTime : TIME;
transitOut : TIME;
startTime : DT;
endTime : DT;
waitTime : TIME;
maxWait : TIME := gvl.maxWait;
nextVat : INT;
srcIdx : INT;
END_VAR
// Начальный момент времени
IF (podveska.currentStep >= 0) AND (podveska.currentPos >= 1) AND (podveska.currentPos <= 17) THEN
arrivalTime := MAX(GVL.sysTime, GVL.vanEndTime[podveska.currentPos]);
ELSE
arrivalTime := GVL.sysTime;
END_IF;
prevVat := podveska.currentPos;
step := podveska.currentStep + 1;
WHILE step <= 14 DO
vat := GetVatFromRecipe(podveska.processType, step);
IF vat = 0 THEN EXIT; END_IF;
// Транспорт к ванне (добавляется только здесь)
srcIdx := MapToTransitIndex(prevVat);
transitTime := REAL_TO_TIME(PV.transitTimed[srcIdx, vat] * 1000.0);
arrivalTime := arrivalTime + transitTime;
// Технологическое время
IF podveska.processType = E_RouteType.eTin THEN
techTime := TargetVars.recipeTin[step].duration;
ELSE
techTime := TargetVars.recipeSilver[step].duration;
END_IF;
// Время выезда из текущей ванны к следующей (вычисляем заранее)
IF step < 14 THEN
nextVat := GetVatFromRecipe(podveska.processType, step + 1);
IF nextVat <> 0 THEN
transitOut := REAL_TO_TIME(PV.transitTimed[vat, nextVat] * 1000.0);
ELSE
transitOut := T#0s;
END_IF;
ELSE
transitOut := T#0s;
END_IF;
// Стандартное время старта (ждём освобождения ванны)
startTime := MAX(arrivalTime, GVL.planVatFreeTime[vat]);
waitTime := startTime - arrivalTime;
// Проверка лимита ожидания
IF waitTime > maxWait THEN
EvaluatePodveska := FALSE;
RETURN;
END_IF;
// Проверка пересечения окон
IF (GVL.planVatBusyUntil[vat] > DT#1970-01-01-00:00:00) THEN
IF (startTime < GVL.planVatBusyUntil[vat]) AND
((startTime + techTime + transitOut) > GVL.planVatBusyUntil[vat]) THEN
EvaluatePodveska := FALSE;
RETURN;
END_IF;
END_IF;
endTime := startTime + techTime;
// Сохраняем в структуру подвески
podveska.simStartTime[vat] := startTime;
//podveska.simFreeTime[vat] := endTime + transitOut;
podveska.simBusyUntil[vat] := endTime + transitOut;
// Готовим время для следующего шага (без удвоения transit)
arrivalTime := endTime;
prevVat := vat;
step := step + 1;
END_WHILE;
EvaluatePodveska := TRUE;
METHOD OnMoveComplete
VAR
src, dst : INT;
podIdx : INT;
route : E_RouteType;
busyTime : TIME;
maxSteps : INT;
nextStep : INT;
END_VAR
src := GVL.aoJobSource;
dst := GVL.aoJobDest;
FOR podIdx := 1 TO 4 DO
IF GVL.podveskas[podIdx].currentPos = src THEN
// ---------- Освобождение исходной позиции ----------
IF src >= 1 AND src <= 17 THEN
GVL.vanBusy[src] := FALSE;
GVL.vanEndTime[src] := DT#1970-01-01-00:00:00;
GVL.vanTransitOut[src] := T#0S; // сброс времени выезда
GVL.planVatFreeTime[src] := GVL.sysTime; // сброс плана для освободившейся ванны
GVL.planVatBusyUntil[src] := GVL.sysTime; // <-- ДОБАВЛЕНО:
ELSIF src = 0 THEN
gvl.loadPosBusy := FALSE;
ELSIF src >= 101 AND src <= 103 THEN
Main.bufferHolds[src - 100] := 0;
END_IF
// ---------- Активная подвеска ----------
IF GVL.podveskas[podIdx].currentStep >= 0 THEN
GVL.podveskas[podIdx].currentPos := dst;
route := GVL.podveskas[podIdx].processType;
// Если подвеска уходит из позиции 0 в буфер – шаг не инкрементируем
IF (src = 0) AND (dst >= 101) AND (dst <= 103) THEN
// currentStep остаётся без изменений (обычно 0)
ELSE
GVL.podveskas[podIdx].currentStep := GVL.podveskas[podIdx].currentStep + 1;
END_IF
IF dst >= 1 AND dst <= 17 THEN
GVL.vanBusy[dst] := TRUE;
IF route = E_RouteType.eTin THEN
busyTime := TargetVars.recipeTin[GVL.podveskas[podIdx].currentStep].duration;
ELSE
busyTime := TargetVars.recipeSilver[GVL.podveskas[podIdx].currentStep].duration;
END_IF
GVL.vanEndTime[dst] := GVL.sysTime + busyTime;
// *** ВЫЧИСЛЕНИЕ ВРЕМЕНИ ВЫЕЗДА К СЛЕДУЮЩЕЙ ВАННЕ ***
nextStep := GVL.podveskas[podIdx].currentStep + 1;
IF nextStep <= 14 THEN
nextVat := GetVatFromRecipe(route, nextStep);
IF nextVat <> 0 THEN
GVL.vanTransitOut[dst] := REAL_TO_TIME(PV.transitTimed[dst, nextVat] * 1000.0);
ELSE
GVL.vanTransitOut[dst] := T#0s;
END_IF;
ELSE
GVL.vanTransitOut[dst] := T#0s;
END_IF;
ELSIF dst = 0 THEN
gvl.loadPosBusy := TRUE;
GVL.podveskas[podIdx].currentStep := -1; // разгрузка – завершение
ELSIF dst >= 101 AND dst <= 103 THEN
Main.bufferHolds[dst - 100] := podIdx;
// Зафиксировать время въезда в буфер
GVL.podveskas[podIdx].bufferEntryTime := GVL.sysTime;
END_IF
ELSE
// ---------- Завершённая подвеска (curStep = -1) ----------
GVL.podveskas[podIdx].currentPos := dst;
IF dst >= 1 AND dst <= 17 THEN
// Временное хранение в технологической ванне (например, ванна 1)
GVL.vanBusy[dst] := TRUE;
GVL.vanEndTime[dst] := GVL.sysTime + T#30s; // бронь на 30 секунд
GVL.vanTransitOut[dst] := T#0s; // время выезда не определено
ELSIF dst = 0 THEN
// Возврат на позицию 0 – сброс флагов загрузки/разгрузки
GVL.podveskas[podIdx].xLoaded := FALSE;
GVL.podveskas[podIdx].xUnloaded := FALSE;
gvl.loadPosBusy := TRUE;
ELSIF dst >= 101 AND dst <= 103 THEN
Main.bufferHolds[dst - 100] := podIdx;
// Зафиксировать время въезда в буфер
GVL.podveskas[podIdx].bufferEntryTime := GVL.sysTime;
END_IF
END_IF
EXIT;
END_IF
END_FOR
// Сброс задания после выполнения
GVL.aoJobSource := -1;
GVL.aoJobDest := -1;
GVL.candidate := 0;