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> crnDataList = new ArrayList<>(); List basCrnps = basCrnpService.selectList(new EntityWrapper().eq("status", 1)); Map stationIndex = new HashMap<>(); Map 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 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 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 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 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().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> taskDataList = new ArrayList<>(); List outTasks = wrkMastService.selectList(new EntityWrapper().eq("wrk_sts", WrkStsType.NEW_OUTBOUND.sts)); for (WrkMast wrkMast : outTasks) { HashMap 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 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 toPos = new HashMap<>(); toPos.put("x", toX * bayWidth); toPos.put("y", toY * levHeight); t.put("toPos", toPos); ArrayList 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> controlRows = basCrnp.getControlRows$(); for (List rows : controlRows) { if (rows.contains(row)) { eligible.add("CRN-" + basCrnp.getCrnNo()); break; } } } } } catch (Exception ignore) {} } t.put("eligibleCranes", eligible); t.put("conveyorPath", new ArrayList<>()); t.put("outGroup", wrkMast.getBatch()); t.put("outSeq", wrkMast.getBatchSeq()); taskDataList.add(t); } List moveTasks = wrkMastService.selectList(new EntityWrapper().eq("wrk_sts", WrkStsType.NEW_LOC_MOVE.sts)); for (WrkMast wrkMast : moveTasks) { HashMap 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 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 toPos = new HashMap<>(); toPos.put("x", toX * bayWidth); toPos.put("y", toY * levHeight); t.put("toPos", toPos); ArrayList 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> controlRows = basCrnp.getControlRows$(); for (List 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 inTasks = wrkMastService.selectList(new EntityWrapper().eq("wrk_sts", WrkStsType.INBOUND_DEVICE_RUN.sts)); for (WrkMast wrkMast : inTasks) { HashMap 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 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 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 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 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 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("wOutTardiness", getIntConfig("plannerOutTardiness", 0)); 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 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().eq("code", "plannerSolverUri")); Config pathCfg = configService.selectOne(new EntityWrapper().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 schedule = result.getJSONArray("schedule"); if (schedule != null) { Map> 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> 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().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().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().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; } }