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(){ 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) { String className = mainProcessPlugin.contains(".") ? mainProcessPlugin : "com.zy.core.plugin." + mainProcessPlugin; Class<? extends MainProcessPluginApi> clazz = Class.forName(className).asSubclass(MainProcessPluginApi.class); try { String className = mainProcessPlugin.contains(".") ? mainProcessPlugin : "com.zy.core.plugin." + mainProcessPlugin; Class<? extends MainProcessPluginApi> clazz = Class.forName(className).asSubclass(MainProcessPluginApi.class); 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, "任务号为空"); } taskQueues.computeIfAbsent(taskNo, k -> new LinkedBlockingQueue<>()).offer(command); taskLastUpdateTime.put(taskNo, System.currentTimeMillis()); // 处理非移动命令 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)); 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