Загрузка данных


METHOD CyclicCall : BOOL;
VAR
END_VAR
    IF NOT GVL.aoBusy THEN
        RebuildBufferAndLoadStatus();
        GVL.aoJobSource := -1;
        GVL.aoJobDest   := -1;
        GVL.candidate := 0;

        // 1. Симулируем все активные подвески
        IF NOT SimulateAllActive() THEN
            RETURN; // ошибка симуляции, перемещения запрещены
        END_IF;

        // 2. Выбираем лучшего кандидата и даём команду
        SelectAndMove();
    END_IF;

METHOD SimulateAllActive : BOOL
VAR
    a : INT;
    i : INT;
END_VAR
  // ----------------------------------------------------------------------------
    // БЛОК 1: СИНХРОНИЗАЦИЯ ПЛАНОВ С ФИЗИЧЕСКИМ СОСТОЯНИЕМ ЛИНИИ (АЛГОРИТМ Б)
    // ----------------------------------------------------------------------------
    FOR i := 1 TO 17 DO 
        IF GVL.vanBusy[i] THEN 
            GVL.planVatBusyUntil[i] := MAX(GVL.vanEndTime[i], GVL.sysTime); 
        ELSE 
            GVL.planVatBusyUntil[i] := GVL.sysTime; 
        END_IF;
    END_FOR;

    // ----------------------------------------------------------------------------
    // БЛОК 2: КАСКАДНОЕ НАКОПЛЕНИЕ ПЛАНОВ АКТИВНЫХ ПОДВЕСОК
    // ----------------------------------------------------------------------------
    FOR a := 1 TO 4 DO
        IF (GVL.podveskas[a].currentStep >= 0) AND (GVL.podveskas[a].currentPos <> 0) THEN
            // Симулируем, но если не получилось – просто пропускаем,
            // не прерывая весь цикл и не блокируя остальные подвески
            IF EvaluatePodveska(podveska := GVL.podveskas[a]) THEN
                FOR i := 1 TO 17 DO
                    GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
                    GVL.planVatStartTime[i] := MAX(GVL.planVatStartTime[i], GVL.podveskas[a].simStartTime[i]);
                END_FOR;
            END_IF;
        END_IF;
    END_FOR;

    // Симуляция всегда завершается успешно, даже если отдельные подвески не прошли проверку.
    // Решение о допуске к движению принимает SelectAndMove.
    SimulateAllActive := TRUE;

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;
    maxWait       : TIME := gvl.maxWait;
    nextVat       : INT;
    srcIdx        : INT;
END_VAR


    // Инициализация
    podveska.firstVatStartTime := DT#1970-01-01-00:00:00;

    // Начальный момент времени
    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.planVatBusyUntil[vat]);

        // Сохраняем время старта для первой ванны
        IF podveska.firstVatStartTime = DT#1970-01-01-00:00:00 THEN
            podveska.firstVatStartTime := startTime;
        END_IF;

        endTime := startTime + techTime;

        // Заполняем массивы
        podveska.simStartTime[vat] := startTime;
        podveska.simBusyUntil[vat] := endTime + transitOut;

        // Готовим время для следующего шага (без удвоения transit)
        arrivalTime := endTime;

        prevVat := vat;
        step := step + 1;
    END_WHILE;

    EvaluatePodveska := TRUE;   // Всегда TRUE – массивы заполнены

METHOD SelectAndMove : BOOL
VAR
    i : INT;
    a : INT;
    a2 : INT;
    podAtPos0 : INT;
    bestIdx : INT;
    bestTime : DT;
    sourcePos : INT;
    route : E_RouteType;
    curStep : INT;
    expectedVat : INT;
    targetVat : INT;
    firstVat : INT;
    GetTargetVatForPodveska : INT;
    IsPodveskaReadyToUnload : BOOL;
    CanLoadTargetVat : BOOL;
    prio : INT;
    highestPrio : INT;
    candidateLocal : INT;
    aoJobSourceLocal : INT;
    aoJobDestLocal : INT;
    savedBusy : ARRAY[1..17] OF DT;
    savedStart : ARRAY[1..17] OF DT;
    evacuateCandidate : BOOL;
    evacuateLeader : BOOL;
END_VAR

    highestPrio := -201;
    candidateLocal := 0;
    aoJobSourceLocal := -1;
    aoJobDestLocal := -1;
    evacuateLeader := FALSE;

    // ---------- Обработка кнопок "Загружена" / "Разгружена" ----------
    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].simBusyUntil[i] := DT#1970-01-01-00:00:00;
                    END_FOR;
                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;

                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;
                
                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
                
                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

    // ---------- Основной цикл выбора победителя ----------
    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
                        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
                IF (sourcePos = 0) OR (sourcePos >= 101 AND sourcePos <= 103) THEN
                    // Для старта из буфера/0
                    EvaluatePodveska(podveska := GVL.podveskas[a]);   // всегда TRUE
                    IF NOT CheckConstraints(podveska := GVL.podveskas[a]) THEN
                        CanLoadTargetVat := FALSE;
                        // *** НОВАЯ ЛОГИКА ЭВАКУАЦИИ ***
                        IF (sourcePos = 0) AND (GVL.podveskas[a].firstVatStartTime > DT#1970-01-02-00:00:00) THEN
                            IF (GVL.podveskas[a].firstVatStartTime > GVL.sysTime + gvl.maxWait) 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;
                                END_IF;
                            END_IF;
                        END_IF;
                    ELSE
                        // Ограничения пройдены, можно стартовать в линию
                        CanLoadTargetVat := NOT GVL.vanBusy[targetVat];
                    END_IF;
                ELSE
                    CanLoadTargetVat := NOT GVL.vanBusy[targetVat];
                END_IF;
            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 CanLoadTargetVat THEN
                FOR i := 1 TO 17 DO
                    savedBusy[i] := GVL.planVatBusyUntil[i];
                    savedStart[i] := GVL.planVatStartTime[i];
                END_FOR;

                FOR i := 1 TO 17 DO
                    GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
                    GVL.planVatStartTime[i] := MAX(GVL.planVatStartTime[i], GVL.podveskas[a].simStartTime[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.planVatBusyUntil[i] := savedBusy[i];
                    GVL.planVatStartTime[i] := savedStart[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.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[GVL.candidate].simBusyUntil[i]);
            GVL.planVatStartTime[i] := MAX(GVL.planVatStartTime[i], GVL.podveskas[GVL.candidate].simStartTime[i]);
        END_FOR;

        GVL.aoBusy := TRUE;
    END_IF;

    SelectAndMove := TRUE;