src/main/java/com/zy/asrs/controller/ConsoleController.java
@@ -59,6 +59,8 @@ private LocMastService locMastService; @Autowired private BasMapService basMapService; @Autowired private StationCycleCapacityService stationCycleCapacityService; @PostMapping("/system/running/status") @ManagerAuth(memo = "ç³»ç»è¿è¡ç¶æ") @@ -266,6 +268,11 @@ return R.ok().add(vos); } @GetMapping("/latest/data/station/cycle/capacity") public R stationCycleCapacity() { return R.ok().add(stationCycleCapacityService.getLatestSnapshot()); } // @PostMapping("/latest/data/barcode") // @ManagerAuth(memo = "æ¡ç æ«æä»ªå®æ¶æ°æ®") // public R barcodeLatestData(){ src/main/java/com/zy/asrs/domain/vo/StationCycleCapacityVo.java
New file @@ -0,0 +1,29 @@ package com.zy.asrs.domain.vo; import lombok.Data; import java.util.ArrayList; import java.util.Date; import java.util.List; @Data public class StationCycleCapacityVo { // 循ç¯åæç» private List<StationCycleLoopVo> loopList = new ArrayList<>(); // 循ç¯åæ°é private Integer loopCount = 0; // 循ç¯åç«ç¹æ»æ° private Integer totalStationCount = 0; // 循ç¯å䏿任å¡ç«ç¹æ»æ° private Integer taskStationCount = 0; // å½åæ¿è½½éï¼0-1ï¼ï¼å½å任塿° / 循ç¯åæ»ç«ç¹æ° private Double currentLoad = 0.0; // ææ°å·æ°æ¶é´ private Date refreshTime; } src/main/java/com/zy/asrs/domain/vo/StationCycleLoopVo.java
New file @@ -0,0 +1,28 @@ package com.zy.asrs.domain.vo; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class StationCycleLoopVo { // 循ç¯ååºå·ï¼ä»1å¼å§ï¼ private Integer loopNo; // 循ç¯åå ç«ç¹ç¼å· private List<Integer> stationIdList = new ArrayList<>(); // 循ç¯åå åå¨çå·¥ä½å· private List<Integer> workNoList = new ArrayList<>(); // 循ç¯åç«ç¹æ»æ° private Integer stationCount = 0; // 循ç¯åå æä»»å¡ç«ç¹æ° private Integer taskCount = 0; // å½åæ¿è½½éï¼0-1ï¼ï¼å½å任塿° / å½å循ç¯åæ»ç«ç¹æ° private Double currentLoad = 0.0; } src/main/java/com/zy/asrs/service/StationCycleCapacityService.java
New file @@ -0,0 +1,13 @@ package com.zy.asrs.service; import com.zy.asrs.domain.vo.StationCycleCapacityVo; public interface StationCycleCapacityService { // ç«å³å·æ°å¾ªç¯å䏿¿è½½éå¿«ç § void refreshSnapshot(); // è·åææ°å¾ªç¯å䏿¿è½½éå¿«ç § StationCycleCapacityVo getLatestSnapshot(); } src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java
New file @@ -0,0 +1,623 @@ 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.domain.vo.StationCycleCapacityVo; import com.zy.asrs.domain.vo.StationCycleLoopVo; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.DeviceConfig; import com.zy.asrs.service.BasMapService; import com.zy.asrs.service.BasDevpService; import com.zy.asrs.service.DeviceConfigService; import com.zy.asrs.service.StationCycleCapacityService; import com.zy.common.model.NavigateNode; import com.zy.common.utils.RedisUtil; import com.zy.common.utils.NavigateSolution; import com.zy.core.cache.SlaveConnection; import com.zy.core.enums.RedisKeyType; import com.zy.core.enums.SlaveType; import com.zy.core.model.StationObjModel; import com.zy.core.model.protocol.StationProtocol; import com.zy.core.thread.StationThread; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @Service("stationCycleCapacityService") @Slf4j public class StationCycleCapacityServiceImpl implements StationCycleCapacityService { private static final long LOOP_LOAD_RESERVE_EXPIRE_MILLIS = 120_000L; @Autowired private BasMapService basMapService; @Autowired private DeviceConfigService deviceConfigService; @Autowired private BasDevpService basDevpService; @Autowired private RedisUtil redisUtil; private final AtomicReference<StationCycleCapacityVo> snapshotRef = new AtomicReference<>(new StationCycleCapacityVo()); @Override public synchronized void refreshSnapshot() { try { StationCycleCapacityVo snapshot = buildSnapshot(); snapshotRef.set(snapshot); } catch (Exception e) { log.error("å·æ°å¾ªç¯åæ¿è½½é失败", e); } } @Override public StationCycleCapacityVo getLatestSnapshot() { StationCycleCapacityVo snapshot = snapshotRef.get(); if (snapshot == null || snapshot.getRefreshTime() == null) { refreshSnapshot(); snapshot = snapshotRef.get(); } return snapshot == null ? new StationCycleCapacityVo() : snapshot; } private StationCycleCapacityVo buildSnapshot() { GraphContext context = buildStationGraph(); Map<Integer, Integer> workNoMap = buildStationWorkNoMap(); Set<Integer> availableStationSet = new HashSet<>(context.graph.keySet()); availableStationSet.removeAll(context.excludeStationSet); Map<Integer, Set<Integer>> filteredGraph = new HashMap<>(); for (Integer stationId : availableStationSet) { Set<Integer> nextSet = context.graph.getOrDefault(stationId, Collections.emptySet()); Set<Integer> filteredNext = new HashSet<>(); for (Integer nextId : nextSet) { if (availableStationSet.contains(nextId)) { filteredNext.add(nextId); } } filteredGraph.put(stationId, filteredNext); } List<Set<Integer>> sccList = findStrongConnectedComponents(filteredGraph); List<StationCycleLoopVo> loopList = new ArrayList<>(); int loopNo = 1; int totalStationCount = 0; int taskStationCount = 0; Set<Integer> actualWorkNoSet = new HashSet<>(); for (Set<Integer> scc : sccList) { if (!isCycleScc(scc, filteredGraph)) { continue; } // 对 SCC åå䏿¬¡âç¯æ ¸å¿âå¥ç¦»ï¼å餿æ/æ»è¡åèç¹ List<Set<Integer>> coreLoopList = extractCoreLoopComponents(scc, filteredGraph); for (Set<Integer> coreLoop : coreLoopList) { List<Integer> stationIdList = new ArrayList<>(coreLoop); Collections.sort(stationIdList); List<Integer> workNoList = new ArrayList<>(); int currentLoopTaskCount = 0; for (Integer stationId : stationIdList) { Integer workNo = workNoMap.get(stationId); if (workNo != null && workNo > 0) { workNoList.add(workNo); currentLoopTaskCount++; actualWorkNoSet.add(workNo); } } StationCycleLoopVo loopVo = new StationCycleLoopVo(); loopVo.setLoopNo(loopNo++); loopVo.setStationIdList(stationIdList); loopVo.setWorkNoList(workNoList); loopVo.setStationCount(stationIdList.size()); loopVo.setTaskCount(currentLoopTaskCount); loopVo.setCurrentLoad(calcCurrentLoad(currentLoopTaskCount, stationIdList.size())); loopList.add(loopVo); totalStationCount += stationIdList.size(); taskStationCount += currentLoopTaskCount; } } int reserveTaskCount = mergeReserveTaskCount(loopList, actualWorkNoSet); taskStationCount += reserveTaskCount; StationCycleCapacityVo vo = new StationCycleCapacityVo(); vo.setLoopList(loopList); vo.setLoopCount(loopList.size()); vo.setTotalStationCount(totalStationCount); vo.setTaskStationCount(taskStationCount); vo.setCurrentLoad(calcCurrentLoad(taskStationCount, totalStationCount)); vo.setRefreshTime(new Date()); return vo; } private int mergeReserveTaskCount(List<StationCycleLoopVo> loopList, Set<Integer> actualWorkNoSet) { if (loopList == null || loopList.isEmpty()) { return 0; } Map<Object, Object> reserveMap = redisUtil.hmget(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key); if (reserveMap == null || reserveMap.isEmpty()) { return 0; } Map<Integer, StationCycleLoopVo> loopMap = new HashMap<>(); Map<Integer, StationCycleLoopVo> stationLoopMap = new HashMap<>(); for (StationCycleLoopVo loopVo : loopList) { if (loopVo != null && loopVo.getLoopNo() != null) { loopMap.put(loopVo.getLoopNo(), loopVo); } if (loopVo == null || loopVo.getStationIdList() == null) { continue; } for (Integer stationId : loopVo.getStationIdList()) { if (stationId != null) { stationLoopMap.put(stationId, loopVo); } } } long now = System.currentTimeMillis(); int mergedCount = 0; List<Object> removeFieldList = new ArrayList<>(); for (Map.Entry<Object, Object> entry : reserveMap.entrySet()) { ReserveRecord record = parseReserveRecord(entry.getKey(), entry.getValue()); if (record == null) { removeFieldList.add(entry.getKey()); continue; } if (actualWorkNoSet.contains(record.wrkNo)) { removeFieldList.add(entry.getKey()); continue; } if (record.createTime <= 0 || now - record.createTime > LOOP_LOAD_RESERVE_EXPIRE_MILLIS) { removeFieldList.add(entry.getKey()); continue; } StationCycleLoopVo loopVo = loopMap.get(record.loopNo); if (loopVo == null && record.hitStationId != null) { loopVo = stationLoopMap.get(record.hitStationId); } if (loopVo == null) { removeFieldList.add(entry.getKey()); continue; } List<Integer> workNoList = loopVo.getWorkNoList(); if (workNoList == null) { workNoList = new ArrayList<>(); loopVo.setWorkNoList(workNoList); } if (workNoList.contains(record.wrkNo)) { continue; } workNoList.add(record.wrkNo); Collections.sort(workNoList); int mergedTaskCount = toNonNegative(loopVo.getTaskCount()) + 1; loopVo.setTaskCount(mergedTaskCount); loopVo.setCurrentLoad(calcCurrentLoad(mergedTaskCount, toNonNegative(loopVo.getStationCount()))); mergedCount++; } if (!removeFieldList.isEmpty()) { redisUtil.hdel(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, removeFieldList.toArray()); } return mergedCount; } private ReserveRecord parseReserveRecord(Object fieldObj, Object valueObj) { if (fieldObj == null || valueObj == null) { return null; } Integer fieldWrkNo = parseInteger(String.valueOf(fieldObj)); if (fieldWrkNo == null || fieldWrkNo <= 0) { return null; } JSONObject jsonObject; try { jsonObject = JSON.parseObject(String.valueOf(valueObj)); } catch (Exception e) { return null; } if (jsonObject == null) { return null; } Integer wrkNo = jsonObject.getInteger("wrkNo"); Integer loopNo = jsonObject.getInteger("loopNo"); Integer hitStationId = jsonObject.getInteger("hitStationId"); Long createTime = jsonObject.getLong("createTime"); if (wrkNo == null || wrkNo <= 0) { wrkNo = fieldWrkNo; } if ((loopNo == null || loopNo <= 0) && (hitStationId == null || hitStationId <= 0)) { return null; } if (createTime == null || createTime <= 0) { return null; } ReserveRecord record = new ReserveRecord(); record.wrkNo = wrkNo; record.loopNo = loopNo; record.hitStationId = hitStationId; record.createTime = createTime; return record; } private Integer parseInteger(String value) { if (value == null || value.trim().isEmpty()) { return null; } try { return Integer.parseInt(value.trim()); } catch (Exception e) { return null; } } private int toNonNegative(Integer value) { if (value == null || value < 0) { return 0; } return value; } private static class ReserveRecord { private Integer wrkNo; private Integer loopNo; private Integer hitStationId; private Long createTime; } private double calcCurrentLoad(int taskCount, int stationCount) { if (stationCount <= 0 || taskCount <= 0) { return 0.0; } double value = (double) taskCount / (double) stationCount; if (value < 0.0) { return 0.0; } if (value > 1.0) { return 1.0; } return value; } private GraphContext buildStationGraph() { GraphContext context = new GraphContext(); List<Integer> levList = basMapService.getLevList(); if (levList == null || levList.isEmpty()) { return context; } NavigateSolution navigateSolution = new NavigateSolution(); List<Integer> sortedLevList = new ArrayList<>(levList); sortedLevList = new ArrayList<>(new HashSet<>(sortedLevList)); Collections.sort(sortedLevList); for (Integer lev : sortedLevList) { List<List<NavigateNode>> stationMap; try { stationMap = navigateSolution.getStationMap(lev); } catch (Exception e) { log.warn("å 载楼å±{}å°å¾å¤±è´¥ï¼è·³è¿å¾ªç¯å计ç®", lev); continue; } if (stationMap == null || stationMap.isEmpty()) { continue; } for (List<NavigateNode> row : stationMap) { for (NavigateNode node : row) { JSONObject valueObj = parseNodeValue(node.getNodeValue()); if (valueObj == null) { continue; } Integer stationId = valueObj.getInteger("stationId"); if (stationId == null) { continue; } context.graph.computeIfAbsent(stationId, k -> new HashSet<>()); if (isExcludeStation(valueObj)) { context.excludeStationSet.add(stationId); } List<NavigateNode> nextNodeList = navigateSolution.extend_current_node(stationMap, node); if (nextNodeList == null || nextNodeList.isEmpty()) { continue; } for (NavigateNode nextNode : nextNodeList) { JSONObject nextValueObj = parseNodeValue(nextNode.getNodeValue()); if (nextValueObj == null) { continue; } Integer nextStationId = nextValueObj.getInteger("stationId"); if (nextStationId == null || stationId.equals(nextStationId)) { continue; } context.graph.computeIfAbsent(nextStationId, k -> new HashSet<>()); context.graph.get(stationId).add(nextStationId); } } } } appendExcludeStationsFromDeviceConfig(context.excludeStationSet); return context; } private void appendExcludeStationsFromDeviceConfig(Set<Integer> excludeStationSet) { List<BasDevp> basDevpList = basDevpService.selectList(new EntityWrapper<>()); if (basDevpList == null || basDevpList.isEmpty()) { return; } for (BasDevp basDevp : basDevpList) { List<StationObjModel> inStationList = basDevp.getInStationList$(); for (StationObjModel stationObjModel : inStationList) { if (stationObjModel != null && stationObjModel.getStationId() != null) { excludeStationSet.add(stationObjModel.getStationId()); } } List<StationObjModel> barcodeStationList = basDevp.getBarcodeStationList$(); for (StationObjModel stationObjModel : barcodeStationList) { if (stationObjModel != null && stationObjModel.getStationId() != null) { excludeStationSet.add(stationObjModel.getStationId()); } } } } private JSONObject parseNodeValue(String nodeValue) { if (nodeValue == null || nodeValue.trim().isEmpty()) { return null; } try { return JSON.parseObject(nodeValue); } catch (Exception ignore) { return null; } } private boolean isExcludeStation(JSONObject valueObj) { Integer isInStation = valueObj.getInteger("isInStation"); Integer isBarcodeStation = valueObj.getInteger("isBarcodeStation"); return (isInStation != null && isInStation == 1) || (isBarcodeStation != null && isBarcodeStation == 1); } private Map<Integer, Integer> buildStationWorkNoMap() { Map<Integer, Integer> workNoMap = new HashMap<>(); List<DeviceConfig> devpList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>() .eq("device_type", String.valueOf(SlaveType.Devp))); if (devpList == null || devpList.isEmpty()) { return workNoMap; } for (DeviceConfig deviceConfig : devpList) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, deviceConfig.getDeviceNo()); if (stationThread == null) { continue; } Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); if (statusMap == null || statusMap.isEmpty()) { continue; } for (StationProtocol protocol : statusMap.values()) { if (protocol == null || protocol.getStationId() == null) { continue; } Integer taskNo = protocol.getTaskNo(); if (taskNo != null && taskNo > 0) { workNoMap.put(protocol.getStationId(), taskNo); } } } return workNoMap; } private List<Set<Integer>> findStrongConnectedComponents(Map<Integer, Set<Integer>> graph) { List<Set<Integer>> result = new ArrayList<>(); if (graph == null || graph.isEmpty()) { return result; } Map<Integer, Integer> indexMap = new HashMap<>(); Map<Integer, Integer> lowLinkMap = new HashMap<>(); Deque<Integer> stack = new ArrayDeque<>(); Set<Integer> inStack = new HashSet<>(); int[] index = new int[]{0}; List<Integer> nodeList = new ArrayList<>(graph.keySet()); Collections.sort(nodeList); for (Integer node : nodeList) { if (!indexMap.containsKey(node)) { strongConnect(node, graph, indexMap, lowLinkMap, stack, inStack, index, result); } } return result; } private void strongConnect(Integer node, Map<Integer, Set<Integer>> graph, Map<Integer, Integer> indexMap, Map<Integer, Integer> lowLinkMap, Deque<Integer> stack, Set<Integer> inStack, int[] index, List<Set<Integer>> result) { indexMap.put(node, index[0]); lowLinkMap.put(node, index[0]); index[0]++; stack.push(node); inStack.add(node); List<Integer> nextList = new ArrayList<>(graph.getOrDefault(node, Collections.emptySet())); Collections.sort(nextList); for (Integer next : nextList) { if (!indexMap.containsKey(next)) { strongConnect(next, graph, indexMap, lowLinkMap, stack, inStack, index, result); lowLinkMap.put(node, Math.min(lowLinkMap.get(node), lowLinkMap.get(next))); } else if (inStack.contains(next)) { lowLinkMap.put(node, Math.min(lowLinkMap.get(node), indexMap.get(next))); } } if (!lowLinkMap.get(node).equals(indexMap.get(node))) { return; } Set<Integer> scc = new HashSet<>(); while (!stack.isEmpty()) { Integer top = stack.pop(); inStack.remove(top); scc.add(top); if (top.equals(node)) { break; } } result.add(scc); } private boolean isCycleScc(Set<Integer> scc, Map<Integer, Set<Integer>> graph) { if (scc == null || scc.isEmpty()) { return false; } if (scc.size() > 1) { return true; } Integer onlyNode = scc.iterator().next(); Set<Integer> nextSet = graph.getOrDefault(onlyNode, Collections.emptySet()); return nextSet.contains(onlyNode); } /** * ä» SCC 䏿åå¾ªç¯æ ¸å¿ï¼ * 1) 转æ åå¾ * 2) éå½å¥ç¦»åº¦æ°<2çèç¹ï¼2-coreï¼ * 3) å°å©ä½èç¹ææè¿éåéï¼æ¯ä¸ªåé>=3æè®¤å®ä¸ºå¾ªç¯å */ private List<Set<Integer>> extractCoreLoopComponents(Set<Integer> scc, Map<Integer, Set<Integer>> graph) { List<Set<Integer>> result = new ArrayList<>(); if (scc == null || scc.isEmpty()) { return result; } // æå»º SCC å æ å黿¥ Map<Integer, Set<Integer>> undirectedMap = new HashMap<>(); for (Integer node : scc) { undirectedMap.put(node, new HashSet<>()); } for (Integer from : scc) { Set<Integer> nextSet = graph.getOrDefault(from, Collections.emptySet()); for (Integer to : nextSet) { if (!scc.contains(to) || from.equals(to)) { continue; } undirectedMap.get(from).add(to); undirectedMap.get(to).add(from); } } // 2-core å¥ç¦» Set<Integer> alive = new HashSet<>(scc); Map<Integer, Integer> degreeMap = new HashMap<>(); ArrayDeque<Integer> queue = new ArrayDeque<>(); for (Integer node : scc) { int degree = undirectedMap.getOrDefault(node, Collections.emptySet()).size(); degreeMap.put(node, degree); if (degree < 2) { queue.offer(node); } } while (!queue.isEmpty()) { Integer node = queue.poll(); if (!alive.remove(node)) { continue; } for (Integer next : undirectedMap.getOrDefault(node, Collections.emptySet())) { if (!alive.contains(next)) { continue; } int newDegree = degreeMap.getOrDefault(next, 0) - 1; degreeMap.put(next, newDegree); if (newDegree < 2) { queue.offer(next); } } } if (alive.size() < 3) { return result; } // æåè¿éåé Set<Integer> visited = new HashSet<>(); List<Integer> sortedAlive = new ArrayList<>(alive); Collections.sort(sortedAlive); for (Integer start : sortedAlive) { if (!visited.add(start)) { continue; } Set<Integer> component = new HashSet<>(); ArrayDeque<Integer> bfs = new ArrayDeque<>(); bfs.offer(start); component.add(start); while (!bfs.isEmpty()) { Integer node = bfs.poll(); for (Integer next : undirectedMap.getOrDefault(node, Collections.emptySet())) { if (!alive.contains(next) || !visited.add(next)) { continue; } component.add(next); bfs.offer(next); } } // è³å°3ä¸ªç¹æè®¤ä¸ºæ¯çæ£âåâ if (component.size() >= 3) { result.add(component); } } return result; } private static class GraphContext { private final Map<Integer, Set<Integer>> graph = new HashMap<>(); private final Set<Integer> excludeStationSet = new HashSet<>(); } } src/main/java/com/zy/asrs/task/StationCycleCapacityScheduler.java
New file @@ -0,0 +1,20 @@ package com.zy.asrs.task; import com.zy.asrs.service.StationCycleCapacityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class StationCycleCapacityScheduler { @Autowired private StationCycleCapacityService stationCycleCapacityService; // æ¯ç§å·æ°ä¸æ¬¡å¾ªç¯åæ¿è½½é @Scheduled(cron = "0/1 * * * * ? ") public void refreshStationCycleCapacity() { stationCycleCapacityService.refreshSnapshot(); } } src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
@@ -103,6 +103,9 @@ } else if ("/console/latest/data/dualcrn".equals(url)) { ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class); resObj = consoleController.dualCrnLatestData(); } else if ("/console/latest/data/station/cycle/capacity".equals(url)) { ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class); resObj = consoleController.stationCycleCapacity(); } else if ("/crn/table/crn/state".equals(url)) { resObj = SpringUtils.getBean(CrnController.class).crnStateTable(); } else if ("/rgv/table/rgv/state".equals(url)) { src/main/java/com/zy/core/ServerBootstrap.java
@@ -135,6 +135,8 @@ thread = new ZyStationThread(deviceConfig, redisUtil); } else if (deviceConfig.getThreadImpl().equals("ZyStationV3Thread")) { thread = new ZyStationV3Thread(deviceConfig, redisUtil); } else if (deviceConfig.getThreadImpl().equals("ZyStationV4Thread")) { thread = new ZyStationV4Thread(deviceConfig, redisUtil); } else { throw new CoolException("æªç¥ç线ç¨å®ç°"); } src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -60,6 +60,7 @@ CRN_OUT_TASK_COMPLETE_STATION_INFO("crn_out_task_complete_station_info_"), WATCH_CIRCLE_STATION_("watch_circle_station_"), STATION_CYCLE_LOAD_RESERVE("station_cycle_load_reserve"), CURRENT_CIRCLE_TASK_CRN_NO("current_circle_task_crn_no_"), ASYNC_WMS_IN_TASK_REQUEST("async_wms_in_task_request_"), src/main/java/com/zy/core/model/command/StationCommand.java
@@ -19,6 +19,9 @@ private List<Integer> navigatePath; // è·¯å¾ä¸çé¡¶åç§»æ ½ç¹ï¼æè·¯å¾é¡ºåºï¼ private List<Integer> liftTransferPath; private List<Integer> originalNavigatePath; private StationCommandType commandType; src/main/java/com/zy/core/network/ZyStationConnectDriver.java
@@ -11,8 +11,10 @@ import java.util.List; import com.zy.core.network.fake.ZyStationFakeConnect; import com.zy.core.network.fake.ZyStationFakeSegConnect; import com.zy.core.network.fake.ZyStationV4FakeSegConnect; import com.zy.core.network.real.ZyStationRealConnect; import com.zy.core.network.real.ZyStationV3RealConnect; import com.zy.core.network.real.ZyStationV4RealConnect; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -27,6 +29,7 @@ private static final ZyStationFakeConnect zyStationFakeConnect = new ZyStationFakeConnect(); private static final ZyStationFakeSegConnect zyStationFakeSegConnect = new ZyStationFakeSegConnect(); private static final ZyStationV4FakeSegConnect zyStationV4FakeSegConnect = new ZyStationV4FakeSegConnect(); private boolean connected = false; private DeviceConfig deviceConfig; @@ -50,6 +53,8 @@ if (deviceConfig.getFake() == 0) { if ("ZyStationV3Thread".equals(deviceConfig.getThreadImpl())) { zyStationConnectApi = new ZyStationV3RealConnect(deviceConfig, redisUtil); } else if ("ZyStationV4Thread".equals(deviceConfig.getThreadImpl())) { zyStationConnectApi = new ZyStationV4RealConnect(deviceConfig, redisUtil); } else { zyStationConnectApi = new ZyStationRealConnect(deviceConfig, redisUtil); } @@ -57,6 +62,9 @@ if ("ZyStationV3Thread".equals(deviceConfig.getThreadImpl())) { zyStationFakeSegConnect.addFakeConnect(deviceConfig, redisUtil); zyStationConnectApi = zyStationFakeSegConnect; } else if ("ZyStationV4Thread".equals(deviceConfig.getThreadImpl())) { zyStationV4FakeSegConnect.addFakeConnect(deviceConfig, redisUtil); zyStationConnectApi = zyStationV4FakeSegConnect; } else { zyStationFakeConnect.addFakeConnect(deviceConfig, redisUtil); zyStationConnectApi = zyStationFakeConnect; src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java
@@ -50,6 +50,9 @@ //éé private Double weight; //ä»»å¡å¯ååº private Integer taskWriteIdx; //è¿è¡å µå¡ private boolean runBlock = false; src/main/java/com/zy/core/network/fake/ZyStationV4FakeSegConnect.java
New file @@ -0,0 +1,8 @@ package com.zy.core.network.fake; /** * è¾éç« V4 仿çåæ®µè¿æ¥å®ç°ã * å½åå¤ç¨ V3 åæ®µä»¿çé»è¾ï¼ä¿çç¬ç«ç±»ç¨äºåç» V4 仿ççç¥æ¼è¿ã */ public class ZyStationV4FakeSegConnect extends ZyStationFakeSegConnect { } src/main/java/com/zy/core/network/real/ZyStationV4RealConnect.java
New file @@ -0,0 +1,335 @@ package com.zy.core.network.real; import HslCommunication.Core.Types.OperateResult; import HslCommunication.Core.Types.OperateResultExOne; import HslCommunication.Profinet.Siemens.SiemensPLCS; import HslCommunication.Profinet.Siemens.SiemensS7Net; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.core.common.DateUtils; import com.core.common.SpringUtils; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.DeviceConfig; import com.zy.asrs.service.BasDevpService; import com.zy.common.utils.RedisUtil; import com.zy.core.News; import com.zy.core.cache.OutputQueue; import com.zy.core.model.CommandResponse; import com.zy.core.model.StationObjModel; import com.zy.core.model.command.StationCommand; import com.zy.core.network.api.ZyStationConnectApi; import com.zy.core.network.entity.ZyStationStatusEntity; import lombok.extern.slf4j.Slf4j; import java.text.MessageFormat; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; /** * è¾éç«çå®è¿æ¥ï¼PLCï¼ */ @Slf4j public class ZyStationV4RealConnect implements ZyStationConnectApi { private List<ZyStationStatusEntity> statusList; private List<StationObjModel> barcodeOriginList; private SiemensS7Net siemensNet; private DeviceConfig deviceConfig; private RedisUtil redisUtil; public ZyStationV4RealConnect(DeviceConfig deviceConfig, RedisUtil redisUtil) { this.deviceConfig = deviceConfig; this.redisUtil = redisUtil; } @Override public boolean connect() { boolean connected = false; siemensNet = new SiemensS7Net(SiemensPLCS.S1200, deviceConfig.getIp()); OperateResult connect = siemensNet.ConnectServer(); if (connect.IsSuccess) { connected = true; OutputQueue.DEVP.offer(MessageFormat.format("ã{0}ãè¾éç«plcè¿æ¥æå ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort())); News.info("è¾éç«plcè¿æ¥æå ===>> [id:{}] [ip:{}] [port:{}]", deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort()); } else { OutputQueue.DEVP.offer(MessageFormat.format("ã{0}ãè¾éç«plcè¿æ¥å¤±è´¥ï¼ï¼ï¼ ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort())); News.error("è¾éç«plcè¿æ¥å¤±è´¥ï¼ï¼ï¼ ===>> [id:{}] [ip:{}] [port:{}]", deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort()); } // siemensNet.ConnectClose(); return connected; } @Override public boolean disconnect() { siemensNet.ConnectClose(); return true; } @Override public List<ZyStationStatusEntity> getStatus(Integer deviceNo) { if (statusList == null) { BasDevpService basDevpService = SpringUtils.getBean(BasDevpService.class); if (basDevpService == null) { return Collections.emptyList(); } BasDevp basDevp = basDevpService .selectOne(new EntityWrapper<BasDevp>().eq("devp_no", deviceConfig.getDeviceNo())); if (basDevp == null) { return Collections.emptyList(); } statusList = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class); if (statusList != null) { statusList.sort(Comparator.comparing(ZyStationStatusEntity::getStationId)); } barcodeOriginList = basDevp.getBarcodeStationList$(); } if (siemensNet == null) { return statusList; } OperateResultExOne<byte[]> result = siemensNet.Read("DB100.0", (short) (statusList.size() * 10)); if (result.IsSuccess) { for (int i = 0; i < statusList.size(); i++) { ZyStationStatusEntity statusEntity = statusList.get(i); // ç«ç¹ç¼å· statusEntity.setTaskNo(siemensNet.getByteTransform().TransInt32(result.Content, i * 10)); // å·¥ä½å· statusEntity.setTargetStaNo((int) siemensNet.getByteTransform().TransInt16(result.Content, i * 10 + 4)); // ç®æ ç« boolean[] status = siemensNet.getByteTransform().TransBool(result.Content, i * 10 + 6, 1); statusEntity.setAutoing(status[0]); // èªå¨ statusEntity.setLoading(status[1]); // æç© statusEntity.setInEnable(status[2]); // å¯å ¥ statusEntity.setOutEnable(status[3]);// å¯åº statusEntity.setEmptyMk(status[4]); // 空æç statusEntity.setFullPlt(status[5]); // 满æç boolean[] status2 = siemensNet.getByteTransform().TransBool(result.Content, i * 10 + 7, 1); statusEntity.setEnableIn(status2[1]);//å¯å¨å ¥åº Integer palletHeight = null; if (status[7]) { palletHeight = 1;//ä½ } if (status2[0]) { palletHeight = 2;//ä¸ } if (status[6]) { palletHeight = 3;//é« } statusEntity.setPalletHeight(palletHeight);//é«ä½ä¿¡å· statusEntity.setError(0);//é»è®¤æ æ¥è¦ statusEntity.setTaskWriteIdx((int) siemensNet.getByteTransform().TransInt16(result.Content, i * 10 + 8));//ä»»å¡å¯ååº } } // æ¡ç æ«æå¨ OperateResultExOne<byte[]> result2 = siemensNet.Read("DB101.16", (short) (barcodeOriginList.size() * 16)); if (result2.IsSuccess) { for (int i = 0; i < barcodeOriginList.size(); i++) { ZyStationStatusEntity barcodeEntity = findStatusEntityByBarcodeIdx(i + 1); if (barcodeEntity == null) { continue; } String barcode = siemensNet.getByteTransform().TransString(result2.Content, i * 16 + 2, 14, "UTF-8"); barcode = barcode.trim(); barcodeEntity.setBarcode(barcode); } } // ç§°é OperateResultExOne<byte[]> result3 = siemensNet.Read("DB102.4", (short) (barcodeOriginList.size() * 4)); if (result3.IsSuccess) { for (int i = 0; i < barcodeOriginList.size(); i++) { ZyStationStatusEntity barcodeEntity = findStatusEntityByBarcodeIdx(i + 1); if (barcodeEntity == null) { continue; } double weight = (double) siemensNet.getByteTransform().TransSingle(result3.Content, i * 4); barcodeEntity.setWeight(weight); } } // æ¥è¦ä¿¡æ¯ OperateResultExOne<byte[]> result4 = siemensNet.Read("DB103.2", (short) (barcodeOriginList.size() * 2)); if (result4.IsSuccess) { for (int i = 0; i < barcodeOriginList.size(); i++) { ZyStationStatusEntity barcodeEntity = findStatusEntityByBarcodeIdx(i + 1); if (barcodeEntity == null) { continue; } StringBuilder sb = new StringBuilder(); boolean[] status1 = siemensNet.getByteTransform().TransBool(result4.Content, i * 2, 1); boolean[] status2 = siemensNet.getByteTransform().TransBool(result4.Content, i * 2 + 1, 1); if(status1[0]){ sb.append("å·¦è¶ å®½æ¥è¦;"); } if(status1[1]) { sb.append("å³è¶ 宽æ¥è¦;"); } if(status1[2]) { sb.append("åè¶ é¿æ¥è¦;"); } if(status1[3]) { sb.append("åè¶ é¿æ¥è¦;"); } if(status1[4]) { sb.append("è¶ é«æ¥è¦;"); } if(status1[5]) { sb.append("æè´§æ¥è¦ï¼ç©ºæå ¥åºæ¶æ£æµæç䏿æ è´§ç©;"); } if(status1[6]) { sb.append("ééå¼å¸¸æ¥è¦;"); } if(status1[7]) { sb.append("æ«ç å¼å¸¸;"); } if(sb.length() > 0) { barcodeEntity.setError(1); }else { barcodeEntity.setError(0); } barcodeEntity.setErrorMsg(sb.toString()); } } return statusList; } @Override public CommandResponse sendCommand(Integer deviceNo, StationCommand command) { CommandResponse commandResponse = new CommandResponse(false); if (null == command) { commandResponse.setMessage("å½ä»¤ä¸ºç©º"); return commandResponse; } int taskWriteIdx = getTaskWriteIdx(command.getStationId()); if (taskWriteIdx == -1) { commandResponse.setMessage("å½ä»¤ä¸åè¶ æ¶ï¼æ æ³æ¾å°å¯ç¨ä¸ååºå"); return commandResponse; } int stationIdx = findIndex(command.getStationId()); short[] data = new short[2]; data[0] = command.getStationId().shortValue(); data[1] = command.getTargetStaNo().shortValue(); OperateResult writeTaskNo = siemensNet.Write("DB13." + (stationIdx * 48 + (taskWriteIdx * 12)), command.getTaskNo()); if (!writeTaskNo.IsSuccess) { log.error("åå ¥è¾é线å½ä»¤å¤±è´¥ãç«ç¹ç¼å·={}ï¼ç«ç¹æ°æ®={}", command.getTaskNo(), JSON.toJSON(command)); commandResponse.setResult(false); commandResponse.setMessage("å½ä»¤ä¸å失败ï¼åå ¥å·¥ä½å·å¤±è´¥"); return commandResponse; } OperateResult writeData = siemensNet.Write("DB13." + (stationIdx * 48 + (taskWriteIdx * 12 + 4)), data); if (!writeData.IsSuccess) { log.error("åå ¥è¾é线å½ä»¤å¤±è´¥ãç«ç¹ç¼å·={}ï¼ç«ç¹æ°æ®={}", command.getTaskNo(), JSON.toJSON(command)); commandResponse.setResult(false); commandResponse.setMessage("å½ä»¤ä¸å失败ï¼åå ¥æ°æ®åºå失败"); return commandResponse; } log.info("åå ¥è¾é线å½ä»¤æåãä»»å¡å·={}ï¼ç«ç¹æ°æ®={}", command.getTaskNo(), JSON.toJSON(command)); commandResponse.setResult(true); return commandResponse; } @Override public synchronized CommandResponse sendOriginCommand(String address, short[] data) { CommandResponse commandResponse = new CommandResponse(false); if (null == data || data.length == 0) { commandResponse.setMessage("æ°æ®ä¸ºç©º"); return commandResponse; } OperateResult write = siemensNet.Write(address, data); if (write.IsSuccess) { log.info("åå ¥åå§å½ä»¤æåãå°å={}ï¼æ°æ®={}", address, JSON.toJSON(data)); commandResponse.setResult(true); } else { log.error("åå ¥åå§å½ä»¤å¤±è´¥ãå°å={}ï¼æ°æ®={}", address, JSON.toJSON(data)); commandResponse.setResult(false); } return commandResponse; } @Override public byte[] readOriginCommand(String address, int length) { OperateResultExOne<byte[]> result = siemensNet.Read(address, (short) length); if (result.IsSuccess) { return result.Content; } return new byte[0]; } private ZyStationStatusEntity findStatusEntityByBarcodeIdx(Integer barcodeIdx) { Integer stationId = null; for (StationObjModel stationObjModel : barcodeOriginList) { if (stationObjModel.getBarcodeIdx().equals(barcodeIdx)) { stationId = stationObjModel.getStationId(); break; } } for (ZyStationStatusEntity zyStationStatusEntity : statusList) { if(zyStationStatusEntity.getStationId().equals(stationId)) { return zyStationStatusEntity; } } return null; } private int getTaskWriteIdx(int stationId) { int useIdx = -1; int stationIdx = findIndex(stationId); if (stationIdx != -1) { ZyStationStatusEntity statusEntity = statusList.get(stationIdx); Integer taskWriteIdx = statusEntity.getTaskWriteIdx(); if (taskWriteIdx > 0) { OperateResultExOne<byte[]> resultTask = siemensNet.Read("DB13." + (stationId * 48), (short) 48); if (resultTask.IsSuccess) { int taskNo = siemensNet.getByteTransform().TransInt32(resultTask.Content, taskWriteIdx * 12); int startPoint = siemensNet.getByteTransform().TransInt16(resultTask.Content, taskWriteIdx * 12 + 4); int targetPoint = siemensNet.getByteTransform().TransInt16(resultTask.Content, taskWriteIdx * 12 + 6); if (taskNo == 0 && startPoint == 0 && targetPoint == 0) { useIdx = taskWriteIdx; } } } } return useIdx; } private int findIndex(Integer stationId) { for (int i = 0; i < statusList.size(); i++) { ZyStationStatusEntity statusEntity = statusList.get(i); if (statusEntity.getStationId().equals(stationId)) { return i; } } return -1; } } src/main/java/com/zy/core/thread/impl/ZyStationV4Thread.java
@@ -213,8 +213,25 @@ if (commandType == StationCommandType.MOVE) { if (!stationId.equals(targetStationId)) { List<Integer> path = calcPathStationIds(stationId, targetStationId); List<NavigateNode> nodes = calcPathNavigateNodes(stationId, targetStationId); List<Integer> path = new ArrayList<>(); List<Integer> liftTransferPath = new ArrayList<>(); for (NavigateNode n : nodes) { JSONObject v = JSONObject.parseObject(n.getNodeValue()); if (v == null) { continue; } Integer stationNo = v.getInteger("stationId"); if (stationNo == null) { continue; } path.add(stationNo); if (Boolean.TRUE.equals(n.getIsLiftTransferPoint())) { liftTransferPath.add(stationNo); } } stationCommand.setNavigatePath(path); stationCommand.setLiftTransferPath(liftTransferPath); } } return stationCommand; @@ -271,65 +288,88 @@ return zyStationConnectDriver.readOriginCommand(address, length); } private List<Integer> calcPathStationIds(Integer startStationId, Integer targetStationId) { private List<NavigateNode> calcPathNavigateNodes(Integer startStationId, Integer targetStationId) { NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class); if (navigateUtils == null) { return new ArrayList<>(); } List<NavigateNode> nodes = navigateUtils.calcByStationId(startStationId, targetStationId); List<Integer> ids = new ArrayList<>(); for (NavigateNode n : nodes) { JSONObject v = JSONObject.parseObject(n.getNodeValue()); if (v != null) { ids.add(v.getInteger("stationId")); } } return ids; return navigateUtils.calcByStationId(startStationId, targetStationId); } private void executeMoveWithSeg(StationCommand original) { int stationCommandSendLength = 20; Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); if (systemConfigMapObj != null) { try { HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj; String stationCommandSendLengthStr = systemConfigMap.get("stationCommandSendLength"); if(stationCommandSendLengthStr != null){ stationCommandSendLength = Integer.parseInt(stationCommandSendLengthStr); } } catch (Exception ignore) {} } if(original.getCommandType() == StationCommandType.MOVE){ List<Integer> path = JSON.parseArray(JSON.toJSONString(original.getNavigatePath(), SerializerFeature.DisableCircularReferenceDetect), Integer.class); List<Integer> liftTransferPath = JSON.parseArray(JSON.toJSONString(original.getLiftTransferPath(), SerializerFeature.DisableCircularReferenceDetect), Integer.class); if (path == null || path.isEmpty()) { return; } int total = path.size(); List<Integer> segmentTargets = new ArrayList<>(); List<Integer> segmentEndIndices = new ArrayList<>(); int idx = 0; while (idx < total) { int end = Math.min(idx + stationCommandSendLength, total) - 1; segmentTargets.add(path.get(end)); segmentEndIndices.add(end); idx = end + 1; if (liftTransferPath != null) { for (Integer liftTransferStationId : liftTransferPath) { int endIndex = path.indexOf(liftTransferStationId); // é¿å 以起ç¹ä½ä¸ºåç¹å¯¼è´ç©ºå段 if (endIndex <= 0) { continue; } if (segmentEndIndices.isEmpty() || endIndex > segmentEndIndices.get(segmentEndIndices.size() - 1)) { segmentEndIndices.add(endIndex); } } } if (segmentEndIndices.isEmpty() || segmentEndIndices.get(segmentEndIndices.size() - 1) != total - 1) { segmentEndIndices.add(total - 1); } List<StationCommand> segmentCommands = new ArrayList<>(); int buildStartIdx = 0; for (Integer endIdx : segmentEndIndices) { if (endIdx == null || endIdx < buildStartIdx) { continue; } List<Integer> segmentPath = new ArrayList<>(path.subList(buildStartIdx, endIdx + 1)); if (segmentPath.isEmpty()) { buildStartIdx = endIdx + 1; continue; } StationCommand segmentCommand = new StationCommand(); segmentCommand.setTaskNo(original.getTaskNo()); segmentCommand.setCommandType(original.getCommandType()); segmentCommand.setPalletSize(original.getPalletSize()); segmentCommand.setBarcode(original.getBarcode()); segmentCommand.setOriginalNavigatePath(path); segmentCommand.setNavigatePath(segmentPath); // æ¯æ®µå½ä»¤ï¼èµ·ç¹=å½å段é¦ç«ç¹ï¼ç»ç¹=å½å段æ«ç«ç¹ segmentCommand.setStationId(segmentPath.get(0)); segmentCommand.setTargetStaNo(segmentPath.get(segmentPath.size() - 1)); segmentCommands.add(segmentCommand); // åæ®µè¾¹çç¹éè¦åæ¶ä½ä¸ºä¸ä¸æ®µçèµ·ç¹ï¼ä¾å¦ [221,220,219] + [219,213,212]ï¼ buildStartIdx = endIdx; } if (segmentCommands.isEmpty()) { return; } int segCursor = 0; Integer currentTarget = segmentTargets.get(segCursor); Integer currentEndIdx = segmentEndIndices.get(segCursor); Integer currentStartIdx = 0; StationCommand segCmd = new StationCommand(); segCmd.setTaskNo(original.getTaskNo()); segCmd.setStationId(original.getStationId()); segCmd.setTargetStaNo(original.getTargetStaNo()); segCmd.setCommandType(original.getCommandType()); segCmd.setPalletSize(original.getPalletSize()); segCmd.setNavigatePath(new ArrayList<>(path.subList(0, currentEndIdx + 1))); sendCommand(segCmd); while (true) { CommandResponse commandResponse = sendCommand(segmentCommands.get(segCursor)); if (commandResponse == null) { try { Thread.sleep(200); } catch (Exception ignore) {} continue; } if (commandResponse.getResult()) { break; } try { Thread.sleep(200); } catch (Exception ignore) {} } long runTime = System.currentTimeMillis(); boolean firstRun = true; @@ -362,26 +402,15 @@ if (remaining <= 0) { break; } int currentSegEndIndex = path.indexOf(segmentTargets.get(segCursor)); int currentSegStartIndex = segCursor == 0 ? 0 : path.indexOf(segmentTargets.get(segCursor - 1)) + 1; int currentSegEndIndex = segmentEndIndices.get(segCursor); int currentSegStartIndex = segCursor == 0 ? 0 : segmentEndIndices.get(segCursor - 1); int segLen = currentSegEndIndex - currentSegStartIndex + 1; int remainingSegment = Math.max(0, currentSegEndIndex - currentIndex); int thresholdSegment = (int) Math.ceil(segLen * 0.3); if (remainingSegment <= thresholdSegment && segCursor < segmentTargets.size() - 1) { if (remainingSegment <= thresholdSegment && segCursor < segmentCommands.size() - 1) { segCursor++; currentEndIdx = segmentEndIndices.get(segCursor); currentStartIdx = segmentEndIndices.get(segCursor - 1) + 1; StationCommand nextCmd = new StationCommand(); nextCmd.setTaskNo(original.getTaskNo()); nextCmd.setStationId(original.getStationId()); nextCmd.setTargetStaNo(original.getTargetStaNo()); nextCmd.setCommandType(original.getCommandType()); nextCmd.setPalletSize(original.getPalletSize()); nextCmd.setNavigatePath(new ArrayList<>(path.subList(currentStartIdx, currentEndIdx + 1))); nextCmd.setOriginalNavigatePath(path); while (true) { CommandResponse commandResponse = sendCommand(nextCmd); CommandResponse commandResponse = sendCommand(segmentCommands.get(segCursor)); if (commandResponse == null) { Thread.sleep(200); continue; src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java
@@ -54,6 +54,8 @@ private CommonService commonService; @Autowired private NotifyUtils notifyUtils; @Autowired private StationOperateProcessUtils stationOperateProcessUtils; public synchronized void crnIoExecute() { Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); @@ -258,6 +260,21 @@ return false; } int stationMaxTaskCount = 30; Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); if (systemConfigMapObj != null) { try { HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj; stationMaxTaskCount = Integer.parseInt(systemConfigMap.getOrDefault("stationMaxTaskCountLimit", "30")); } catch (Exception ignore) {} } int currentStationTaskCount = stationOperateProcessUtils.getCurrentStationTaskCount(); if (stationMaxTaskCount > 0 && currentStationTaskCount >= stationMaxTaskCount) { News.warn("è¾éç«ç¹ä»»å¡æ°éè¾¾å°ä¸éï¼å·²åæ¢ä»»å¡ä¸åãå½å任塿°={}ï¼ä¸é={}", currentStationTaskCount, stationMaxTaskCount); return false; } Integer crnNo = basCrnp.getCrnNo(); List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>() src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -7,6 +7,8 @@ import com.core.common.Cools; import com.core.exception.CoolException; import com.zy.asrs.domain.enums.NotifyMsgType; import com.zy.asrs.domain.vo.StationCycleCapacityVo; import com.zy.asrs.domain.vo.StationCycleLoopVo; import com.zy.asrs.entity.*; import com.zy.asrs.service.*; import com.zy.asrs.utils.NotifyUtils; @@ -32,6 +34,7 @@ @Component public class StationOperateProcessUtils { private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120; @Autowired private BasDevpService basDevpService; @@ -51,10 +54,16 @@ private NavigateUtils navigateUtils; @Autowired private BasStationService basStationService; @Autowired private StationCycleCapacityService stationCycleCapacityService; //æ§è¡è¾éç«ç¹å ¥åºä»»å¡ public synchronized void stationInExecute() { try { DispatchLimitConfig limitConfig = getDispatchLimitConfig(); int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()}; LoadGuardState loadGuardState = buildLoadGuardState(limitConfig); List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); @@ -109,6 +118,12 @@ continue; } LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), targetStationId, loadGuardState); if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) { return; } StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0); if (command == null) { News.taskInfo(wrkMast.getWrkNo(), "{}å·¥ä½,è·åè¾é线å½ä»¤å¤±è´¥", wrkMast.getWrkNo()); @@ -124,6 +139,8 @@ MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command)); News.info("è¾éç«ç¹å ¥åºå½ä»¤ä¸åæåï¼ç«ç¹å·={}ï¼å·¥ä½å·={}ï¼å½ä»¤æ°æ®={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command)); redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5); loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); } } } @@ -136,6 +153,10 @@ //æ§è¡å åæºè¾éç«ç¹åºåºä»»å¡ public synchronized void crnStationOutExecute() { try { DispatchLimitConfig limitConfig = getDispatchLimitConfig(); int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()}; LoadGuardState loadGuardState = buildLoadGuardState(limitConfig); List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>() .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts) .isNotNull("crn_no") @@ -188,6 +209,12 @@ } } LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), moveStaNo, loadGuardState); if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) { return; } StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), moveStaNo, 0); if (command == null) { News.taskInfo(wrkMast.getWrkNo(), "è·åè¾é线å½ä»¤å¤±è´¥"); @@ -202,6 +229,9 @@ News.info("è¾éç«ç¹åºåºå½ä»¤ä¸åæåï¼ç«ç¹å·={}ï¼å·¥ä½å·={}ï¼å½ä»¤æ°æ®={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command)); redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5); redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo()); currentStationTaskCountRef[0]++; loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); } } } @@ -495,22 +525,7 @@ //è·åè¾éçº¿ä»»å¡æ°é 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; return countCurrentStationTask(); } // æ£æµåºåºæåº @@ -756,4 +771,306 @@ return seq; } private int countCurrentStationTask() { int currentStationTaskCount = 0; List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); if (stationThread == null) { continue; } for (StationProtocol stationProtocol : stationThread.getStatus()) { if (stationProtocol.getTaskNo() > 0) { currentStationTaskCount++; } } } return currentStationTaskCount; } private boolean isDispatchBlocked(DispatchLimitConfig config, int currentStationTaskCount, LoadGuardState loadGuardState, boolean needReserveLoopLoad) { if (config.loopModeEnable) { double currentLoad = loadGuardState.currentLoad(); if (currentLoad >= config.circleMaxLoadLimit) { News.warn("å½åæ¿è½½éè¾¾å°ä¸éï¼å·²åæ¢ç«ç¹ä»»å¡ä¸åãå½åæ¿è½½é={}ï¼ä¸é={}", formatPercent(currentLoad), formatPercent(config.circleMaxLoadLimit)); return true; } if (needReserveLoopLoad) { double reserveLoad = loadGuardState.loadAfterReserve(); if (reserveLoad >= config.circleMaxLoadLimit) { News.warn("é¢å åæ¿è½½éè¾¾å°ä¸éï¼å·²åæ¢ç«ç¹ä»»å¡ä¸åãé¢å åæ¿è½½é={}ï¼ä¸é={}", formatPercent(reserveLoad), formatPercent(config.circleMaxLoadLimit)); return true; } } } return false; } private LoadGuardState buildLoadGuardState(DispatchLimitConfig config) { LoadGuardState state = new LoadGuardState(); if (!config.loopModeEnable) { return state; } StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); if (capacityVo == null) { return state; } state.totalStationCount = toNonNegative(capacityVo.getTotalStationCount()); state.projectedTaskStationCount = toNonNegative(capacityVo.getTaskStationCount()); List<StationCycleLoopVo> loopList = capacityVo.getLoopList(); if (loopList != null) { for (StationCycleLoopVo loopVo : loopList) { if (loopVo == null || loopVo.getStationIdList() == null) { continue; } Integer loopNo = loopVo.getLoopNo(); for (Integer stationId : loopVo.getStationIdList()) { if (stationId != null) { if (loopNo != null) { state.stationLoopNoMap.put(stationId, loopNo); } } } } } return state; } private LoopHitResult findPathLoopHit(DispatchLimitConfig config, Integer sourceStationId, Integer targetStationId, LoadGuardState loadGuardState) { if (!config.loopModeEnable) { return LoopHitResult.NO_HIT; } if (sourceStationId == null || targetStationId == null) { return LoopHitResult.NO_HIT; } if (loadGuardState.stationLoopNoMap.isEmpty()) { return LoopHitResult.NO_HIT; } try { List<NavigateNode> nodes = navigateUtils.calcByStationId(sourceStationId, targetStationId); if (nodes == null || nodes.isEmpty()) { return LoopHitResult.NO_HIT; } for (NavigateNode node : nodes) { Integer stationId = getStationIdFromNode(node); if (stationId == null) { continue; } Integer loopNo = loadGuardState.stationLoopNoMap.get(stationId); if (loopNo != null) { return new LoopHitResult(true, loopNo, stationId); } } } catch (Exception e) { return LoopHitResult.NO_HIT; } return LoopHitResult.NO_HIT; } private Integer getStationIdFromNode(NavigateNode node) { if (node == null || isBlank(node.getNodeValue())) { return null; } try { JSONObject v = JSONObject.parseObject(node.getNodeValue()); if (v == null) { return null; } return v.getInteger("stationId"); } catch (Exception e) { return null; } } private int toNonNegative(Integer value) { if (value == null || value < 0) { return 0; } return value; } private void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) { if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) { return; } JSONObject reserveJson = new JSONObject(); reserveJson.put("wrkNo", wrkNo); reserveJson.put("loopNo", loopHitResult.getLoopNo()); reserveJson.put("hitStationId", loopHitResult.getHitStationId()); reserveJson.put("createTime", System.currentTimeMillis()); redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString()); redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS); } private DispatchLimitConfig getDispatchLimitConfig() { DispatchLimitConfig config = new DispatchLimitConfig(); Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); if (!(systemConfigMapObj instanceof Map)) { return config; } Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj; config.circleMaxLoadLimit = parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.circleMaxLoadLimit); String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable"); if (isBlank(loopModeValue)) { loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable"); } if (isBlank(loopModeValue)) { loopModeValue = getConfigValue(systemConfigMap, "isCircleMode"); } config.loopModeEnable = parseBoolean(loopModeValue, config.loopModeEnable); return config; } private String getConfigValue(Map<?, ?> configMap, String key) { Object value = configMap.get(key); if (value == null) { return null; } return String.valueOf(value).trim(); } private boolean parseBoolean(String value, boolean defaultValue) { if (isBlank(value)) { return defaultValue; } String lowValue = value.toLowerCase(Locale.ROOT); if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue) || "1".equals(lowValue) || "on".equals(lowValue)) { return true; } if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue) || "0".equals(lowValue) || "off".equals(lowValue)) { return false; } return defaultValue; } private double parseLoadLimit(String value, double defaultValue) { if (isBlank(value)) { return defaultValue; } try { String normalized = value.replace("%", "").trim(); double parsed = Double.parseDouble(normalized); if (parsed > 1.0) { parsed = parsed / 100.0; } if (parsed < 0.0) { return 0.0; } if (parsed > 1.0) { return 1.0; } return parsed; } catch (Exception e) { return defaultValue; } } private int parseInt(String value, int defaultValue) { if (isBlank(value)) { return defaultValue; } try { int parsed = Integer.parseInt(value.trim()); return parsed < 0 ? defaultValue : parsed; } catch (Exception e) { return defaultValue; } } private String formatPercent(double value) { return String.format(Locale.ROOT, "%.1f%%", value * 100.0); } private boolean isBlank(String value) { return value == null || value.trim().isEmpty(); } private static class DispatchLimitConfig { // åæå¤§æ¿è½½è½åï¼é»è®¤80% private double circleMaxLoadLimit = 0.8d; // æ¯å¦å¯ç¨ç»å模å¼ï¼ä» å¯ç¨æ¶æçææ¿è½½éå¶ï¼ private boolean loopModeEnable = false; } private static class LoadGuardState { private int totalStationCount = 0; private int projectedTaskStationCount = 0; private final Map<Integer, Integer> stationLoopNoMap = new HashMap<>(); private double currentLoad() { return calcLoad(this.projectedTaskStationCount, this.totalStationCount); } private double loadAfterReserve() { return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount); } private void reserveLoopTask(Integer loopNo) { if (loopNo == null || loopNo <= 0) { return; } if (this.totalStationCount <= 0) { return; } this.projectedTaskStationCount++; } private double calcLoad(int taskCount, int stationCount) { if (stationCount <= 0 || taskCount <= 0) { return 0.0; } double load = (double) taskCount / (double) stationCount; if (load < 0.0) { return 0.0; } if (load > 1.0) { return 1.0; } return load; } } private static class LoopHitResult { private static final LoopHitResult NO_HIT = new LoopHitResult(false, null, null); private final boolean throughLoop; private final Integer loopNo; private final Integer hitStationId; private LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) { this.throughLoop = throughLoop; this.loopNo = loopNo; this.hitStationId = hitStationId; } private boolean isThroughLoop() { return throughLoop; } private Integer getLoopNo() { return loopNo; } private Integer getHitStationId() { return hitStationId; } } } src/main/resources/application.yml
@@ -1,6 +1,6 @@ # ç³»ç»çæ¬ä¿¡æ¯ app: version: 1.0.4.4 version: 1.0.4.6 version-type: dev # prd æ dev server: src/main/resources/sql/·ÂÕæ²âÊÔ±¸·Ý_1.0.4.6.nb3Binary files differ
src/main/webapp/components/MapCanvas.js
@@ -2,6 +2,20 @@ template: ` <div style="width: 100%; height: 100%; position: relative;"> <div ref="pixiView" style="position: absolute; inset: 0;"></div> <div style="position: absolute; top: 12px; left: 14px; z-index: 30; pointer-events: none; max-width: 52%;"> <div style="display: flex; flex-direction: column; gap: 6px; align-items: flex-start;"> <div v-for="item in cycleCapacity.loopList" :key="'loop-' + item.loopNo" @mouseenter="handleLoopCardEnter(item)" @mouseleave="handleLoopCardLeave(item)" style="padding: 6px 10px; border-radius: 4px; background: rgba(11, 35, 58, 0.72); color: #fff; font-size: 12px; line-height: 1.4; white-space: nowrap; pointer-events: auto;"> å{{ item.loopNo }} | ç«ç¹: {{ item.stationCount || 0 }} | ä»»å¡: {{ item.taskCount || 0 }} | æ¿è½½: {{ formatLoadPercent(item.currentLoad) }} </div> </div> </div> <div v-show="shelfTooltip.visible" :style="shelfTooltipStyle()"> {{ shelfTooltip.text }} @@ -83,7 +97,16 @@ containerResizeObserver: null, timer: null, adjustLabelTimer: null, isSwitchingFloor: false isSwitchingFloor: false, cycleCapacity: { loopList: [], totalStationCount: 0, taskStationCount: 0, currentLoad: 0 }, hoverLoopNo: null, hoverLoopStationIdSet: new Set(), loopHighlightColor: 0xfff34d } }, mounted() { @@ -102,6 +125,7 @@ this.getCrnInfo(); this.getDualCrnInfo(); this.getSiteInfo(); this.getCycleCapacityInfo(); this.getRgvInfo(); }, 1000); }, @@ -314,6 +338,7 @@ }, changeFloor(lev) { this.currentLev = lev; this.clearLoopStationHighlight(); this.isSwitchingFloor = true; this.hideShelfTooltip(); this.hoveredShelfCell = null; @@ -338,6 +363,7 @@ this.getMap(); }, createMapData(map) { this.clearLoopStationHighlight(); this.hideShelfTooltip(); this.hoveredShelfCell = null; this.mapRowOffsets = []; @@ -652,21 +678,21 @@ sta.statusObj = null; if (sta.textObj.parent !== sta) { sta.addChild(sta.textObj); sta.textObj.position.set(sta.width / 2, sta.height / 2); } } let baseColor = 0xb8b8b8; if (status === "site-auto") { this.updateColor(sta, 0x78ff81); baseColor = 0x78ff81; } else if (status === "site-auto-run" || status === "site-auto-id" || status === "site-auto-run-id") { this.updateColor(sta, 0xfa51f6); baseColor = 0xfa51f6; } else if (status === "site-unauto") { this.updateColor(sta, 0xb8b8b8); baseColor = 0xb8b8b8; } else if (status === "machine-pakin") { this.updateColor(sta, 0x30bffc); baseColor = 0x30bffc; } else if (status === "machine-pakout") { this.updateColor(sta, 0x97b400); baseColor = 0x97b400; } else if (status === "site-run-block") { this.updateColor(sta, 0xe69138); } else { this.updateColor(sta, 0xb8b8b8); baseColor = 0xe69138; } this.setStationBaseColor(sta, baseColor); }); }, getCrnInfo() { @@ -684,6 +710,38 @@ getRgvInfo() { if (this.isSwitchingFloor) { return; } this.sendWs(JSON.stringify({ url: "/console/latest/data/rgv", data: {} })); }, getCycleCapacityInfo() { if (this.isSwitchingFloor) { return; } this.sendWs(JSON.stringify({ url: "/console/latest/data/station/cycle/capacity", data: {} })); }, setCycleCapacityInfo(res) { const payload = res && res.code === 200 ? res.data : null; if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; return; } if (!payload) { return; } const loopList = Array.isArray(payload.loopList) ? payload.loopList : []; this.cycleCapacity = { loopList: loopList, totalStationCount: payload.totalStationCount || 0, taskStationCount: payload.taskStationCount || 0, currentLoad: typeof payload.currentLoad === 'number' ? payload.currentLoad : parseFloat(payload.currentLoad || 0) }; if (this.hoverLoopNo != null) { const targetLoop = loopList.find(v => v && v.loopNo === this.hoverLoopNo); if (targetLoop) { this.hoverLoopStationIdSet = this.buildStationIdSet(targetLoop.stationIdList); this.applyLoopStationHighlight(); } else { this.clearLoopStationHighlight(); } } }, formatLoadPercent(load) { let value = typeof load === 'number' ? load : parseFloat(load || 0); if (!isFinite(value)) { value = 0; } if (value < 0) { value = 0; } if (value > 1) { value = 1; } return (value * 100).toFixed(1) + "%"; }, setCrnInfo(res) { let crns = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null); @@ -839,6 +897,7 @@ if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; } this.wsReconnectAttempts = 0; this.getMap(this.currentLev); this.getCycleCapacityInfo(); }, webSocketOnError(e) { this.scheduleReconnect(); @@ -853,6 +912,8 @@ this.setDualCrnInfo(JSON.parse(result.data)); } else if (result.url === "/console/latest/data/rgv") { this.setRgvInfo(JSON.parse(result.data)); } else if (result.url === "/console/latest/data/station/cycle/capacity") { this.setCycleCapacityInfo(JSON.parse(result.data)); } else if (typeof result.url === "string" && result.url.indexOf("/basMap/lev/") === 0) { this.setMap(JSON.parse(result.data)); } @@ -1200,7 +1261,11 @@ text.position.set(sprite.width / 2, sprite.height / 2); sprite.addChild(text); sprite.textObj = text; if (siteId != null && siteId !== -1) { this.pixiStaMap.set(parseInt(siteId), sprite); } const stationIdInt = parseInt(siteId, 10); if (!isNaN(stationIdInt)) { this.pixiStaMap.set(stationIdInt, sprite); } sprite._stationId = isNaN(stationIdInt) ? null : stationIdInt; sprite._baseColor = 0x00ff7f; sprite._loopHighlighted = false; sprite.interactive = true; sprite.buttonMode = true; sprite.on('pointerdown', () => { @@ -1401,6 +1466,74 @@ return; } sprite.tint = color; }, setStationBaseColor(sprite, color) { if (!sprite) { return; } sprite._baseColor = color; if (this.isStationInHoverLoop(sprite)) { this.applyHighlightColor(sprite); } else { this.updateColor(sprite, color); sprite._loopHighlighted = false; } }, applyHighlightColor(sprite) { if (!sprite) { return; } this.updateColor(sprite, this.loopHighlightColor); sprite._loopHighlighted = true; }, isStationInHoverLoop(sprite) { if (!sprite || sprite._stationId == null || !this.hoverLoopStationIdSet) { return false; } return this.hoverLoopStationIdSet.has(sprite._stationId); }, buildStationIdSet(stationIdList) { const set = new Set(); if (!Array.isArray(stationIdList)) { return set; } stationIdList.forEach((id) => { const v = parseInt(id, 10); if (!isNaN(v)) { set.add(v); } }); return set; }, applyLoopStationHighlight() { if (!this.pixiStaMap) { return; } this.pixiStaMap.forEach((sprite) => { if (!sprite) { return; } if (this.isStationInHoverLoop(sprite)) { this.applyHighlightColor(sprite); } else if (sprite._loopHighlighted) { const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8; this.updateColor(sprite, baseColor); sprite._loopHighlighted = false; } }); }, clearLoopStationHighlight() { if (this.pixiStaMap) { this.pixiStaMap.forEach((sprite) => { if (!sprite || !sprite._loopHighlighted) { return; } const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8; this.updateColor(sprite, baseColor); sprite._loopHighlighted = false; }); } this.hoverLoopNo = null; this.hoverLoopStationIdSet = new Set(); }, handleLoopCardEnter(loopItem) { if (!loopItem) { return; } this.hoverLoopNo = loopItem.loopNo; this.hoverLoopStationIdSet = this.buildStationIdSet(loopItem.stationIdList); this.applyLoopStationHighlight(); }, handleLoopCardLeave(loopItem) { if (!loopItem) { this.clearLoopStationHighlight(); return; } if (this.hoverLoopNo === loopItem.loopNo) { this.clearLoopStationHighlight(); } }, isJson(str) { try { JSON.parse(str); return true; } catch (e) { return false; } @@ -1883,11 +2016,6 @@ } } });