#
vincentlu
1 天以前 6bde5b6f7f4ad8c1bac3732e46cf8d966deab845
zy-acs-manager/src/main/java/com/zy/acs/manager/core/service/GuaranteeRuntimeService.java
@@ -2,19 +2,14 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zy.acs.common.enums.AgvStatusType;
import com.zy.acs.manager.manager.entity.Agv;
import com.zy.acs.manager.manager.entity.AgvDetail;
import com.zy.acs.manager.manager.entity.Guarantee;
import com.zy.acs.manager.manager.entity.Task;
import com.zy.acs.manager.manager.enums.StatusType;
import com.zy.acs.manager.manager.enums.TaskStsType;
import com.zy.acs.manager.manager.enums.TaskTypeType;
import com.zy.acs.manager.manager.entity.*;
import com.zy.acs.manager.manager.enums.*;
import com.zy.acs.manager.manager.service.AgvDetailService;
import com.zy.acs.manager.manager.service.AgvService;
import com.zy.acs.manager.manager.service.SegmentService;
import com.zy.acs.manager.manager.service.TaskService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -22,11 +17,13 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@Slf4j
@Service
public class GuaranteeRuntimeService {
    @Autowired
    private SegmentService segmentService;
    private final AgvService agvService;
    private final AgvDetailService agvDetailService;
@@ -44,93 +41,93 @@
    }
    public void prepare(Guarantee plan, LocalDateTime targetTime) {
        CapacitySnapshot snapshot = evaluate(plan);
        if (snapshot.availableCount >= plan.getRequiredCount()) {
            log.debug("Guarantee[{}] already has {} available vehicles for {}",
                    plan.getName(), snapshot.availableCount, targetTime);
        int requiredCount = plan.getRequiredCount() == null ? 0 : plan.getRequiredCount();
        if (requiredCount <= 0) {
            log.warn("Guarantee[{}] requiredCount is not configured, skip", plan.getName());
            return;
        }
        int shortage = plan.getRequiredCount() - snapshot.availableCount;
        log.info("Guarantee[{}] shortage {} vehicles for {} (minSoc={}%), scheduling charge.",
                plan.getName(), shortage, targetTime, plan.getMinSoc());
        dispatchChargeTasks(snapshot, shortage);
    }
    public void lock(Guarantee plan, LocalDateTime targetTime) {
        log.info("Guarantee[{}] entering lock stage for {}", plan.getName(), targetTime);
        // TODO persist lock state / inform dispatcher once integration is ready
    }
    public void checkWindow(Guarantee plan, LocalDateTime targetTime) {
        CapacitySnapshot snapshot = evaluate(plan);
        if (snapshot.availableCount < plan.getRequiredCount()) {
            int shortage = plan.getRequiredCount() - snapshot.availableCount;
            log.warn("Guarantee[{}] window shortage {} vehicles for {}. Trigger quick recharge.",
                    plan.getName(), shortage, targetTime);
            dispatchChargeTasks(snapshot, shortage);
        }
    }
    public void finish(Guarantee plan, LocalDateTime targetTime) {
        log.info("Guarantee[{}] finished window for {}", plan.getName(), targetTime);
        // TODO release any lock/flag once detail implementation is defined
    }
    private CapacitySnapshot evaluate(Guarantee plan) {
        int minSoc = Optional.ofNullable(plan.getMinSoc()).orElse(50);
        List<Agv> scopedAgvs = findScopedAgvs(plan);
        int minSoc = plan.getMinSoc();
        List<Agv> scopedAgvList = findScopedAgvList(plan);
        int available = 0;
        List<ChargeCandidate> candidates = new ArrayList<>();
        for (Agv agv : scopedAgvs) {
            AgvDetail detail = agvDetailService.selectByAgvId(agv.getId());
            if (detail == null || detail.getSoc() == null) {
        for (Agv agv : scopedAgvList) {
            AgvDetail detail = agvDetailService.selectMajorByAgvId(agv.getId());
            if (null == detail || null == detail.getSoc() || null == detail.getAgvStatus()) {
                continue;
            }
            if (detail.getSoc() >= minSoc && isAvailable(agv, detail)) {
            if (detail.getAgvStatus().equals(AgvStatusType.CHARGE)) {
                continue;
            }
            if (!isIdle(agv, detail)) {
                continue;
            }
            int soc = detail.getSoc();
            if (soc >= minSoc) {
                available++;
            } else {
                candidates.add(new ChargeCandidate(agv, detail));
                candidates.add(new ChargeCandidate(agv, soc));
            }
        }
        candidates.sort(Comparator.comparingInt(o -> Optional.ofNullable(o.detail.getSoc()).orElse(0)));
        return new CapacitySnapshot(available, candidates);
        if (available >= requiredCount) {
//            log.debug("Guarantee[{}] already has {} vehicles >= {}% SOC for {}", plan.getName(), available, minSoc, targetTime);
            return;
        }
        int shortage = requiredCount - available;
        candidates.sort(Comparator.comparingInt(ChargeCandidate::getSoc));
        int scheduled = 0;
        for (ChargeCandidate candidate : candidates) {
            if (scheduled >= shortage) {
                break;
            }
            log.info("Guarantee[{}] schedule AGV {} charging (soc={}%) for target {}",
                    plan.getName(), candidate.getAgv().getName(), candidate.getSoc(), targetTime);
            mainLockWrapService.buildMinorTask(candidate.getAgv().getId(), TaskTypeType.TO_CHARGE, null, null);
            scheduled++;
        }
        if (scheduled < shortage) {
            log.warn("Guarantee[{}] still short of {} vehicles for {} (only {} idle low-soc AGVs)",
                    plan.getName(), shortage - scheduled, targetTime, candidates.size());
        }
    }
    private boolean isAvailable(Agv agv, AgvDetail detail) {
    private boolean isIdle(Agv agv, AgvDetail detail) {
        if (!Objects.equals(agv.getStatus(), StatusType.ENABLE.val)) {
            return false;
        }
        if (detail.getAgvStatus() != null && detail.getAgvStatus().equals(AgvStatusType.CHARGE)) {
            return false;
        }
        boolean busy = taskService.count(new LambdaQueryWrapper<Task>()
        if (0 < taskService.count(new LambdaQueryWrapper<Task>()
                .eq(Task::getAgvId, agv.getId())
//                .and(i -> {
//                    i.eq(Task::getTaskSts, TaskStsType.WAITING.val())
//                            .or().eq(Task::getTaskSts, TaskStsType.ASSIGN.val())
//                            .or().eq(Task::getTaskSts, TaskStsType.PROGRESS.val());
//                })
                .in(Task::getTaskSts,
                        TaskStsType.WAITING.val(),
                        TaskStsType.ASSIGN.val(),
                        TaskStsType.PROGRESS.val())) > 0;
        return !busy;
                        TaskStsType.PROGRESS.val())
        )) {
            return false;
        }
        if (0 < segmentService.count(new LambdaQueryWrapper<Segment>()
                        .eq(Segment::getAgvId, agv.getId())
                        .and( i -> {
//                                i.eq(Segment::getState, SegmentStateType.WAITING.toString()).or()
                            i.eq(Segment::getState, SegmentStateType.RUNNING.toString());
                        })
        )) {
            return false;
        }
        return true;
    }
    private void dispatchChargeTasks(CapacitySnapshot snapshot, int shortage) {
        if (shortage <= 0) {
            return;
        }
        int scheduled = 0;
        for (ChargeCandidate candidate : snapshot.candidates) {
            if (scheduled >= shortage) {
                break;
            }
            log.info("Scheduling AGV [{}] for charging to support guarantee", candidate.agv.getName());
            mainLockWrapService.buildMinorTask(candidate.agv.getId(), TaskTypeType.TO_CHARGE, null, null);
            scheduled++;
        }
    }
    private List<Agv> findScopedAgvs(Guarantee plan) {
    private List<Agv> findScopedAgvList(Guarantee plan) {
        LambdaQueryWrapper<Agv> wrapper = new LambdaQueryWrapper<Agv>()
                .eq(Agv::getStatus, StatusType.ENABLE.val);
        if ("MODEL".equalsIgnoreCase(plan.getScopeType()) && plan.getScopeValue() != null) {
        if (GuaranteeScopeType.MODEL.toString().equalsIgnoreCase(plan.getScopeType()) && plan.getScopeValue() != null) {
            try {
                wrapper.eq(Agv::getAgvModel, Long.valueOf(plan.getScopeValue()));
            } catch (NumberFormatException ignore) {
@@ -140,16 +137,21 @@
        return agvService.list(wrapper);
    }
    @Data
    private static class CapacitySnapshot {
        private final int availableCount;
        private final List<ChargeCandidate> candidates;
    }
    @Data
    @AllArgsConstructor
    private static class ChargeCandidate {
        private Agv agv;
        private AgvDetail detail;
        private final Agv agv;
        private final int soc;
        ChargeCandidate(Agv agv, int soc) {
            this.agv = agv;
            this.soc = soc;
        }
        public Agv getAgv() {
            return agv;
        }
        public int getSoc() {
            return soc;
        }
    }
}