#
Junjie
2026-01-14 aa151079c2d02047f6cb5f8ad56ff92b98544e99
#
10个文件已修改
6个文件已添加
1890 ■■■■■ 已修改文件
pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/PlannerController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/planner/PlannerOrtoolsSolverService.java 701 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/PlannerService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/PlannerServiceImpl.java 548 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/PlannerExecutor.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/PlannerScheduler.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/MainProcess.java 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/RedisKeyType.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/command/CrnCommand.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/real/ZyCrnRealConnect.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java 337 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -20,6 +20,7 @@
        <mybatis-plus.version>2.3.2</mybatis-plus.version>
        <fastjson.version>1.2.83</fastjson.version>
        <springfox.version>2.7.0</springfox.version>
        <ortools.version>9.10.4067</ortools.version>
    </properties>
    <dependencies>
@@ -135,6 +136,11 @@
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.ortools</groupId>
            <artifactId>ortools-java</artifactId>
            <version>${ortools.version}</version>
        </dependency>
    </dependencies>
    <build>
src/main/java/com/zy/asrs/controller/PlannerController.java
New file
@@ -0,0 +1,25 @@
package com.zy.asrs.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.core.common.R;
import com.zy.asrs.service.PlannerService;
@RestController
public class PlannerController {
    @Autowired
    private PlannerService plannerService;
    @GetMapping("/planner/calc")
    public R plannerCalc() {
        JSONObject result = plannerService.calculateAndSaveSchedule();
        if (result == null) {
            return R.error("求解失败");
        }
        return R.ok(result);
    }
}
src/main/java/com/zy/asrs/planner/PlannerOrtoolsSolverService.java
New file
@@ -0,0 +1,701 @@
package com.zy.asrs.planner;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.ortools.Loader;
import com.google.ortools.sat.BoolVar;
import com.google.ortools.sat.CircuitConstraint;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.CumulativeConstraint;
import com.google.ortools.sat.IntervalVar;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.LinearExpr;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class PlannerOrtoolsSolverService {
    private static volatile boolean loaded = false;
    public JSONObject solve(JSONObject req) {
        ensureLoaded();
        JSONArray cranesIn = req.getJSONArray("cranes");
        JSONArray tasksIn = req.getJSONArray("tasks");
        JSONObject cfgIn = req.getJSONObject("config");
        if (cranesIn == null || cranesIn.isEmpty()) {
            throw new IllegalArgumentException("cranes is empty");
        }
        if (tasksIn == null || tasksIn.isEmpty()) {
            JSONObject out = new JSONObject();
            out.put("schedule", new JSONArray());
            out.put("objectiveValue", 0);
            return out;
        }
        List<String> craneCodes = new ArrayList<>();
        Map<String, JSONObject> craneByCode = new HashMap<>();
        for (int i = 0; i < cranesIn.size(); i++) {
            JSONObject c = cranesIn.getJSONObject(i);
            if (c == null) continue;
            String code = c.getString("code");
            if (code == null || code.trim().isEmpty()) continue;
            craneCodes.add(code);
            craneByCode.put(code, c);
        }
        if (craneCodes.isEmpty()) {
            throw new IllegalArgumentException("cranes is empty");
        }
        int outMinGapSec = optInt(cfgIn, "outMinGapSec", 0);
        int wMakespan = optInt(cfgIn, "wMakespan", 1000);
        int wPriorityEarlyFinish = optInt(cfgIn, "wPriorityEarlyFinish", 5);
        int wWaitTime = optInt(cfgIn, "wWaitTime", 1);
        int wOutTardiness = optInt(cfgIn, "wOutTardiness", 0);
        boolean enableOutDue = optBool(cfgIn, "enableOutDue", false);
        int outTaktSec = optInt(cfgIn, "outTaktSec", 20);
        double maxSolveSeconds = optDouble(cfgIn, "maxSolveSeconds", 5.0);
        int numSearchWorkers = optInt(cfgIn, "numSearchWorkers", 8);
        boolean logSearchProgress = optBool(cfgIn, "logSearchProgress", false);
        int horizonPaddingSec = optInt(cfgIn, "horizonPaddingSec", 3600);
        int conveyorStationTaskLimit = optInt(cfgIn, "conveyorStationTaskLimit", 0);
        int conveyorStationRunningTasks = optInt(cfgIn, "conveyorStationRunningTasks", 0);
        double conveyorDefaultSpeedMps = optDouble(req, "conveyorDefaultSpeedMps", 0.6);
        if (conveyorDefaultSpeedMps <= 0) conveyorDefaultSpeedMps = 0.6;
        List<Integer> taskIds = new ArrayList<>();
        Map<Integer, JSONObject> taskById = new HashMap<>();
        for (int i = 0; i < tasksIn.size(); i++) {
            JSONObject t = tasksIn.getJSONObject(i);
            if (t == null) continue;
            Integer taskId = t.getInteger("taskId");
            if (taskId == null || taskId <= 0) continue;
            taskIds.add(taskId);
            taskById.put(taskId, t);
        }
        if (taskIds.isEmpty()) {
            JSONObject out = new JSONObject();
            out.put("schedule", new JSONArray());
            out.put("objectiveValue", 0);
            return out;
        }
        Map<Integer, Integer> nodeByTaskId = new HashMap<>();
        int nodeIdx = 1;
        for (Integer taskId : taskIds) {
            nodeByTaskId.put(taskId, nodeIdx);
            nodeIdx++;
        }
        Map<Integer, Double> fromX = new HashMap<>();
        Map<Integer, Double> fromY = new HashMap<>();
        Map<Integer, Double> toX = new HashMap<>();
        Map<Integer, Double> toY = new HashMap<>();
        double minX = Double.POSITIVE_INFINITY;
        double maxX = Double.NEGATIVE_INFINITY;
        double minY = Double.POSITIVE_INFINITY;
        double maxY = Double.NEGATIVE_INFINITY;
        for (Integer taskId : taskIds) {
            JSONObject t = taskById.get(taskId);
            JSONObject fp = t == null ? null : t.getJSONObject("fromPos");
            JSONObject tp = t == null ? null : t.getJSONObject("toPos");
            double fx = optDouble(fp, "x", 0);
            double fy = optDouble(fp, "y", 0);
            double tx = optDouble(tp, "x", 0);
            double ty = optDouble(tp, "y", 0);
            fromX.put(taskId, fx);
            fromY.put(taskId, fy);
            toX.put(taskId, tx);
            toY.put(taskId, ty);
            minX = Math.min(minX, Math.min(fx, tx));
            maxX = Math.max(maxX, Math.max(fx, tx));
            minY = Math.min(minY, Math.min(fy, ty));
            maxY = Math.max(maxY, Math.max(fy, ty));
        }
        if (!Double.isFinite(minX)) {
            minX = 0;
            maxX = 0;
            minY = 0;
            maxY = 0;
        }
        Map<Integer, Integer> outConv = new HashMap<>();
        Map<String, Integer> baseDur = new HashMap<>();
        int maxReady = 0;
        int setupBoundPerTask = 0;
        for (String cc : craneCodes) {
            JSONObject c = craneByCode.get(cc);
            int ready = optInt(c, "readySec", 0);
            if (ready > maxReady) maxReady = ready;
            int bound = craneMoveTimeSecForDelta(c, Math.abs(maxX - minX), Math.abs(maxY - minY));
            setupBoundPerTask = Math.max(setupBoundPerTask, bound);
        }
        for (Integer taskId : taskIds) {
            JSONObject t = taskById.get(taskId);
            outConv.put(taskId, conveyorTimeSec(t, conveyorDefaultSpeedMps));
            for (String cc : eligibleCodes(t, craneCodes, craneByCode)) {
                baseDur.put(durKey(taskId, cc), craneBaseExecTimeSec(craneByCode.get(cc), t));
            }
        }
        int horizon = estimateHorizon(taskIds, taskById, craneCodes, baseDur, outConv, maxReady, horizonPaddingSec, craneByCode, setupBoundPerTask);
        CpModel m = new CpModel();
        Map<Integer, IntVar> startVars = new HashMap<>();
        Map<Integer, IntVar> endVars = new HashMap<>();
        Map<String, BoolVar> chosen = new HashMap<>();
        Map<String, Map<Integer, BoolVar>> presenceByCrane = new HashMap<>();
        Map<String, List<Integer>> eligibleTasksByCrane = new HashMap<>();
        for (String cc : craneCodes) {
            presenceByCrane.put(cc, new HashMap<>());
            eligibleTasksByCrane.put(cc, new ArrayList<>());
        }
        Map<Integer, IntVar> outEtaVars = new HashMap<>();
        List<IntervalVar> convIntervals = new ArrayList<>();
        for (Integer taskId : taskIds) {
            JSONObject t = taskById.get(taskId);
            IntVar s = m.newIntVar(0, horizon, "S_" + taskId);
            IntVar e = m.newIntVar(0, horizon, "E_" + taskId);
            startVars.put(taskId, s);
            endVars.put(taskId, e);
            List<String> eligible = eligibleCodes(t, craneCodes, craneByCode);
            List<BoolVar> presences = new ArrayList<>();
            for (String cc : eligible) {
                BoolVar p = m.newBoolVar("P_" + taskId + "_" + cc);
                presences.add(p);
                chosen.put(chosenKey(taskId, cc), p);
                presenceByCrane.get(cc).put(taskId, p);
                eligibleTasksByCrane.get(cc).add(taskId);
                int ready = optInt(craneByCode.get(cc), "readySec", 0);
                m.addGreaterOrEqual(s, ready).onlyEnforceIf(p);
            }
            m.addExactlyOne(presences.toArray(new BoolVar[0]));
            String taskType = optStr(t, "taskType", "");
            if ("OUT".equalsIgnoreCase(taskType)) {
                IntVar outEta = m.newIntVar(0, horizon, "OUT_" + taskId);
                outEtaVars.put(taskId, outEta);
                m.addEquality(outEta, LinearExpr.sum(new LinearExpr[]{
                        LinearExpr.term(e, 1),
                        LinearExpr.constant(outConv.get(taskId))
                }));
                int conv = outConv.get(taskId);
                IntVar sConv = m.newIntVar(0, horizon, "CS_" + taskId);
                IntVar eConv = m.newIntVar(0, horizon, "CE_" + taskId);
                m.addEquality(sConv, e);
                m.addEquality(eConv, LinearExpr.sum(new LinearExpr[]{
                        LinearExpr.term(sConv, 1),
                        LinearExpr.constant(conv)
                }));
                IntervalVar itvConv = m.newIntervalVar(sConv, LinearExpr.constant(conv), eConv, "CITV_" + taskId);
                convIntervals.add(itvConv);
            }
        }
        for (String cc : craneCodes) {
            JSONObject crane = craneByCode.get(cc);
            int ready = optInt(crane, "readySec", 0);
            Map<Integer, BoolVar> pMap = presenceByCrane.get(cc);
            List<Integer> eligibleTasks = eligibleTasksByCrane.get(cc);
            CircuitConstraint circuit = m.addCircuit();
            BoolVar loop0 = m.newBoolVar("LOOP0_" + cc);
            circuit.addArc(0, 0, loop0);
            for (Integer tid : taskIds) {
                int node = nodeByTaskId.get(tid);
                BoolVar p = pMap.get(tid);
                if (p != null) {
                    circuit.addArc(node, node, p.not());
                    m.addImplication(p, loop0.not());
                } else {
                    circuit.addArc(node, node, m.trueLiteral());
                }
            }
            for (Integer tid : eligibleTasks) {
                int nodeJ = nodeByTaskId.get(tid);
                BoolVar pJ = pMap.get(tid);
                BoolVar arc0j = m.newBoolVar("ARC_" + cc + "_0_" + nodeJ);
                circuit.addArc(0, nodeJ, arc0j);
                m.addImplication(arc0j, loop0.not());
                m.addImplication(arc0j, pJ);
                int setup0j = craneSetupFromInitSec(crane, fromX.get(tid), fromY.get(tid));
                int dJ = baseDur.get(durKey(tid, cc));
                m.addGreaterOrEqual(startVars.get(tid), ready).onlyEnforceIf(arc0j);
                m.addGreaterOrEqual(endVars.get(tid), LinearExpr.sum(new LinearExpr[]{
                        LinearExpr.term(startVars.get(tid), 1),
                        LinearExpr.constant(setup0j + dJ)
                })).onlyEnforceIf(arc0j);
                BoolVar arcj0 = m.newBoolVar("ARC_" + cc + "_" + nodeJ + "_0");
                circuit.addArc(nodeJ, 0, arcj0);
                m.addImplication(arcj0, loop0.not());
                m.addImplication(arcj0, pJ);
            }
            for (int i = 0; i < eligibleTasks.size(); i++) {
                int tidI = eligibleTasks.get(i);
                int nodeI = nodeByTaskId.get(tidI);
                BoolVar pI = pMap.get(tidI);
                for (int j = 0; j < eligibleTasks.size(); j++) {
                    if (i == j) continue;
                    int tidJ = eligibleTasks.get(j);
                    int nodeJ = nodeByTaskId.get(tidJ);
                    BoolVar pJ = pMap.get(tidJ);
                    BoolVar arc = m.newBoolVar("ARC_" + cc + "_" + nodeI + "_" + nodeJ);
                    circuit.addArc(nodeI, nodeJ, arc);
                    m.addImplication(arc, pI);
                    m.addImplication(arc, pJ);
                    int setup = craneMoveTimeSec(crane, toX.get(tidI), toY.get(tidI), fromX.get(tidJ), fromY.get(tidJ));
                    int dJ = baseDur.get(durKey(tidJ, cc));
                    m.addGreaterOrEqual(startVars.get(tidJ), endVars.get(tidI)).onlyEnforceIf(arc);
                    m.addGreaterOrEqual(endVars.get(tidJ), LinearExpr.sum(new LinearExpr[]{
                            LinearExpr.term(startVars.get(tidJ), 1),
                            LinearExpr.constant(setup + dJ)
                    })).onlyEnforceIf(arc);
                }
            }
        }
        if (conveyorStationTaskLimit > 0 && !convIntervals.isEmpty()) {
            int effCap = Math.max(0, conveyorStationTaskLimit - Math.max(0, conveyorStationRunningTasks));
            if (effCap > 0) {
                CumulativeConstraint cumul = m.addCumulative(effCap);
                for (IntervalVar itv : convIntervals) {
                    cumul.addDemand(itv, 1);
                }
            }
        }
        addOutboundOrderConstraints(m, taskIds, taskById, outEtaVars, outMinGapSec);
        IntVar makespan = m.newIntVar(0, horizon, "MAKESPAN");
        List<IntVar> ends = new ArrayList<>();
        for (Integer tid : taskIds) {
            ends.add(endVars.get(tid));
        }
        m.addMaxEquality(makespan, ends.toArray(new IntVar[0]));
        List<LinearExpr> obj = new ArrayList<>();
        obj.add(LinearExpr.term(makespan, wMakespan));
        if (wPriorityEarlyFinish > 0) {
            for (Integer tid : taskIds) {
                int pri = optInt(taskById.get(tid), "priority", 5);
                if (pri < 0) pri = 0;
                long coeff = (long) wPriorityEarlyFinish * (long) pri;
                if (coeff != 0) {
                    obj.add(LinearExpr.term(endVars.get(tid), coeff));
                }
            }
        }
        if (wWaitTime > 0) {
            long now = System.currentTimeMillis();
            for (Integer tid : taskIds) {
                long createTime = optLong(taskById.get(tid), "createTime", now);
                long waitSeconds = Math.max(0, (now - createTime) / 1000);
                long coeff = (long) wWaitTime * waitSeconds; // 等待时间越长,权重越大,越希望尽早完成
                // 这里我们希望尽早完成等待时间长的任务,所以将完成时间乘以等待时间作为惩罚项
                // 惩罚 = endVar * wWaitTime * waitSeconds
                if (coeff > 0) {
                    obj.add(LinearExpr.term(endVars.get(tid), coeff));
                }
            }
        }
        if (enableOutDue && wOutTardiness > 0) {
            IntVar tardSum = addOutboundDueTardiness(m, taskIds, taskById, outEtaVars, outTaktSec);
            obj.add(LinearExpr.term(tardSum, wOutTardiness));
        }
        m.minimize(LinearExpr.sum(obj.toArray(new LinearExpr[0])));
        CpSolver solver = new CpSolver();
        solver.getParameters().setMaxTimeInSeconds(maxSolveSeconds);
        solver.getParameters().setNumSearchWorkers(numSearchWorkers);
        solver.getParameters().setLogSearchProgress(logSearchProgress);
        CpSolverStatus status = solver.solve(m);
        if (status != CpSolverStatus.OPTIMAL && status != CpSolverStatus.FEASIBLE) {
            throw new IllegalStateException("No feasible solution");
        }
        JSONArray schedule = new JSONArray();
        for (Integer tid : taskIds) {
            JSONObject t = taskById.get(tid);
            int s = (int) solver.value(startVars.get(tid));
            int e = (int) solver.value(endVars.get(tid));
            String chosenCrane = null;
            for (String cc : eligibleCodes(t, craneCodes, craneByCode)) {
                BoolVar p = chosen.get(chosenKey(tid, cc));
                if (p != null && solver.booleanValue(p)) {
                    chosenCrane = cc;
                    break;
                }
            }
            if (chosenCrane == null) {
                throw new IllegalStateException("internal error: task " + tid + " no crane chosen");
            }
            int d = (int) (solver.value(endVars.get(tid)) - solver.value(startVars.get(tid)));
            JSONObject item = new JSONObject();
            item.put("craneCode", chosenCrane);
            item.put("taskId", tid);
            item.put("taskType", optStr(t, "taskType", ""));
            item.put("startSec", s);
            item.put("endSec", e);
            item.put("durationSec", d);
            item.put("priority", optInt(t, "priority", 5));
            if ("OUT".equalsIgnoreCase(optStr(t, "taskType", ""))) {
                IntVar outEta = outEtaVars.get(tid);
                if (outEta != null) {
                    item.put("outEtaSec", (int) solver.value(outEta));
                }
            }
            schedule.add(item);
        }
        schedule.sort(new Comparator<Object>() {
            @Override
            public int compare(Object o1, Object o2) {
                JSONObject a = (JSONObject) JSONObject.toJSON(o1);
                JSONObject b = (JSONObject) JSONObject.toJSON(o2);
                String ca = a.getString("craneCode");
                String cb = b.getString("craneCode");
                int cmp = (ca == null ? "" : ca).compareTo(cb == null ? "" : cb);
                if (cmp != 0) return cmp;
                return Integer.compare(a.getIntValue("startSec"), b.getIntValue("startSec"));
            }
        });
        JSONObject out = new JSONObject();
        out.put("schedule", schedule);
        out.put("objectiveValue", solver.objectiveValue());
        return out;
    }
    private static void ensureLoaded() {
        if (loaded) return;
        synchronized (PlannerOrtoolsSolverService.class) {
            if (loaded) return;
            Loader.loadNativeLibraries();
            loaded = true;
        }
    }
    private static String chosenKey(int taskId, String cc) {
        return taskId + "|" + cc;
    }
    private static String durKey(int taskId, String cc) {
        return taskId + "|" + cc;
    }
    private static int estimateHorizon(List<Integer> taskIds,
                                       Map<Integer, JSONObject> taskById,
                                       List<String> craneCodes,
                                       Map<String, Integer> dur,
                                       Map<Integer, Integer> outConv,
                                       int maxReady,
                                       int padding,
                                       Map<String, JSONObject> craneByCode,
                                       int setupBoundPerTask) {
        int total = 0;
        for (Integer tid : taskIds) {
            JSONObject t = taskById.get(tid);
            List<String> elig = eligibleCodes(t, craneCodes, craneByCode);
            int best = Integer.MAX_VALUE;
            for (String cc : elig) {
                Integer d = dur.get(durKey(tid, cc));
                if (d != null && d < best) best = d;
            }
            if (best != Integer.MAX_VALUE) total += best;
            total += Math.max(0, setupBoundPerTask);
            Integer conv = outConv.get(tid);
            if (conv != null) total += conv;
        }
        return total + maxReady + Math.max(0, padding);
    }
    private static List<String> eligibleCodes(JSONObject task, List<String> craneCodes, Map<String, JSONObject> craneByCode) {
        JSONArray arr = task.getJSONArray("eligibleCranes");
        if (arr == null || arr.isEmpty()) {
            return new ArrayList<>(craneCodes);
        }
        List<String> out = new ArrayList<>();
        for (int i = 0; i < arr.size(); i++) {
            String cc = arr.getString(i);
            if (cc != null && craneByCode.containsKey(cc)) {
                out.add(cc);
            }
        }
        if (out.isEmpty()) {
            throw new IllegalArgumentException("Task " + task.getInteger("taskId") + " has no eligible cranes after filtering");
        }
        return out;
    }
    private static int craneBaseExecTimeSec(JSONObject crane, JSONObject task) {
        JSONObject fromPos = task.getJSONObject("fromPos");
        JSONObject toPos = task.getJSONObject("toPos");
        double fromX = optDouble(fromPos, "x", 0);
        double fromY = optDouble(fromPos, "y", 0);
        double toX = optDouble(toPos, "x", 0);
        double toY = optDouble(toPos, "y", 0);
        int move = craneMoveTimeSec(crane, fromX, fromY, toX, toY);
        int pick = optInt(crane, "pickSec", 0);
        int drop = optInt(crane, "dropSec", 0);
        return move + pick + drop;
    }
    private static int craneSetupFromInitSec(JSONObject crane, double toX, double toY) {
        JSONObject initPos = crane.getJSONObject("initPos");
        if (initPos == null) {
            return 0;
        }
        double initX = optDouble(initPos, "x", Double.NaN);
        double initY = optDouble(initPos, "y", Double.NaN);
        if (Double.isNaN(initX) || Double.isNaN(initY)) {
            return 0;
        }
        return craneMoveTimeSec(crane, initX, initY, toX, toY);
    }
    private static int craneMoveTimeSecForDelta(JSONObject crane, double dx, double dy) {
        double vx = optDouble(crane, "vx", 1.0);
        double vy = optDouble(crane, "vy", 1.0);
        String moveMode = optStr(crane, "moveMode", "max");
        double tx = vx <= 0 ? 0 : Math.abs(dx) / vx;
        double ty = vy <= 0 ? 0 : Math.abs(dy) / vy;
        double t = "sum".equalsIgnoreCase(moveMode) ? (tx + ty) : Math.max(tx, ty);
        return (int) Math.round(t);
    }
    private static int craneMoveTimeSec(JSONObject crane, double fromX, double fromY, double toX, double toY) {
        return craneMoveTimeSecForDelta(crane, toX - fromX, toY - fromY);
    }
    private static int conveyorTimeSec(JSONObject task, double defaultSpeedMps) {
        String taskType = optStr(task, "taskType", "");
        if (!"OUT".equalsIgnoreCase(taskType)) {
            return 0;
        }
        JSONArray segs = task.getJSONArray("conveyorPath");
        if (segs != null && !segs.isEmpty()) {
            double total = 0.0;
            for (int i = 0; i < segs.size(); i++) {
                JSONObject seg = segs.getJSONObject(i);
                if (seg == null) continue;
                double len = optDouble(seg, "length_m", 0);
                double sp = optDouble(seg, "speed_mps", 0);
                if (len > 0 && sp > 0) total += len / sp;
            }
            return (int) Math.round(total);
        }
        JSONArray nav = task.getJSONArray("navigatePath");
        JSONArray edges = task.getJSONArray("pathEdges");
        if (nav != null && nav.size() >= 2 && edges != null && !edges.isEmpty()) {
            Map<String, Edge> edgeMap = new HashMap<>();
            for (int i = 0; i < edges.size(); i++) {
                JSONObject e = edges.getJSONObject(i);
                if (e == null) continue;
                int u = e.getIntValue("fromId");
                int v = e.getIntValue("toId");
                double len = optDouble(e, "length_m", 0);
                Double sp = e.containsKey("speed_mps") ? e.getDouble("speed_mps") : null;
                edgeMap.put(u + "->" + v, new Edge(len, sp));
            }
            double total = 0.0;
            for (int i = 0; i < nav.size() - 1; i++) {
                int u = nav.getIntValue(i);
                int v = nav.getIntValue(i + 1);
                Edge ed = edgeMap.get(u + "->" + v);
                if (ed == null) ed = edgeMap.get(v + "->" + u);
                if (ed == null) {
                    continue;
                }
                double sp = ed.speedMps != null && ed.speedMps > 0 ? ed.speedMps : defaultSpeedMps;
                if (ed.lengthM > 0 && sp > 0) total += ed.lengthM / sp;
            }
            return (int) Math.round(total);
        }
        return 0;
    }
    private static void addOutboundOrderConstraints(CpModel m,
                                                   List<Integer> taskIds,
                                                   Map<Integer, JSONObject> taskById,
                                                   Map<Integer, IntVar> outEtaVars,
                                                   int minGap) {
        Map<String, List<GroupItem>> groups = new HashMap<>();
        for (Integer tid : taskIds) {
            JSONObject t = taskById.get(tid);
            if (!"OUT".equalsIgnoreCase(optStr(t, "taskType", ""))) continue;
            String g = t.getString("outGroup");
            Integer seq = t.getInteger("outSeq");
            if (g == null || seq == null) continue;
            groups.computeIfAbsent(g, k -> new ArrayList<>()).add(new GroupItem(seq, tid));
        }
        for (Map.Entry<String, List<GroupItem>> e : groups.entrySet()) {
            List<GroupItem> items = e.getValue();
            Collections.sort(items, new Comparator<GroupItem>() {
                @Override
                public int compare(GroupItem a, GroupItem b) {
                    return Integer.compare(a.seq, b.seq);
                }
            });
            for (int i = 0; i < items.size() - 1; i++) {
                int aId = items.get(i).taskId;
                int bId = items.get(i + 1).taskId;
                IntVar aEta = outEtaVars.get(aId);
                IntVar bEta = outEtaVars.get(bId);
                if (aEta != null && bEta != null) {
                    m.addGreaterOrEqual(bEta, LinearExpr.sum(new LinearExpr[]{
                            LinearExpr.term(aEta, 1),
                            LinearExpr.constant(minGap)
                    }));
                }
            }
        }
    }
    private static IntVar addOutboundDueTardiness(CpModel m,
                                                 List<Integer> taskIds,
                                                 Map<Integer, JSONObject> taskById,
                                                 Map<Integer, IntVar> outEtaVars,
                                                 int taktSec) {
        Map<String, List<GroupItem>> groups = new HashMap<>();
        for (Integer tid : taskIds) {
            JSONObject t = taskById.get(tid);
            if (!"OUT".equalsIgnoreCase(optStr(t, "taskType", ""))) continue;
            String g = t.getString("outGroup");
            Integer seq = t.getInteger("outSeq");
            if (g == null || seq == null) continue;
            if (!outEtaVars.containsKey(tid)) continue;
            groups.computeIfAbsent(g, k -> new ArrayList<>()).add(new GroupItem(seq, tid));
        }
        List<IntVar> tardVars = new ArrayList<>();
        for (Map.Entry<String, List<GroupItem>> e : groups.entrySet()) {
            List<GroupItem> items = e.getValue();
            Collections.sort(items, new Comparator<GroupItem>() {
                @Override
                public int compare(GroupItem a, GroupItem b) {
                    return Integer.compare(a.seq, b.seq);
                }
            });
            for (int idx = 0; idx < items.size(); idx++) {
                int tid = items.get(idx).taskId;
                IntVar outEta = outEtaVars.get(tid);
                int due = idx * taktSec;
                IntVar tard = m.newIntVar(0, 1_000_000_000, "TARD_" + e.getKey() + "_" + tid);
                m.addGreaterOrEqual(tard, LinearExpr.sum(new LinearExpr[]{
                        LinearExpr.term(outEta, 1),
                        LinearExpr.constant(-due)
                }));
                m.addGreaterOrEqual(tard, 0);
                tardVars.add(tard);
            }
        }
        if (tardVars.isEmpty()) {
            IntVar z = m.newIntVar(0, 0, "TARD_SUM_ZERO");
            m.addEquality(z, 0);
            return z;
        }
        IntVar s = m.newIntVar(0, 1_000_000_000, "TARD_SUM");
        m.addEquality(s, LinearExpr.sum(tardVars.toArray(new IntVar[0])));
        return s;
    }
    private static int optInt(JSONObject o, String k, int def) {
        if (o == null) return def;
        try {
            Integer v = o.getInteger(k);
            return v == null ? def : v;
        } catch (Exception e) {
            return def;
        }
    }
    private static double optDouble(JSONObject o, String k, double def) {
        if (o == null) return def;
        try {
            Double v = o.getDouble(k);
            return v == null ? def : v;
        } catch (Exception e) {
            return def;
        }
    }
    private static String optStr(JSONObject o, String k, String def) {
        if (o == null) return def;
        try {
            String v = o.getString(k);
            return v == null ? def : v;
        } catch (Exception e) {
            return def;
        }
    }
    private static boolean optBool(JSONObject o, String k, boolean def) {
        if (o == null) return def;
        try {
            Object v = o.get(k);
            if (v == null) return def;
            if (v instanceof Boolean) return (Boolean) v;
            String s = String.valueOf(v).trim().toUpperCase();
            if ("Y".equals(s) || "TRUE".equals(s) || "1".equals(s)) return true;
            if ("N".equals(s) || "FALSE".equals(s) || "0".equals(s)) return false;
            return def;
        } catch (Exception e) {
            return def;
        }
    }
    private static double optDouble(JSONObject o, String k, Double def) {
        return optDouble(o, k, def == null ? 0.0 : def);
    }
    private static long optLong(JSONObject o, String k, long def) {
        if (o == null) return def;
        try {
            Long v = o.getLong(k);
            return v == null ? def : v;
        } catch (Exception e) {
            return def;
        }
    }
    private static final class Edge {
        final double lengthM;
        final Double speedMps;
        Edge(double lengthM, Double speedMps) {
            this.lengthM = lengthM;
            this.speedMps = speedMps;
        }
    }
    private static final class GroupItem {
        final int seq;
        final int taskId;
        GroupItem(int seq, int taskId) {
            this.seq = seq;
            this.taskId = taskId;
        }
    }
}
src/main/java/com/zy/asrs/service/PlannerService.java
New file
@@ -0,0 +1,7 @@
package com.zy.asrs.service;
import com.alibaba.fastjson.JSONObject;
public interface PlannerService {
    JSONObject calculateAndSaveSchedule();
}
src/main/java/com/zy/asrs/service/impl/PlannerServiceImpl.java
New file
@@ -0,0 +1,548 @@
package com.zy.asrs.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.planner.PlannerOrtoolsSolverService;
import com.zy.asrs.service.*;
import com.zy.common.utils.HttpHandler;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.SlaveType;
import com.zy.core.enums.WrkStsType;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.StationThread;
import com.zy.core.utils.StationOperateProcessUtils;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class PlannerServiceImpl implements PlannerService {
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private LocMastService locMastService;
    @Autowired
    private ConfigService configService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private PlannerOrtoolsSolverService plannerOrtoolsSolverService;
    @Autowired
    private StationOperateProcessUtils stationOperateProcessUtils;
    @Override
    public JSONObject calculateAndSaveSchedule() {
        ArrayList<HashMap<String, Object>> crnDataList = new ArrayList<>();
        List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<BasCrnp>().eq("status", 1));
        Map<Integer, StationObjModel> stationIndex = new HashMap<>();
        Map<Integer, String> stationCrnCodeIndex = new HashMap<>();
        Double bayWidth = getDoubleConfig("plannerBayWidthM", 1.0);
        Double levHeight = getDoubleConfig("plannerLevHeightM", 1.0);
        // First pass: Build station index
        for (BasCrnp basCrnp : basCrnps) {
            List<StationObjModel> inStations = basCrnp.getInStationList$();
            if (inStations != null) {
                for (StationObjModel stationObjModel : inStations) {
                    Integer sid = stationObjModel.getStationId();
                    if (sid != null) {
                        stationIndex.put(sid, stationObjModel);
                        stationCrnCodeIndex.put(sid, "CRN-" + basCrnp.getCrnNo());
                    }
                }
            }
            List<StationObjModel> outStations = basCrnp.getOutStationList$();
            if (outStations != null) {
                for (StationObjModel stationObjModel : outStations) {
                    Integer sid = stationObjModel.getStationId();
                    if (sid != null) {
                        stationIndex.put(sid, stationObjModel);
                    }
                }
            }
        }
        // Second pass: Build crane data
        for (BasCrnp basCrnp : basCrnps) {
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
            if (crnThread == null) {
                continue;
            }
            HashMap<String, Object> crnData = new HashMap<>();
            crnData.put("code", "CRN-" + basCrnp.getCrnNo());
            crnData.put("name", basCrnp.getCrnNo());
            // 规划时使用额定速度,而非当前实时速度
            Double vx = getDoubleConfig("plannerVxDefault", 2.6);
            Double vy = getDoubleConfig("plannerVyDefault", 0.67);
            crnData.put("vx", vx);
            crnData.put("vy", vy);
            try {
                if (crnThread.getStatus() != null) {
                    Integer bay = crnThread.getStatus().getBay();
                    Integer lev = crnThread.getStatus().getLevel();
                    if (bay != null && lev != null) {
                        HashMap<String, Object> initPos = new HashMap<>();
                        initPos.put("x", bay * bayWidth);
                        initPos.put("y", lev * levHeight);
                        crnData.put("initPos", initPos);
                    }
                }
            } catch (Exception ignore) {}
            Integer pickSec = getIntConfig("plannerPickSec", 8);
            Integer dropSec = getIntConfig("plannerDropSec", 6);
            crnData.put("pickSec", pickSec);
            crnData.put("dropSec", dropSec);
            crnData.put("moveMode", "max");
            int readySec = 0;
            try {
                if (crnThread.getStatus() != null) {
                    CrnProtocol p = crnThread.getStatus();
                    Integer tNo = p.getTaskNo();
                    if (tNo != null && tNo > 0) {
                        WrkMast task = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", tNo));
                        if (task != null) {
                            // Calculate current X, Y
                            double curX = (p.getBay() == null ? 0 : p.getBay()) * bayWidth;
                            double curY = (p.getLevel() == null ? 0 : p.getLevel()) * levHeight;
                            double targetX = curX;
                            double targetY = curY;
                            boolean hasGoods = (p.getLoaded() != null && p.getLoaded() == 1);
                            if (hasGoods) {
                                // Moving to destination
                                if (task.getWrkSts() != null && task.getWrkSts() < 50) { // IN task
                                    // For IN task with goods, destination is Location
                                    if (task.getLocNo() != null) {
                                        LocMast loc = locMastService.queryByLoc(task.getLocNo());
                                        if (loc != null) {
                                            targetX = (loc.getBay1() == null ? 0 : loc.getBay1()) * bayWidth;
                                            targetY = (loc.getLev1() == null ? 0 : loc.getLev1()) * levHeight;
                                        }
                                    }
                                } else { // OUT or LOC_MOVE
                                    if (task.getStaNo() != null) { // OUT task
                                        // For OUT task with goods, destination is Station
                                        StationObjModel s = stationIndex.get(task.getStaNo());
                                        if (s != null) {
                                            targetX = (s.getDeviceBay() == null ? 0 : s.getDeviceBay()) * bayWidth;
                                            targetY = (s.getDeviceLev() == null ? 0 : s.getDeviceLev()) * levHeight;
                                        }
                                    } else if (task.getLocNo() != null) { // LOC_MOVE
                                        LocMast loc = locMastService.queryByLoc(task.getLocNo());
                                        if (loc != null) {
                                            targetX = (loc.getBay1() == null ? 0 : loc.getBay1()) * bayWidth;
                                            targetY = (loc.getLev1() == null ? 0 : loc.getLev1()) * levHeight;
                                        }
                                    }
                                }
                            } else {
                                // Moving to source
                                if (task.getWrkSts() != null && task.getWrkSts() < 50) { // IN task
                                     StationObjModel s = stationIndex.get(task.getStaNo());
                                     if (s != null) {
                                        targetX = (s.getDeviceBay() == null ? 0 : s.getDeviceBay()) * bayWidth;
                                        targetY = (s.getDeviceLev() == null ? 0 : s.getDeviceLev()) * levHeight;
                                     }
                                } else { // OUT or LOC_MOVE
                                    if (task.getSourceLocNo() != null) {
                                        LocMast loc = locMastService.queryByLoc(task.getSourceLocNo());
                                        if (loc != null) {
                                            targetX = (loc.getBay1() == null ? 0 : loc.getBay1()) * bayWidth;
                                            targetY = (loc.getLev1() == null ? 0 : loc.getLev1()) * levHeight;
                                        }
                                    }
                                }
                            }
                            double dx = Math.abs(targetX - curX);
                            double dy = Math.abs(targetY - curY);
                            double tx = vx <= 0 ? 0 : dx / vx;
                            double ty = vy <= 0 ? 0 : dy / vy;
                            double moveTime = Math.max(tx, ty);
                            double total = moveTime;
                            if (hasGoods) {
                                total += dropSec;
                            } else {
                                total += pickSec;
                                // Plus time to move to destination + drop
                                double srcX = targetX;
                                double srcY = targetY;
                                double dstX = srcX;
                                double dstY = srcY;
                                if (task.getWrkSts() != null && task.getWrkSts() < 50) { // Inbound
                                    if (task.getLocNo() != null) {
                                        LocMast loc = locMastService.queryByLoc(task.getLocNo());
                                        if (loc != null) {
                                            dstX = (loc.getBay1() == null ? 0 : loc.getBay1()) * bayWidth;
                                            dstY = (loc.getLev1() == null ? 0 : loc.getLev1()) * levHeight;
                                        }
                                    }
                                } else { // Outbound or LocMove
                                    if (task.getStaNo() != null) {
                                        StationObjModel s = stationIndex.get(task.getStaNo());
                                        if (s != null) {
                                            dstX = (s.getDeviceBay() == null ? 0 : s.getDeviceBay()) * bayWidth;
                                            dstY = (s.getDeviceLev() == null ? 0 : s.getDeviceLev()) * levHeight;
                                        }
                                    } else if (task.getLocNo() != null) {
                                        LocMast loc = locMastService.queryByLoc(task.getLocNo());
                                        if (loc != null) {
                                            dstX = (loc.getBay1() == null ? 0 : loc.getBay1()) * bayWidth;
                                            dstY = (loc.getLev1() == null ? 0 : loc.getLev1()) * levHeight;
                                        }
                                    }
                                }
                                double d2x = Math.abs(dstX - srcX);
                                double d2y = Math.abs(dstY - srcY);
                                double t2x = vx <= 0 ? 0 : d2x / vx;
                                double t2y = vy <= 0 ? 0 : d2y / vy;
                                total += Math.max(t2x, t2y);
                                total += dropSec;
                            }
                            readySec = (int) Math.ceil(total);
                        }
                    }
                }
            } catch (Exception ignore) {}
            crnData.put("readySec", readySec);
            crnDataList.add(crnData);
        }
        ArrayList<HashMap<String, Object>> taskDataList = new ArrayList<>();
        Map<String, Integer> outGroupSeqCounter = new HashMap<>();
        List<WrkMast> outTasks = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("wrk_sts", WrkStsType.NEW_OUTBOUND.sts));
        for (WrkMast wrkMast : outTasks) {
            HashMap<String, Object> t = new HashMap<>();
            t.put("taskId", wrkMast.getWrkNo());
            t.put("taskType", "OUT");
            t.put("priority", wrkMast.getIoPri() == null ? 5 : wrkMast.getIoPri().intValue());
            if (wrkMast.getAppeTime() != null) {
                t.put("createTime", wrkMast.getAppeTime().getTime());
            }
            String src = wrkMast.getSourceLocNo();
            LocMast srcLoc = src == null ? null : locMastService.queryByLoc(src);
            int srcX = srcLoc == null ? 0 : (srcLoc.getBay1() == null ? 0 : srcLoc.getBay1());
            int srcY = srcLoc == null ? 0 : (srcLoc.getLev1() == null ? 0 : srcLoc.getLev1());
            HashMap<String, Object> fromPos = new HashMap<>();
            fromPos.put("x", srcX * bayWidth);
            fromPos.put("y", srcY * levHeight);
            t.put("fromPos", fromPos);
            Integer targetStationId = wrkMast.getStaNo();
            double toX = 0;
            double toY = 0;
            try {
                if (targetStationId != null) {
                    StationObjModel s = stationIndex.get(targetStationId);
                    if (s != null) {
                        toX = s.getDeviceBay() == null ? 0 : s.getDeviceBay();
                        toY = s.getDeviceLev() == null ? 0 : s.getDeviceLev();
                    }
                }
            } catch (Exception ignore) {}
            HashMap<String, Object> toPos = new HashMap<>();
            toPos.put("x", toX * bayWidth);
            toPos.put("y", toY * levHeight);
            t.put("toPos", toPos);
            ArrayList<String> eligible = new ArrayList<>();
            Integer crnNo = wrkMast.getCrnNo();
            if (crnNo != null && crnNo > 0) {
                eligible.add("CRN-" + crnNo);
            } else {
                try {
                    String locNo = src;
                    if (locNo != null) {
                        Integer row = com.zy.asrs.utils.Utils.getRow(locNo);
                        for (BasCrnp basCrnp : basCrnps) {
                            List<List<Integer>> controlRows = basCrnp.getControlRows$();
                            for (List<Integer> rows : controlRows) {
                                if (rows.contains(row)) {
                                    eligible.add("CRN-" + basCrnp.getCrnNo());
                                    break;
                                }
                            }
                        }
                    }
                } catch (Exception ignore) {}
            }
            t.put("eligibleCranes", eligible);
            t.put("conveyorPath", new ArrayList<>());
            String group = wrkMast.getWmsWrkNo();
            if (group == null || group.trim().isEmpty()) {
                group = "WCS";
            }
            int seq = outGroupSeqCounter.getOrDefault(group, 0) + 1;
            outGroupSeqCounter.put(group, seq);
            t.put("outGroup", group);
            t.put("outSeq", seq);
            taskDataList.add(t);
        }
        List<WrkMast> moveTasks = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("wrk_sts", WrkStsType.NEW_LOC_MOVE.sts));
        for (WrkMast wrkMast : moveTasks) {
            HashMap<String, Object> t = new HashMap<>();
            t.put("taskId", wrkMast.getWrkNo());
            t.put("taskType", "MOVE");
            t.put("priority", wrkMast.getIoPri() == null ? 5 : wrkMast.getIoPri().intValue());
            if (wrkMast.getAppeTime() != null) {
                t.put("createTime", wrkMast.getAppeTime().getTime());
            }
            String src = wrkMast.getSourceLocNo();
            LocMast srcLoc = src == null ? null : locMastService.queryByLoc(src);
            int srcX = srcLoc == null ? 0 : (srcLoc.getBay1() == null ? 0 : srcLoc.getBay1());
            int srcY = srcLoc == null ? 0 : (srcLoc.getLev1() == null ? 0 : srcLoc.getLev1());
            HashMap<String, Object> fromPos = new HashMap<>();
            fromPos.put("x", srcX * bayWidth);
            fromPos.put("y", srcY * levHeight);
            t.put("fromPos", fromPos);
            String dst = wrkMast.getLocNo();
            LocMast dstLoc = dst == null ? null : locMastService.queryByLoc(dst);
            int toX = dstLoc == null ? 0 : (dstLoc.getBay1() == null ? 0 : dstLoc.getBay1());
            int toY = dstLoc == null ? 0 : (dstLoc.getLev1() == null ? 0 : dstLoc.getLev1());
            HashMap<String, Object> toPos = new HashMap<>();
            toPos.put("x", toX * bayWidth);
            toPos.put("y", toY * levHeight);
            t.put("toPos", toPos);
            ArrayList<String> eligible = new ArrayList<>();
            Integer crnNo = wrkMast.getCrnNo();
            if (crnNo != null && crnNo > 0) {
                eligible.add("CRN-" + crnNo);
            } else {
                try {
                    String locNo = src;
                    if (locNo != null) {
                        Integer row = com.zy.asrs.utils.Utils.getRow(locNo);
                        for (BasCrnp basCrnp : basCrnps) {
                            List<List<Integer>> controlRows = basCrnp.getControlRows$();
                            for (List<Integer> rows : controlRows) {
                                if (rows.contains(row)) {
                                    eligible.add("CRN-" + basCrnp.getCrnNo());
                                    break;
                                }
                            }
                        }
                    }
                } catch (Exception ignore) {}
            }
            t.put("eligibleCranes", eligible);
            t.put("conveyorPath", new ArrayList<>());
            taskDataList.add(t);
        }
        List<WrkMast> inTasks = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("wrk_sts", WrkStsType.INBOUND_DEVICE_RUN.sts));
        for (WrkMast wrkMast : inTasks) {
            HashMap<String, Object> t = new HashMap<>();
            t.put("taskId", wrkMast.getWrkNo());
            t.put("taskType", "IN");
            t.put("priority", wrkMast.getIoPri() == null ? 5 : wrkMast.getIoPri().intValue());
            if (wrkMast.getAppeTime() != null) {
                t.put("createTime", wrkMast.getAppeTime().getTime());
            }
            Integer targetStationId = wrkMast.getStaNo();
            HashMap<String, Object> fromPos = new HashMap<>();
            String matchedCrnCode = null;
            boolean stationReady = false;
            if (targetStationId != null) {
                StationObjModel stationObjModel = stationIndex.get(targetStationId);
                if (stationObjModel != null) {
                    StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
                    if (stationThread != null) {
                        Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
                        StationProtocol sp = statusMap == null ? null : statusMap.get(targetStationId);
                        if (sp != null && sp.isAutoing() && sp.isLoading() && sp.getTaskNo().equals(wrkMast.getWrkNo())) {
                            stationReady = true;
                            matchedCrnCode = stationCrnCodeIndex.get(targetStationId);
                            fromPos.put("x", stationObjModel.getDeviceBay() * bayWidth);
                            fromPos.put("y", stationObjModel.getDeviceLev() * levHeight);
                        }
                    }
                }
            }
            if (!stationReady) {
                continue;
            }
            t.put("fromPos", fromPos);
            HashMap<String, Object> toPos = new HashMap<>();
            String dst = wrkMast.getLocNo();
            LocMast dstLoc = dst == null ? null : locMastService.queryByLoc(dst);
            int toX = dstLoc == null ? 0 : (dstLoc.getBay1() == null ? 0 : dstLoc.getBay1());
            int toY = dstLoc == null ? 0 : (dstLoc.getLev1() == null ? 0 : dstLoc.getLev1());
            toPos.put("x", toX * bayWidth);
            toPos.put("y", toY * levHeight);
            t.put("toPos", toPos);
            ArrayList<String> eligible = new ArrayList<>();
            if (matchedCrnCode != null) {
                eligible.add(matchedCrnCode);
            }
            t.put("eligibleCranes", eligible);
            t.put("conveyorPath", new ArrayList<>());
            taskDataList.add(t);
        }
        //获取输送线任务数量
        int currentStationTaskCount = stationOperateProcessUtils.getCurrentStationTaskCount();
        HashMap<String, Object> config = new HashMap<>();
        config.put("outMinGapSec", getIntConfig("plannerOutMinGapSec", 0));
        config.put("wMakespan", getIntConfig("plannerWeightMakespan", 1000));
        config.put("wPriorityEarlyFinish", getIntConfig("plannerWeightPriorityEarlyFinish", 5));
        config.put("wWaitTime", getIntConfig("plannerWeightWaitTime", 1));
        config.put("enableOutDue", getBoolConfig("plannerEnableOutDue", false));
        config.put("maxSolveSeconds", getIntConfig("plannerMaxSolveSeconds", 3));
        config.put("numSearchWorkers", getIntConfig("plannerNumSearchWorkers", 8));
        config.put("conveyorStationTaskLimit", getIntConfig("conveyorStationTaskLimit", 30));
        config.put("conveyorStationRunningTasks", currentStationTaskCount);
        HashMap<String, Object> request = new HashMap<>();
        request.put("cranes", crnDataList);
        request.put("tasks", taskDataList);
        request.put("config", config);
        JSONObject result;
        boolean useInternal = getBoolConfig("plannerUseInternalSolver", true);
        if (useInternal) {
            try {
                result = plannerOrtoolsSolverService.solve(JSONObject.parseObject(JSON.toJSONString(request)));
            } catch (Throwable e) {
                News.error("内部求解器执行失败:{}", e.getMessage());
                result = null;
            }
        } else {
            Config uriCfg = configService.selectOne(new EntityWrapper<Config>().eq("code", "plannerSolverUri"));
            Config pathCfg = configService.selectOne(new EntityWrapper<Config>().eq("code", "plannerSolverPath"));
            if (uriCfg == null || pathCfg == null || uriCfg.getValue() == null || pathCfg.getValue() == null) {
                // 如果是服务调用,这里无法直接返回R.error,返回null或者empty json
                return null;
            }
            String solverUri = uriCfg.getValue();
            String solverPath = pathCfg.getValue();
            String response;
            try {
                HttpHandler http = new HttpHandler.Builder()
                        .setUri(solverUri)
                        .setPath(solverPath)
                        .setTimeout(30, TimeUnit.SECONDS)
                        .setJson(JSON.toJSONString(request))
                        .build();
                response = http.doPost();
            } catch (Exception e) {
                News.error("求解器调用失败:{}", e.getMessage());
                return null;
            }
            try {
                result = JSON.parseObject(response);
            } catch (Exception ignore) {
                return null;
            }
        }
        try {
            try {
                int nowSec = (int) (System.currentTimeMillis() / 1000);
                if (result != null && result.containsKey("schedule")) {
                    List<Object> schedule = result.getJSONArray("schedule");
                    if (schedule != null) {
                        Map<String, List<String>> groupByCrane = new HashMap<>();
                        for (Object obj : schedule) {
                            JSONObject item = (JSONObject) JSONObject.toJSON(obj);
                            String craneCode = item.getString("craneCode");
                            Integer taskId = item.getInteger("taskId");
                            String taskType = item.getString("taskType");
                            Integer startSec = item.getInteger("startSec");
                            Integer endSec = item.getInteger("endSec");
                            Integer outEtaSec = item.getInteger("outEtaSec");
                            if (craneCode == null || taskId == null || startSec == null) {
                                continue;
                            }
                            JSONObject store = new JSONObject();
                            store.put("taskId", taskId);
                            store.put("taskType", taskType);
                            store.put("startEpochSec", nowSec + startSec);
                            if (endSec != null) {
                                store.put("endEpochSec", nowSec + endSec);
                            }
                            if (outEtaSec != null) {
                                store.put("outEtaEpochSec", nowSec + outEtaSec);
                            }
                            groupByCrane.computeIfAbsent(craneCode, k -> new ArrayList<>()).add(store.toJSONString());
                        }
                        for (Map.Entry<String, List<String>> e : groupByCrane.entrySet()) {
                            String key = RedisKeyType.PLANNER_SCHEDULE.key + e.getKey();
                            redisUtil.del(key);
                            for (String s : e.getValue()) {
                                redisUtil.lSet(key, s);
                            }
                        }
                    }
                }
            } catch (Exception ignore) {}
            return result;
        } catch (Exception ignore) {
            return null;
        }
    }
    private Double getDoubleConfig(String code, Double def) {
        try {
            Config c = configService.selectOne(new EntityWrapper<Config>().eq("code", code));
            if (c != null && c.getValue() != null && c.getValue().trim().length() > 0) {
                return Double.parseDouble(c.getValue().trim());
            }
        } catch (Exception ignore) {}
        return def;
    }
    private Integer getIntConfig(String code, Integer def) {
        try {
            Config c = configService.selectOne(new EntityWrapper<Config>().eq("code", code));
            if (c != null && c.getValue() != null && c.getValue().trim().length() > 0) {
                String v = c.getValue().trim();
                if (v.endsWith("%")) v = v.substring(0, v.length() - 1);
                return Integer.parseInt(v);
            }
        } catch (Exception ignore) {}
        return def;
    }
    private Boolean getBoolConfig(String code, Boolean def) {
        try {
            Config c = configService.selectOne(new EntityWrapper<Config>().eq("code", code));
            if (c != null && c.getValue() != null) {
                String v = c.getValue().trim().toUpperCase();
                if ("Y".equals(v) || "TRUE".equals(v)) return true;
                if ("N".equals(v) || "FALSE".equals(v)) return false;
            }
        } catch (Exception ignore) {}
        return def;
    }
}
src/main/java/com/zy/asrs/task/PlannerExecutor.java
New file
@@ -0,0 +1,20 @@
package com.zy.asrs.task;
import com.zy.core.utils.CrnOperateProcessUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class PlannerExecutor {
    @Autowired
    private CrnOperateProcessUtils crnOperateProcessUtils;
    @Scheduled(cron = "0/1 * * * * ? ")
    @Transactional
    public void executePlanner() {
        crnOperateProcessUtils.plannerExecute();
    }
}
src/main/java/com/zy/asrs/task/PlannerScheduler.java
New file
@@ -0,0 +1,25 @@
package com.zy.asrs.task;
import com.alibaba.fastjson.JSONObject;
import com.zy.asrs.service.PlannerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class PlannerScheduler {
    @Autowired
    private PlannerService plannerService;
    // 每3秒触发一次求解
    @Scheduled(fixedDelay = 3000)
    public void schedulePlanner() {
        try {
            JSONObject result = plannerService.calculateAndSaveSchedule();
            // 日志记录可以根据需要添加,避免过于频繁
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
src/main/java/com/zy/core/MainProcess.java
@@ -1,7 +1,6 @@
package com.zy.core;
import com.core.common.SpringUtils;
import com.core.exception.CoolException;
import com.zy.core.plugin.api.MainProcessPluginApi;
import com.zy.core.properties.SystemProperties;
import lombok.extern.slf4j.Slf4j;
@@ -20,38 +19,62 @@
    private String mainProcessPlugin;
    private MainProcessPluginApi mainProcessPluginApi;
    // 所属线程
    private Thread thread;
    private volatile Thread thread;
    /**
     * =====>>  开始工作
     */
    public void start(){
        if (thread != null && thread.isAlive()) {
            log.warn("MainProcess is already running.");
            return;
        }
        thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    // 1. 加载插件
                    if (mainProcessPluginApi == null) {
                        try {
                        String className = mainProcessPlugin.contains(".") ? mainProcessPlugin : "com.zy.core.plugin." + mainProcessPlugin;
                        Class<? extends MainProcessPluginApi> clazz = Class.forName(className).asSubclass(MainProcessPluginApi.class);
                        try {
                            mainProcessPluginApi = SpringUtils.getBean(clazz);
                        } catch (CoolException coolException) {
                            Thread.sleep(300);
                        } catch (Exception e) {
                            log.error("Failed to load mainProcessPlugin: {}", mainProcessPlugin, e);
                        }
                    }
                    // 系统运行状态判断
                    if (!SystemProperties.WCS_RUNNING_STATUS.get()) {
                    // 如果加载失败,等待后重试,防止空指针
                    if (mainProcessPluginApi == null) {
                        Thread.sleep(3000);
                        continue;
                    }
                    // 2. 系统运行状态判断
                    if (!SystemProperties.WCS_RUNNING_STATUS.get()) {
                        // 防止忙等 (Busy-wait optimization)
                        Thread.sleep(1000);
                        continue;
                    }
                    // 3. 执行主流程
                    mainProcessPluginApi.run();
                    // 间隔
                    // 4. 间隔
                    Thread.sleep(200);
                } catch (InterruptedException ie) {
                    log.info("MainProcess thread interrupted, stopping...");
                    Thread.currentThread().interrupt();
                    break;
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("Error in MainProcess execution loop", e);
                    try {
                        // 避免异常导致的狂刷日志
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        });
src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -52,6 +52,7 @@
    AI_CHAT_HISTORY("ai_chat_history_"),
    AI_CHAT_META("ai_chat_meta_"),
    MAIN_PROCESS_PSEUDOCODE("main_process_pseudocode"),
    PLANNER_SCHEDULE("planner_schedule_"),
    ;
    public String key;
src/main/java/com/zy/core/model/command/CrnCommand.java
@@ -1,9 +1,9 @@
package com.zy.core.model.command;
import lombok.Data;
/**
 * 堆垛机命令报文
 * Created by vincent on 2020/8/11
 */
@Data
public class CrnCommand {
@@ -12,10 +12,10 @@
    private Integer crnNo = 0;
    // 任务完成确认位
    private Short ackFinish = 0;
    private Integer ackFinish = 0;
    // 任务号
    private Short taskNo = 0;
    private Integer taskNo = 0;
    /**
     * 任务模式:
@@ -30,27 +30,27 @@
     * 90 = 设置时间
     * 99 = 取消当前任务
     */
    private Short taskMode = 0;
    private Integer taskMode = 0;
    // 源位置排号
    private Short sourcePosX = 0;
    private Integer sourcePosX = 0;
    // 源位置列号
    private Short sourcePosY = 0;
    private Integer sourcePosY = 0;
    // 源位置层号
    private Short sourcePosZ = 0;
    private Integer sourcePosZ = 0;
    // 目标位置排号
    private Short destinationPosX = 0;
    private Integer destinationPosX = 0;
    // 目标位置列号
    private Short destinationPosY = 0;
    private Integer destinationPosY = 0;
    // 目标位置层号
    private Short destinationPosZ = 0;
    private Integer destinationPosZ = 0;
    // 任务确认 0:未确认 1:已确认
    private Short command = 0;
    private Integer command = 0;
}
src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
@@ -3,6 +3,7 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.asrs.utils.Utils;
import com.zy.common.model.NavigateNode;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
@@ -49,6 +50,34 @@
    @Override
    public boolean connect() {
        Thread checkThread = new Thread(() -> {
            while (true) {
                try {
                    for (Map.Entry<Integer, List<ZyStationStatusEntity>> entry : deviceStatusMap.entrySet()) {
                        List<ZyStationStatusEntity> stationList = entry.getValue();
                        for (ZyStationStatusEntity statusEntity : stationList) {
                            if (statusEntity.isAutoing()
                                    && statusEntity.isLoading()
                                    && statusEntity.getTaskNo() > 0
                                    && !statusEntity.isRunBlock()
                                    && !statusEntity.getStationId().equals(statusEntity.getTargetStaNo())
                            ) {
                                BlockingQueue<StationCommand> commands = taskQueues.get(statusEntity.getTaskNo());
                                if (commands == null) {
                                    statusEntity.setRunBlock(true);
                                }
                            }
                        }
                    }
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        checkThread.start();
        return true;
    }
@@ -94,11 +123,16 @@
            return new CommandResponse(false, "任务号为空");
        }
        // 处理非移动命令
        if (command.getCommandType() != StationCommandType.MOVE) {
            handleCommand(deviceNo, command);
        }else {
        taskQueues.computeIfAbsent(taskNo, k -> new LinkedBlockingQueue<>()).offer(command);
        taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
        if (taskRunning.putIfAbsent(taskNo, true) == null) {
            executor.submit(() -> runTaskLoop(deviceNo, taskNo));
            }
        }
        return new CommandResponse(true, "命令已受理(异步执行)");
@@ -118,6 +152,7 @@
            StationCommand initialCommand = null;
            Integer finalTargetStationId = null;
            boolean generateBarcode = false;
            long stepExecuteTime = System.currentTimeMillis();
            while (true) {
                BlockingQueue<StationCommand> commandQueue = taskQueues.get(taskNo);
@@ -128,6 +163,7 @@
                // 尝试获取新命令,如果没有新命令则继续执行现有路径
                StationCommand command = commandQueue.poll(100, TimeUnit.MILLISECONDS);
                if (command != null) {
                    stepExecuteTime = System.currentTimeMillis();
                    taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
                    
                    if (initialCommand == null) {
@@ -159,11 +195,6 @@
                                pathQueue.offer(stationId);
                            }
                        }
                    }
                    // 处理非移动命令
                    if (command.getCommandType() != StationCommandType.MOVE) {
                        handleCommand(deviceNo, command);
                    }
                }
@@ -200,8 +231,27 @@
                            if (moveSuccess) {
                                currentPathIndex++;
                                pathQueue.poll();
                                stepExecuteTime = System.currentTimeMillis();
                                sleep(1000); // 模拟耗时
                            } else {
                                if (!checkTaskNoInArea(taskNo)) {
                                    boolean fakeAllowCheckBlock = true;
                                    Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
                                    if (systemConfigMapObj != null) {
                                        HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
                                        if (systemConfigMap.get("fakeAllowCheckBlock") != null && !systemConfigMap.get("fakeAllowCheckBlock").equals("Y")) {
                                            fakeAllowCheckBlock = false;
                                        }
                                    }
                                    if (fakeAllowCheckBlock && System.currentTimeMillis() - stepExecuteTime > 1000 * 10) {
                                        //认定堵塞
                                        boolean result = runBlockStation(taskNo, currentStationId, currentDeviceNo, taskNo, currentStationId);
                                        if(result) {
                                            break;
                                        }
                                    }
                                }
                                sleep(1000); // 失败重试等待
                            }
                        } else {
src/main/java/com/zy/core/network/real/ZyCrnRealConnect.java
@@ -123,14 +123,14 @@
            short[] array = new short[9];
            array[0] = (short) 0;
            array[1] = command.getTaskNo();
            array[2] = command.getTaskMode();
            array[3] = command.getSourcePosX();
            array[4] = command.getSourcePosY();
            array[5] = command.getSourcePosZ();
            array[6] = command.getDestinationPosX();
            array[7] = command.getDestinationPosY();
            array[8] = command.getDestinationPosZ();
            array[1] = command.getTaskNo().shortValue();
            array[2] = command.getTaskMode().shortValue();
            array[3] = command.getSourcePosX().shortValue();
            array[4] = command.getSourcePosY().shortValue();
            array[5] = command.getSourcePosZ().shortValue();
            array[6] = command.getDestinationPosX().shortValue();
            array[7] = command.getDestinationPosY().shortValue();
            array[8] = command.getDestinationPosZ().shortValue();
            OperateResult result = siemensNet.Write("DB100.0", array);
            if (!result.IsSuccess) {
src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java
@@ -256,15 +256,15 @@
    public CrnCommand getPickAndPutCommand(String sourceLocNo, String targetLocNo, Integer taskNo, Integer crnNo) {
        CrnCommand crnCommand = new CrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo(taskNo.shortValue()); // 工作号
        crnCommand.setTaskMode(CrnTaskModeType.LOC_MOVE.id.shortValue()); // 任务模式:  库位移转
        crnCommand.setSourcePosX((short) Utils.getRow(sourceLocNo));     // 源库位排
        crnCommand.setSourcePosY((short) Utils.getBay(sourceLocNo));     // 源库位列
        crnCommand.setSourcePosZ((short) Utils.getLev(sourceLocNo));     // 源库位层
        crnCommand.setDestinationPosX((short) Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY((short) Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ((short) Utils.getLev(targetLocNo));     // 目标库位层
        crnCommand.setCommand((short) 1);     // 任务确认
        crnCommand.setTaskNo(taskNo); // 工作号
        crnCommand.setTaskMode(CrnTaskModeType.LOC_MOVE.id); // 任务模式:  库位移转
        crnCommand.setSourcePosX(Utils.getRow(sourceLocNo));     // 源库位排
        crnCommand.setSourcePosY(Utils.getBay(sourceLocNo));     // 源库位列
        crnCommand.setSourcePosZ(Utils.getLev(sourceLocNo));     // 源库位层
        crnCommand.setDestinationPosX(Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY(Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ(Utils.getLev(targetLocNo));     // 目标库位层
        crnCommand.setCommand(1);     // 任务确认
        return crnCommand;
    }
@@ -272,13 +272,13 @@
    public CrnCommand getMoveCommand(String targetLocNo, Integer taskNo, Integer crnNo) {
        CrnCommand crnCommand = new CrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo(taskNo.shortValue()); // 工作号
        crnCommand.setAckFinish((short) 0);  // 任务完成确认位
        crnCommand.setTaskMode(CrnTaskModeType.CRN_MOVE.id.shortValue()); // 任务模式:  堆垛机移动
        crnCommand.setDestinationPosX((short) Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY((short) Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ((short) Utils.getLev(targetLocNo));     // 目标库位层
        crnCommand.setCommand((short) 1);     // 任务确认
        crnCommand.setTaskNo(taskNo); // 工作号
        crnCommand.setAckFinish(0);  // 任务完成确认位
        crnCommand.setTaskMode(CrnTaskModeType.CRN_MOVE.id); // 任务模式:  堆垛机移动
        crnCommand.setDestinationPosX(Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY(Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ(Utils.getLev(targetLocNo));     // 目标库位层
        crnCommand.setCommand(1);     // 任务确认
        return crnCommand;
    }
@@ -286,16 +286,16 @@
    public CrnCommand getResetCommand(Integer crnNo) {
        CrnCommand crnCommand = new CrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo((short) 0); // 工作号
        crnCommand.setAckFinish((short) 1);  // 任务完成确认位
        crnCommand.setTaskMode(CrnTaskModeType.NONE.id.shortValue()); // 任务模式
        crnCommand.setSourcePosX((short)0);     // 源库位排
        crnCommand.setSourcePosY((short)0);     // 源库位列
        crnCommand.setSourcePosZ((short)0);     // 源库位层
        crnCommand.setDestinationPosX((short)0);     // 目标库位排
        crnCommand.setDestinationPosY((short)0);     // 目标库位列
        crnCommand.setDestinationPosZ((short)0);     // 目标库位层
        crnCommand.setCommand((short) 1);     // 任务确认
        crnCommand.setTaskNo(0); // 工作号
        crnCommand.setAckFinish(1);  // 任务完成确认位
        crnCommand.setTaskMode(CrnTaskModeType.NONE.id); // 任务模式
        crnCommand.setSourcePosX(0);     // 源库位排
        crnCommand.setSourcePosY(0);     // 源库位列
        crnCommand.setSourcePosZ(0);     // 源库位层
        crnCommand.setDestinationPosX(0);     // 目标库位排
        crnCommand.setDestinationPosY(0);     // 目标库位列
        crnCommand.setDestinationPosZ(0);     // 目标库位层
        crnCommand.setCommand(1);     // 任务确认
        return crnCommand;
    }
src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java
@@ -50,8 +50,20 @@
    @Autowired
    private CommonService commonService;
    //入出库  ===>>  堆垛机入出库作业下发
    public synchronized void crnIoExecute() {
        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj != null) {
            HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
            if (systemConfigMap.get("crnRunMethod").equals("solver")) {
                plannerExecute();
            }else {
                crnIoExecuteNormal();
            }
        }
    }
    //入出库  ===>>  堆垛机入出库作业下发
    public synchronized void crnIoExecuteNormal() {
        List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
        for (BasCrnp basCrnp : basCrnps) {
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
@@ -290,6 +302,182 @@
        }
    }
    private synchronized boolean crnExecuteInPlanner(BasCrnp basCrnp, CrnThread crnThread, WrkMast wrkMast) {
        CrnProtocol crnProtocol = crnThread.getStatus();
        if (crnProtocol == null) {
            return false;
        }
        if (!basCrnp.getInEnable().equals("Y")) {
            News.info("堆垛机:{} 可入信号不满足", basCrnp.getCrnNo());
            return false;
        }
        List<StationObjModel> inStationList = basCrnp.getInStationList$();
        if (inStationList.isEmpty()) {
            News.info("堆垛机:{} 入库站点未设置", basCrnp.getCrnNo());
            return false;
        }
        Integer crnNo = basCrnp.getCrnNo();
        for (StationObjModel stationObjModel : inStationList) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationProtocolMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = stationProtocolMap.get(stationObjModel.getStationId());
            if (stationProtocol == null) {
                continue;
            }
            if (!stationProtocol.isAutoing()) {
                continue;
            }
            if (!stationProtocol.isLoading()) {
                continue;
            }
            if (stationProtocol.getTaskNo() <= 0) {
                continue;
            }
            if (!stationProtocol.isInEnable()) {
                News.taskInfo(stationProtocol.getTaskNo(), "取货站点:{} 没有可入信号", stationObjModel.getStationId());
                continue;
            }
            if (!wrkMast.getWrkNo().equals(stationProtocol.getTaskNo())) {
                continue;
            }
            if (wrkMast.getWrkSts() != WrkStsType.INBOUND_DEVICE_RUN.sts) {
                continue;
            }
            // 获取库位信息
            LocMast locMast = locMastService.selectById(wrkMast.getLocNo());
            if (locMast == null) {
                News.taskInfo(wrkMast.getWrkNo(), "目标库位:{} 信息不存在", wrkMast.getLocNo());
                continue;
            }
            if (!locMast.getLocSts().equals("S")) {
                News.taskInfo(wrkMast.getWrkNo(), "目标库位:{} 状态异常", wrkMast.getLocNo());
                continue;
            }
            //检测浅库位状态
            boolean checkStatus = checkShallowLocStatus(locMast.getLocNo(), wrkMast.getWrkNo());
            if (!checkStatus) {
                News.taskInfo(wrkMast.getWrkNo(), "因浅库位堵塞无法执行");
                continue;
            }
            String sourceLocNo = Utils.getLocNo(stationObjModel.getDeviceRow(), stationObjModel.getDeviceBay(), stationObjModel.getDeviceLev());
            CrnCommand command = crnThread.getPickAndPutCommand(sourceLocNo, wrkMast.getLocNo(), wrkMast.getWrkNo(), crnNo);
            wrkMast.setWrkSts(WrkStsType.INBOUND_RUN.sts);
            wrkMast.setCrnNo(crnNo);
            wrkMast.setSystemMsg("");
            wrkMast.setIoTime(new Date());
            if (wrkMastService.updateById(wrkMast)) {
                MessageQueue.offer(SlaveType.Crn, crnNo, new Task(2, command));
                News.info("堆垛机命令下发成功,堆垛机号={},任务数据={}", crnNo, JSON.toJSON(command));
                return true;
            }
        }
        return false;
    }
    private synchronized boolean crnExecuteOutPlanner(BasCrnp basCrnp, CrnThread crnThread, WrkMast wrkMast) {
        CrnProtocol crnProtocol = crnThread.getStatus();
        if (crnProtocol == null) {
            return false;
        }
        if (!basCrnp.getOutEnable().equals("Y")) {
            News.info("堆垛机:{} 可出信号不满足", basCrnp.getCrnNo());
            return false;
        }
        List<StationObjModel> outStationList = basCrnp.getOutStationList$();
        if (outStationList.isEmpty()) {
            News.info("堆垛机:{} 出库站点未设置", basCrnp.getCrnNo());
            return false;
        }
        Integer crnNo = basCrnp.getCrnNo();
        for (StationObjModel stationObjModel : outStationList) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationProtocolMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = stationProtocolMap.get(stationObjModel.getStationId());
            if (stationProtocol == null) {
                continue;
            }
            if (!stationProtocol.isAutoing()) {
                continue;
            }
            if (stationProtocol.isLoading()) {
                continue;
            }
            if (stationProtocol.getTaskNo() != 0) {
                continue;
            }
            if (!stationProtocol.isOutEnable()) {
                News.info("放货站点:{} 没有可出信号", stationObjModel.getStationId());
                continue;
            }
            // 获取库位信息
            LocMast locMast = locMastService.selectById(wrkMast.getSourceLocNo());
            if (locMast == null) {
                News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 信息不存在", wrkMast.getSourceLocNo());
                continue;
            }
            if (!locMast.getLocSts().equals("R")) {
                News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 状态异常", wrkMast.getSourceLocNo());
                continue;
            }
            //检测浅库位状态
            boolean checkStatus = checkShallowLocStatus(locMast.getLocNo(), wrkMast.getWrkNo());
            if (!checkStatus) {
                News.taskInfo(wrkMast.getWrkNo(), "因浅库位堵塞无法执行");
                continue;
            }
            String targetLocNo = Utils.getLocNo(stationObjModel.getDeviceRow(), stationObjModel.getDeviceBay(), stationObjModel.getDeviceLev());
            CrnCommand command = crnThread.getPickAndPutCommand(wrkMast.getSourceLocNo(), targetLocNo, wrkMast.getWrkNo(), crnNo);
            wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN.sts);
            wrkMast.setCrnNo(crnNo);
            wrkMast.setSystemMsg("");
            wrkMast.setIoTime(new Date());
            if (wrkMastService.updateById(wrkMast)) {
                MessageQueue.offer(SlaveType.Crn, crnNo, new Task(2, command));
                News.info("堆垛机命令下发成功,堆垛机号={},任务数据={}", crnNo, JSON.toJSON(command));
                return true;
            }
        }
        return false;
    }
    private synchronized void crnExecuteLocTransfer(BasCrnp basCrnp, CrnThread crnThread) {
        CrnProtocol crnProtocol = crnThread.getStatus();
        if(crnProtocol == null){
@@ -398,6 +586,153 @@
        }
    }
    public synchronized void plannerExecute() {
        int nowSec = (int) (System.currentTimeMillis() / 1000);
        List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
        for (BasCrnp basCrnp : basCrnps) {
            String key = RedisKeyType.PLANNER_SCHEDULE.key + "CRN-" + basCrnp.getCrnNo();
            List<Object> items = redisUtil.lGet(key, 0, -1);
            if (items == null || items.isEmpty()) {
                continue;
            }
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
            if (crnThread == null) {
                continue;
            }
            CrnProtocol crnProtocol = crnThread.getStatus();
            if (crnProtocol == null) {
                continue;
            }
            List<WrkMast> running = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                    .eq("crn_no", basCrnp.getCrnNo())
                    .in("wrk_sts", WrkStsType.INBOUND_RUN.sts, WrkStsType.OUTBOUND_RUN.sts, WrkStsType.LOC_MOVE_RUN.sts)
            );
            if (!running.isEmpty()) {
                continue;
            }
            if (!(crnProtocol.getMode() == CrnModeType.AUTO.id
                    && crnProtocol.getTaskNo() == 0
                    && crnProtocol.getStatus() == CrnStatusType.IDLE.id
                    && crnProtocol.getLoaded() == 0
                    && crnProtocol.getForkPos() == 0
                    && crnProtocol.getAlarm() == 0)) {
                continue;
            }
            for (Object v : items) {
                String s = String.valueOf(v);
                JSONObject obj = null;
                try { obj = JSON.parseObject(s); } catch (Exception ignore) {}
                if (obj == null) {
                    continue;
                }
                Integer startEpochSec = obj.getInteger("startEpochSec");
                Integer endEpochSec = obj.getInteger("endEpochSec");
                Integer taskId = obj.getInteger("taskId");
                String taskType = obj.getString("taskType");
                if (startEpochSec == null || taskId == null || taskType == null) {
                    continue;
                }
                int earlySlackSec = 5;
                int lateSlackSec = 10;
                Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
                if (systemConfigMapObj != null) {
                    try {
                        HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
                        String es = systemConfigMap.getOrDefault("plannerEarlySlackSec", "60");
                        String ls = systemConfigMap.getOrDefault("plannerLateSlackSec", "10");
                        earlySlackSec = Integer.parseInt(es);
                        lateSlackSec = Integer.parseInt(ls);
                    } catch (Exception ignore) {}
                }
                if (nowSec < startEpochSec - earlySlackSec) {
                    continue;
                }
                if (endEpochSec != null && nowSec > endEpochSec + lateSlackSec) {
                    redisUtil.lRemove(key, 1, s);
                    continue;
                }
                WrkMast wrkMast = wrkMastService.selectByWorkNo(taskId);
                if (wrkMast == null) {
                    redisUtil.lRemove(key, 1, s);
                    continue;
                }
                if ("IN".equalsIgnoreCase(taskType)) {
                    boolean result = this.crnExecuteInPlanner(basCrnp, crnThread, wrkMast);//入库
                    if (result) {
                        redisUtil.lRemove(key, 1, s);
                        break;
                    }
                } else if ("OUT".equalsIgnoreCase(taskType)) {
                    boolean result = this.crnExecuteOutPlanner(basCrnp, crnThread, wrkMast);//出库
                    if (result) {
                        redisUtil.lRemove(key, 1, s);
                        break;
                    }
                } else if ("MOVE".equalsIgnoreCase(taskType)) {
                    boolean result = this.crnExecuteMovePlanner(basCrnp, crnThread, wrkMast);//移库
                    if (result) {
                        redisUtil.lRemove(key, 1, s);
                        break;
                    }
                }
            }
        }
    }
    private synchronized boolean crnExecuteMovePlanner(BasCrnp basCrnp, CrnThread crnThread, WrkMast wrkMast) {
        CrnProtocol crnProtocol = crnThread.getStatus();
        if (crnProtocol == null) {
            return false;
        }
        Integer crnNo = basCrnp.getCrnNo();
        if (!wrkMast.getWrkSts().equals(WrkStsType.NEW_LOC_MOVE.sts)) {
            return false;
        }
        // 获取源库位信息
        LocMast sourceLocMast = locMastService.selectById(wrkMast.getSourceLocNo());
        if (sourceLocMast == null) {
            News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 信息不存在", wrkMast.getSourceLocNo());
            return false;
        }
        if(!sourceLocMast.getLocSts().equals("R")){
            News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 状态异常,不属于出库预约状态", wrkMast.getSourceLocNo());
            return false;
        }
        // 获取库位信息
        LocMast locMast = locMastService.selectById(wrkMast.getLocNo());
        if (locMast == null) {
            News.taskInfo(wrkMast.getWrkNo(), "库位:{} 信息不存在", wrkMast.getLocNo());
            return false;
        }
        if (!locMast.getLocSts().equals("S")) {
            News.taskInfo(wrkMast.getWrkNo(), "库位:{} 状态异常,不属于入库预约状态", wrkMast.getLocNo());
            return false;
        }
        CrnCommand command = crnThread.getPickAndPutCommand(wrkMast.getSourceLocNo(), wrkMast.getLocNo(), wrkMast.getWrkNo(), crnNo);
        wrkMast.setWrkSts(WrkStsType.LOC_MOVE_RUN.sts);
        wrkMast.setCrnNo(crnNo);
        wrkMast.setSystemMsg("");
        wrkMast.setIoTime(new Date());
        if (wrkMastService.updateById(wrkMast)) {
            MessageQueue.offer(SlaveType.Crn, crnNo, new Task(2, command));
            News.info("堆垛机命令下发成功,堆垛机号={},任务数据={}", crnNo, JSON.toJSON(command));
            return true;
        }
        return false;
    }
    //检测浅库位状态
    public synchronized boolean checkShallowLocStatus(String locNo, Integer taskNo) {
        String checkDeepLocOutTaskBlockReport = "Y";
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -13,10 +13,7 @@
import com.zy.core.News;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.SlaveType;
import com.zy.core.enums.StationCommandType;
import com.zy.core.enums.WrkStsType;
import com.zy.core.enums.*;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.Task;
import com.zy.core.model.command.StationCommand;
@@ -258,7 +255,7 @@
                    }
                    redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15);
                    if (runBlockReassignLocStationList.contains(stationProtocol.getStationId())) {
                    if (wrkMast.getIoType() == WrkIoType.IN.id && runBlockReassignLocStationList.contains(stationProtocol.getStationId())) {
                        //站点处于重新分配库位区域
                        //运行堵塞,重新申请任务
                        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
@@ -358,5 +355,25 @@
        }
    }
    //获取输送线任务数量
    public synchronized int getCurrentStationTaskCount() {
        int currentStationTaskCount = 0;
        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getId());
            if (stationThread == null) {
                continue;
            }
            for (StationProtocol stationProtocol : stationThread.getStatus()) {
                if (stationProtocol.getTaskNo() > 0) {
                    currentStationTaskCount++;
                }
            }
        }
        return currentStationTaskCount;
    }
}
src/main/resources/application.yml
@@ -67,7 +67,7 @@
  threadControlCount: 10
  liftType: lift
mainProcessPlugin: NormalProcess
mainProcessPlugin: FakeProcess
deviceLogStorage:
  # 设备日志存储方式 mysql file