| | |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.core.common.SpringUtils; |
| | | import com.zy.asrs.entity.DeviceConfig; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.network.api.ZyStationConnectApi; |
| | | import com.zy.core.network.entity.ZyStationStatusEntity; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.Arrays; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Random; |
| | | import java.util.concurrent.BlockingQueue; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.CopyOnWriteArrayList; |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.BlockingQueue; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.locks.ReentrantLock; |
| | | import java.util.Map; |
| | | import java.util.Arrays; |
| | | |
| | | public class ZyStationFakeSegConnect implements ZyStationConnectApi { |
| | | private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L; |
| | | |
| | | // 站点级锁:每个站点独立一把锁,提升并发性能 |
| | | private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<>(); |
| | | private HashMap<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new HashMap<>(); |
| | | private HashMap<Integer, DeviceConfig> deviceConfigMap = new HashMap<>(); |
| | | private RedisUtil redisUtil; |
| | | private final Map<Integer, BlockingQueue<StationCommand>> taskQueues = new ConcurrentHashMap<>(); |
| | | private final Map<Integer, Long> taskLastUpdateTime = new ConcurrentHashMap<>(); |
| | | private final Map<Integer, Boolean> taskRunning = new ConcurrentHashMap<>(); |
| | | private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L; |
| | | private static final long WAIT_SEGMENT_TIMEOUT_MS = 30000L; |
| | | |
| | | private static final String STATUS_WAITING = "WAITING"; |
| | | private static final String STATUS_RUNNING = "RUNNING"; |
| | | private static final String STATUS_BLOCKED = "BLOCKED"; |
| | | private static final String STATUS_CANCELLED = "CANCELLED"; |
| | | private static final String STATUS_TIMEOUT = "TIMEOUT"; |
| | | private static final String STATUS_FINISHED = "FINISHED"; |
| | | |
| | | private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<Integer, ReentrantLock>(); |
| | | private final Map<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new ConcurrentHashMap<Integer, List<ZyStationStatusEntity>>(); |
| | | private final Map<Integer, DeviceConfig> deviceConfigMap = new ConcurrentHashMap<Integer, DeviceConfig>(); |
| | | private final Map<Integer, BlockingQueue<StationCommand>> taskQueues = new ConcurrentHashMap<Integer, BlockingQueue<StationCommand>>(); |
| | | private final Map<Integer, Long> taskLastUpdateTime = new ConcurrentHashMap<Integer, Long>(); |
| | | private final Map<Integer, Boolean> taskRunning = new ConcurrentHashMap<Integer, Boolean>(); |
| | | private final ExecutorService executor = Executors.newCachedThreadPool(); |
| | | |
| | | private RedisUtil redisUtil; |
| | | |
| | | public void addFakeConnect(DeviceConfig deviceConfig, RedisUtil redisUtil) { |
| | | this.redisUtil = redisUtil; |
| | | |
| | | if (deviceConfigMap.containsKey(deviceConfig.getDeviceNo())) { |
| | | return; |
| | | } |
| | | deviceConfigMap.put(deviceConfig.getDeviceNo(), deviceConfig); |
| | | deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<>()); |
| | | deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<ZyStationStatusEntity>()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | public List<ZyStationStatusEntity> getStatus(Integer deviceNo) { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return new ArrayList<>(); |
| | | return new ArrayList<ZyStationStatusEntity>(); |
| | | } |
| | | |
| | | DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo); |
| | | if (statusList.isEmpty()) { |
| | | if (statusList.isEmpty() && deviceConfig != null) { |
| | | List<ZyStationStatusEntity> init = JSON.parseArray(deviceConfig.getFakeInitStatus(), |
| | | ZyStationStatusEntity.class); |
| | | if (init != null) { |
| | |
| | | return new CommandResponse(false, "任务号为空"); |
| | | } |
| | | |
| | | // 处理非移动命令 |
| | | if (command.getCommandType() != StationCommandType.MOVE) { |
| | | handleCommand(deviceNo, command); |
| | | } else { |
| | | // 将移动命令追加到任务队列(支持分段下发) |
| | | taskQueues.computeIfAbsent(taskNo, k -> new LinkedBlockingQueue<>()).offer(command); |
| | | taskLastUpdateTime.put(taskNo, System.currentTimeMillis()); |
| | | return new CommandResponse(true, "命令已受理(异步执行)"); |
| | | } |
| | | |
| | | // 只有任务未启动时才启动执行器,后续分段命令仅追加到队列 |
| | | if (taskRunning.putIfAbsent(taskNo, true) == null) { |
| | | executor.submit(() -> runTaskLoop(deviceNo, taskNo)); |
| | | } |
| | | // 后续分段命令不再返回错误,正常追加到队列 |
| | | if (isDirectMoveCommand(command)) { |
| | | handleDirectMoveCommand(deviceNo, command); |
| | | return new CommandResponse(true, "命令已受理(异步执行)"); |
| | | } |
| | | |
| | | taskQueues.computeIfAbsent(taskNo, key -> new LinkedBlockingQueue<StationCommand>()).offer(command); |
| | | taskLastUpdateTime.put(taskNo, System.currentTimeMillis()); |
| | | |
| | | if (taskRunning.putIfAbsent(taskNo, true) == null) { |
| | | executor.submit(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | runTaskLoop(deviceNo, taskNo); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | return new CommandResponse(true, "命令已受理(异步执行)"); |
| | | } |
| | | |
| | | private void runTaskLoop(Integer deviceNo, Integer taskNo) { |
| | | try { |
| | | // 待执行的路径队列(存储站点ID序列) |
| | | LinkedBlockingQueue<Integer> pendingPathQueue = new LinkedBlockingQueue<>(); |
| | | // 当前所在站点ID |
| | | Integer currentStationId = null; |
| | | // 最终目标站点ID |
| | | Integer finalTargetStationId = null; |
| | | // 是否需要生成条码 |
| | | boolean generateBarcode = false; |
| | | // 是否已初始化起点 |
| | | boolean initialized = false; |
| | | // 上一步执行时间(用于堵塞检测) |
| | | long stepExecuteTime = System.currentTimeMillis(); |
| | | long runBlockTimeoutMs = getFakeRunBlockTimeoutMs(); |
| | | // 仅在每次到达目标时执行一次到位处理,避免重复生成条码 |
| | | boolean arrivalHandled = false; |
| | | @Override |
| | | public CommandResponse sendOriginCommand(String address, short[] data) { |
| | | return new CommandResponse(true, "原始命令已受理(异步执行)"); |
| | | } |
| | | |
| | | @Override |
| | | public byte[] readOriginCommand(String address, int length) { |
| | | return new byte[0]; |
| | | } |
| | | |
| | | private boolean isDirectMoveCommand(StationCommand command) { |
| | | if (command == null || command.getCommandType() != StationCommandType.MOVE) { |
| | | return false; |
| | | } |
| | | List<Integer> path = command.getNavigatePath(); |
| | | if (command.getStationId() == null || !command.getStationId().equals(command.getTargetStaNo())) { |
| | | return false; |
| | | } |
| | | if (path == null || path.isEmpty()) { |
| | | return true; |
| | | } |
| | | return path.size() == 1 && command.getStationId().equals(path.get(0)); |
| | | } |
| | | |
| | | private void handleDirectMoveCommand(Integer deviceNo, StationCommand command) { |
| | | Integer taskNo = command.getTaskNo(); |
| | | Integer stationId = command.getStationId(); |
| | | Integer targetStationId = command.getTargetStaNo(); |
| | | if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null |
| | | && stationId.equals(targetStationId)) { |
| | | generateStationData(deviceNo, taskNo, stationId, targetStationId); |
| | | } |
| | | |
| | | TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo)); |
| | | context.startStationId = stationId; |
| | | context.currentStationId = stationId; |
| | | context.finalTargetStationId = targetStationId; |
| | | context.initialized = true; |
| | | context.status = STATUS_RUNNING; |
| | | context.appendStitchedPath(Arrays.asList(stationId)); |
| | | context.addPassedStation(stationId); |
| | | context.generateBarcode = checkTaskNoInArea(taskNo); |
| | | |
| | | traceEvent(deviceNo, context, "MOVE_INIT", "同站点任务直接到位", buildDetails("stationId", stationId), false); |
| | | if (context.generateBarcode) { |
| | | generateStationBarcode(taskNo, stationId, deviceNo); |
| | | } |
| | | traceEvent(deviceNo, context, "ARRIVED", "任务已在起点站点完成", buildDetails("barcodeGenerated", |
| | | context.generateBarcode, "stationId", stationId), false); |
| | | context.status = STATUS_FINISHED; |
| | | traceEvent(deviceNo, context, "TASK_END", "任务执行完成", buildDetails("reason", STATUS_FINISHED), true); |
| | | } |
| | | |
| | | private void runTaskLoop(Integer deviceNo, Integer taskNo) { |
| | | TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo)); |
| | | try { |
| | | while (true) { |
| | | if (Thread.currentThread().isInterrupted()) { |
| | | if (!isTerminalStatus(context.status)) { |
| | | context.status = STATUS_CANCELLED; |
| | | } |
| | | break; |
| | | } |
| | | |
| | | if (hasTaskReset(taskNo)) { |
| | | context.status = STATUS_CANCELLED; |
| | | break; |
| | | } |
| | | |
| | |
| | | break; |
| | | } |
| | | |
| | | // 尝试获取新的分段命令 |
| | | StationCommand command = commandQueue.poll(100, TimeUnit.MILLISECONDS); |
| | | if (command != null) { |
| | | taskLastUpdateTime.put(taskNo, System.currentTimeMillis()); |
| | | List<Integer> newPath = command.getNavigatePath(); |
| | | Integer lastInQueue = getLastInQueue(pendingPathQueue); |
| | | int startIndex = getPathAppendStartIndex(newPath, currentStationId, lastInQueue); |
| | | |
| | | if (newPath != null && !newPath.isEmpty() && startIndex < 0) { |
| | | News.info("[WCS Debug] 任务{}忽略无法衔接的旧路径段: {}, 当前位置: {}, 队列尾: {}", taskNo, |
| | | newPath, currentStationId, lastInQueue); |
| | | continue; |
| | | } |
| | | |
| | | // 每次接收命令都刷新目标,避免沿用旧目标导致状态抖动 |
| | | Integer commandTargetStationId = command.getTargetStaNo(); |
| | | if (commandTargetStationId != null) { |
| | | if (!commandTargetStationId.equals(finalTargetStationId)) { |
| | | arrivalHandled = false; |
| | | News.info("[WCS Debug] 任务{}切换目标: {} -> {}", taskNo, finalTargetStationId, |
| | | commandTargetStationId); |
| | | } |
| | | finalTargetStationId = commandTargetStationId; |
| | | // 当前站点先同步最新目标,避免上层在窗口期重复下发同一路径 |
| | | syncCurrentStationTarget(taskNo, currentStationId, finalTargetStationId); |
| | | } |
| | | |
| | | if (!generateBarcode && checkTaskNoInArea(taskNo)) { |
| | | generateBarcode = true; |
| | | } |
| | | |
| | | // 将新路径追加到待执行队列 |
| | | if (newPath != null && !newPath.isEmpty()) { |
| | | for (int i = startIndex; i < newPath.size(); i++) { |
| | | pendingPathQueue.offer(newPath.get(i)); |
| | | } |
| | | |
| | | News.info("[WCS Debug] 任务{}追加路径段: {} -> 队列大小: {}", taskNo, newPath, pendingPathQueue.size()); |
| | | } |
| | | context.lastCommandAt = System.currentTimeMillis(); |
| | | handleIncomingSegment(deviceNo, context, command); |
| | | } |
| | | |
| | | // 执行移动逻辑 |
| | | if (!pendingPathQueue.isEmpty()) { |
| | | Integer nextStationId = pendingPathQueue.peek(); |
| | | |
| | | // 如果尚未初始化起点 |
| | | if (!initialized && currentStationId == null) { |
| | | // 优先查找托盘当前实际位置(支持堵塞后重路由场景) |
| | | Integer actualCurrentStationId = findCurrentStationIdByTask(taskNo); |
| | | if (actualCurrentStationId != null) { |
| | | // 找到了当前托盘位置,使用实际位置作为起点 |
| | | currentStationId = actualCurrentStationId; |
| | | initialized = true; |
| | | |
| | | // 清除该站点的 runBlock 标记(堵塞恢复) |
| | | Integer deviceId = getDeviceNoByStationId(currentStationId); |
| | | if (deviceId != null) { |
| | | clearRunBlock(currentStationId, deviceId); |
| | | } |
| | | |
| | | // 如果路径起点与当前位置相同,移除起点避免重复 |
| | | if (nextStationId.equals(currentStationId)) { |
| | | pendingPathQueue.poll(); |
| | | } |
| | | |
| | | stepExecuteTime = System.currentTimeMillis(); |
| | | News.info("[WCS Debug] 任务{}恢复执行,当前位置: {}", taskNo, currentStationId); |
| | | continue; |
| | | } |
| | | |
| | | // 未找到当前位置(首次执行),首个站点就是起点 |
| | | currentStationId = nextStationId; |
| | | Integer deviceId = getDeviceNoByStationId(currentStationId); |
| | | if (deviceId != null) { |
| | | boolean result = initStationMove(taskNo, currentStationId, deviceId, taskNo, |
| | | finalTargetStationId, true, null); |
| | | if (result) { |
| | | initialized = true; |
| | | pendingPathQueue.poll(); // 移除起点 |
| | | stepExecuteTime = System.currentTimeMillis(); |
| | | News.info("[WCS Debug] 任务{}初始化起点: {}", taskNo, currentStationId); |
| | | } |
| | | } |
| | | sleep(500); |
| | | if (!context.pendingPathQueue.isEmpty()) { |
| | | if (!context.initialized || context.currentStationId == null) { |
| | | initializeTaskPosition(deviceNo, context); |
| | | continue; |
| | | } |
| | | |
| | | // 执行从当前站点到下一站点的移动 |
| | | Integer currentDeviceNo = getDeviceNoByStationId(currentStationId); |
| | | Integer nextDeviceNo = getDeviceNoByStationId(nextStationId); |
| | | |
| | | if (currentDeviceNo != null && nextDeviceNo != null) { |
| | | boolean moveSuccess = stationMoveToNext(taskNo, currentStationId, currentDeviceNo, |
| | | nextStationId, nextDeviceNo, taskNo, finalTargetStationId); |
| | | if (moveSuccess) { |
| | | currentStationId = nextStationId; |
| | | pendingPathQueue.poll(); |
| | | stepExecuteTime = System.currentTimeMillis(); |
| | | arrivalHandled = false; |
| | | News.info("[WCS Debug] 任务{}移动到站点: {}, 剩余队列: {}", taskNo, currentStationId, |
| | | pendingPathQueue.size()); |
| | | sleep(1000); // 模拟移动耗时 |
| | | } else { |
| | | // 移动失败,检查是否堵塞 |
| | | if (!checkTaskNoInArea(taskNo)) { |
| | | boolean fakeAllowCheckBlock = getFakeAllowCheckBlock(); |
| | | |
| | | if (fakeAllowCheckBlock |
| | | && System.currentTimeMillis() - stepExecuteTime > runBlockTimeoutMs) { |
| | | // 认定堵塞 |
| | | boolean result = runBlockStation(taskNo, currentStationId, currentDeviceNo, taskNo, |
| | | currentStationId); |
| | | if (result) { |
| | | News.info("[WCS Debug] 任务{}在站点{}被标记为堵塞", taskNo, currentStationId); |
| | | pendingPathQueue.clear(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | sleep(500); // 失败重试等待 |
| | | } |
| | | } else { |
| | | // 无法获取设备号,跳过该站点 |
| | | pendingPathQueue.poll(); |
| | | } |
| | | } else { |
| | | // 路径队列为空,等待新的分段命令 |
| | | if (currentStationId != null && finalTargetStationId != null |
| | | && currentStationId.equals(finalTargetStationId)) { |
| | | // 已到达当前目标后继续等待下一条分段命令,避免排序/绕圈场景吞掉后续命令 |
| | | if (!arrivalHandled) { |
| | | if (generateBarcode) { |
| | | Integer targetDeviceNo = getDeviceNoByStationId(finalTargetStationId); |
| | | if (targetDeviceNo != null) { |
| | | generateStationBarcode(taskNo, finalTargetStationId, targetDeviceNo); |
| | | News.info("[WCS Debug] 任务{}到达目标{}并生成条码", taskNo, finalTargetStationId); |
| | | } |
| | | } |
| | | arrivalHandled = true; |
| | | } |
| | | } |
| | | |
| | | // 继续等待新的分段命令 |
| | | Long lastTime = taskLastUpdateTime.get(taskNo); |
| | | if (lastTime != null && System.currentTimeMillis() - lastTime > 30000) { |
| | | // 超时:30秒内没有收到新分段命令 |
| | | News.info("[WCS Debug] 任务{}等待分段超时,当前位置: {}, 目标: {}", taskNo, currentStationId, |
| | | finalTargetStationId); |
| | | if (!executeNextMove(deviceNo, context)) { |
| | | break; |
| | | } |
| | | // 继续等待新分段命令(不做任何事情,下一轮循环会尝试获取新命令) |
| | | continue; |
| | | } |
| | | |
| | | if (handleIdleState(deviceNo, context)) { |
| | | break; |
| | | } |
| | | } |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | if (!isTerminalStatus(context.status)) { |
| | | context.status = STATUS_CANCELLED; |
| | | } |
| | | } finally { |
| | | taskQueues.remove(taskNo); |
| | | taskLastUpdateTime.remove(taskNo); |
| | | taskRunning.remove(taskNo); |
| | | News.info("[WCS Debug] 任务{}执行结束并清理资源", taskNo); |
| | | |
| | | if (!isTerminalStatus(context.status)) { |
| | | context.status = STATUS_FINISHED; |
| | | } |
| | | traceEvent(deviceNo, context, "TASK_END", "任务执行结束并清理资源", |
| | | buildDetails("reason", context.status), true); |
| | | News.info("[WCS Debug] 任务{}执行结束并清理资源,状态={}", taskNo, context.status); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取队列中最后一个元素(不移除) |
| | | */ |
| | | private void handleIncomingSegment(Integer deviceNo, TaskRuntimeContext context, StationCommand command) { |
| | | List<Integer> newPath = normalizePath(command.getNavigatePath()); |
| | | Integer lastInQueue = getLastInQueue(context.pendingPathQueue); |
| | | int startIndex = getPathAppendStartIndex(newPath, context.currentStationId, lastInQueue); |
| | | |
| | | context.setStartStationIdIfAbsent(command.getStationId()); |
| | | if (!context.generateBarcode && checkTaskNoInArea(context.taskNo)) { |
| | | context.generateBarcode = true; |
| | | } |
| | | |
| | | traceEvent(deviceNo, context, "SEGMENT_RECEIVED", "收到新的路径分段命令", |
| | | buildDetails("segmentPath", newPath, "appendStartIndex", startIndex, "currentStationId", |
| | | context.currentStationId, "queueTailStationId", lastInQueue, "commandStationId", |
| | | command.getStationId(), "commandTargetStationId", command.getTargetStaNo()), |
| | | false); |
| | | |
| | | Integer commandTargetStationId = command.getTargetStaNo(); |
| | | if (commandTargetStationId != null) { |
| | | if (!commandTargetStationId.equals(context.finalTargetStationId)) { |
| | | traceEvent(deviceNo, context, "TARGET_SWITCHED", |
| | | "任务目标站发生切换: " + context.finalTargetStationId + " -> " + commandTargetStationId, |
| | | buildDetails("fromTargetStationId", context.finalTargetStationId, "toTargetStationId", |
| | | commandTargetStationId), |
| | | false); |
| | | context.arrivalHandled = false; |
| | | } |
| | | context.finalTargetStationId = commandTargetStationId; |
| | | syncCurrentStationTarget(context.taskNo, context.currentStationId, context.finalTargetStationId); |
| | | } |
| | | |
| | | if (!newPath.isEmpty() && startIndex < 0) { |
| | | traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法与当前运行上下文衔接,已忽略", |
| | | buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId, |
| | | "queueTailStationId", lastInQueue, "ignoreReason", "PATH_NOT_CONNECTED"), |
| | | false); |
| | | context.latestAppendedPath.clear(); |
| | | return; |
| | | } |
| | | |
| | | List<Integer> appendedPath = new ArrayList<Integer>(); |
| | | for (int i = startIndex; i < newPath.size(); i++) { |
| | | Integer stationId = newPath.get(i); |
| | | context.pendingPathQueue.offer(stationId); |
| | | appendedPath.add(stationId); |
| | | } |
| | | context.appendStitchedPath(appendedPath); |
| | | |
| | | if (!appendedPath.isEmpty()) { |
| | | traceEvent(deviceNo, context, "SEGMENT_APPENDED", |
| | | "路径分段已追加到待执行队列,队列长度=" + context.pendingPathQueue.size(), |
| | | buildDetails("segmentPath", newPath, "appendedPath", appendedPath, "appendStartIndex", |
| | | startIndex, "queueSize", context.pendingPathQueue.size()), |
| | | false); |
| | | } |
| | | } |
| | | |
| | | private void initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) { |
| | | Integer nextStationId = context.pendingPathQueue.peek(); |
| | | if (nextStationId == null) { |
| | | return; |
| | | } |
| | | |
| | | if (context.currentStationId == null) { |
| | | Integer actualCurrentStationId = findCurrentStationIdByTask(context.taskNo); |
| | | if (actualCurrentStationId != null) { |
| | | context.currentStationId = actualCurrentStationId; |
| | | context.initialized = true; |
| | | context.status = STATUS_RUNNING; |
| | | context.blockedStationId = null; |
| | | |
| | | Integer actualDeviceNo = getDeviceNoByStationId(actualCurrentStationId); |
| | | if (actualDeviceNo != null) { |
| | | clearRunBlock(actualCurrentStationId, actualDeviceNo); |
| | | } |
| | | |
| | | trimPendingPathToCurrent(context.pendingPathQueue, actualCurrentStationId); |
| | | if (actualCurrentStationId.equals(context.pendingPathQueue.peek())) { |
| | | context.pendingPathQueue.poll(); |
| | | } |
| | | |
| | | context.addPassedStation(actualCurrentStationId); |
| | | context.lastStepAt = System.currentTimeMillis(); |
| | | traceEvent(deviceNo, context, "MOVE_INIT", "任务从当前实际站点恢复执行", |
| | | buildDetails("stationId", actualCurrentStationId, "recovered", true), false); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | context.currentStationId = nextStationId; |
| | | Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId); |
| | | if (currentDeviceNo == null) { |
| | | context.pendingPathQueue.poll(); |
| | | return; |
| | | } |
| | | |
| | | boolean result = initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo, |
| | | context.finalTargetStationId, true, null); |
| | | if (!result) { |
| | | sleep(200); |
| | | return; |
| | | } |
| | | |
| | | context.initialized = true; |
| | | context.status = STATUS_RUNNING; |
| | | context.pendingPathQueue.poll(); |
| | | context.addPassedStation(context.currentStationId); |
| | | context.lastStepAt = System.currentTimeMillis(); |
| | | traceEvent(deviceNo, context, "MOVE_INIT", "任务初始化起点站点", |
| | | buildDetails("stationId", context.currentStationId, "recovered", false), false); |
| | | sleep(500); |
| | | } |
| | | |
| | | private boolean executeNextMove(Integer deviceNo, TaskRuntimeContext context) { |
| | | Integer nextStationId = context.pendingPathQueue.peek(); |
| | | if (nextStationId == null || context.currentStationId == null) { |
| | | return true; |
| | | } |
| | | |
| | | Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId); |
| | | Integer nextDeviceNo = getDeviceNoByStationId(nextStationId); |
| | | if (currentDeviceNo == null || nextDeviceNo == null) { |
| | | context.pendingPathQueue.poll(); |
| | | return true; |
| | | } |
| | | |
| | | boolean moveSuccess = stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo, |
| | | nextStationId, nextDeviceNo, context.taskNo, context.finalTargetStationId); |
| | | if (moveSuccess) { |
| | | Integer previousStationId = context.currentStationId; |
| | | context.currentStationId = nextStationId; |
| | | context.pendingPathQueue.poll(); |
| | | context.addPassedStation(nextStationId); |
| | | context.arrivalHandled = false; |
| | | context.blockedStationId = null; |
| | | context.status = STATUS_RUNNING; |
| | | context.lastStepAt = System.currentTimeMillis(); |
| | | traceEvent(deviceNo, context, "MOVE_STEP_OK", "任务完成一步站点移动", |
| | | buildDetails("fromStationId", previousStationId, "toStationId", nextStationId, |
| | | "remainingPendingPath", context.getPendingStationIds()), |
| | | false); |
| | | sleep(1000); |
| | | return true; |
| | | } |
| | | |
| | | if (!checkTaskNoInArea(context.taskNo) && getFakeAllowCheckBlock() |
| | | && System.currentTimeMillis() - context.lastStepAt > getFakeRunBlockTimeoutMs()) { |
| | | boolean blocked = runBlockStation(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo, |
| | | context.currentStationId); |
| | | if (blocked) { |
| | | context.blockedStationId = context.currentStationId; |
| | | context.status = STATUS_BLOCKED; |
| | | context.pendingPathQueue.clear(); |
| | | traceEvent(deviceNo, context, "RUN_BLOCKED", "任务在当前站点被标记为堵塞", |
| | | buildDetails("blockedStationId", context.currentStationId), false); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | sleep(500); |
| | | return true; |
| | | } |
| | | |
| | | private boolean handleIdleState(Integer deviceNo, TaskRuntimeContext context) { |
| | | if (context.currentStationId != null && context.finalTargetStationId != null |
| | | && context.currentStationId.equals(context.finalTargetStationId)) { |
| | | if (!context.arrivalHandled) { |
| | | boolean barcodeGenerated = false; |
| | | if (context.generateBarcode) { |
| | | Integer targetDeviceNo = getDeviceNoByStationId(context.finalTargetStationId); |
| | | if (targetDeviceNo != null) { |
| | | barcodeGenerated = generateStationBarcode(context.taskNo, context.finalTargetStationId, |
| | | targetDeviceNo); |
| | | } |
| | | } |
| | | context.arrivalHandled = true; |
| | | traceEvent(deviceNo, context, "ARRIVED", "任务到达最终目标站点", |
| | | buildDetails("stationId", context.currentStationId, "barcodeGenerated", barcodeGenerated), |
| | | false); |
| | | } |
| | | context.status = STATUS_FINISHED; |
| | | return true; |
| | | } |
| | | |
| | | Long lastTime = taskLastUpdateTime.get(context.taskNo); |
| | | if (lastTime != null && System.currentTimeMillis() - lastTime > WAIT_SEGMENT_TIMEOUT_MS) { |
| | | context.status = STATUS_TIMEOUT; |
| | | traceEvent(deviceNo, context, "WAIT_TIMEOUT", "等待新的路径分段超时", |
| | | buildDetails("timeoutMs", WAIT_SEGMENT_TIMEOUT_MS, "currentStationId", context.currentStationId, |
| | | "targetStationId", context.finalTargetStationId), |
| | | false); |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private List<Integer> normalizePath(List<Integer> path) { |
| | | List<Integer> result = new ArrayList<Integer>(); |
| | | if (path == null) { |
| | | return result; |
| | | } |
| | | for (Integer stationId : path) { |
| | | if (stationId != null) { |
| | | result.add(stationId); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private void trimPendingPathToCurrent(LinkedBlockingQueue<Integer> queue, Integer currentStationId) { |
| | | if (queue == null || currentStationId == null || queue.isEmpty()) { |
| | | return; |
| | | } |
| | | List<Integer> snapshot = new ArrayList<Integer>(queue); |
| | | int index = snapshot.indexOf(currentStationId); |
| | | if (index <= 0) { |
| | | return; |
| | | } |
| | | for (int i = 0; i < index; i++) { |
| | | queue.poll(); |
| | | } |
| | | } |
| | | |
| | | private boolean hasTaskReset(Integer taskNo) { |
| | | if (redisUtil == null || taskNo == null) { |
| | | return false; |
| | | } |
| | | Object cancel = redisUtil.get(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo); |
| | | return cancel != null; |
| | | } |
| | | |
| | | private Integer getLastInQueue(LinkedBlockingQueue<Integer> queue) { |
| | | Integer last = null; |
| | | for (Integer item : queue) { |
| | |
| | | return last; |
| | | } |
| | | |
| | | /** |
| | | * 计算新路径在队列中的追加起点,避免重复下发导致路径来回跳 |
| | | */ |
| | | private int getPathAppendStartIndex(List<Integer> newPath, Integer currentStationId, Integer lastInQueue) { |
| | | if (newPath == null || newPath.isEmpty()) { |
| | | return 0; |
| | |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * 命令刚到达时同步当前站点目标,降低上层重复发同一路径的概率 |
| | | */ |
| | | private void syncCurrentStationTarget(Integer taskNo, Integer currentStationId, Integer targetStationId) { |
| | | if (currentStationId == null || targetStationId == null) { |
| | | return; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取是否允许检查堵塞的配置 |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | private boolean getFakeAllowCheckBlock() { |
| | | boolean fakeAllowCheckBlock = true; |
| | | Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); |
| | | if (systemConfigMapObj != null) { |
| | | HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj; |
| | | Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); |
| | | if (systemConfigMapObj instanceof Map) { |
| | | Map<String, String> systemConfigMap = (Map<String, String>) systemConfigMapObj; |
| | | String value = systemConfigMap.get("fakeAllowCheckBlock"); |
| | | if (value != null && !value.equals("Y")) { |
| | | if (value != null && !"Y".equals(value)) { |
| | | fakeAllowCheckBlock = false; |
| | | } |
| | | } |
| | |
| | | |
| | | private long getFakeRunBlockTimeoutMs() { |
| | | long timeoutMs = DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS; |
| | | Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); |
| | | Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); |
| | | if (systemConfigMapObj instanceof Map) { |
| | | Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj; |
| | | Object value = systemConfigMap.get("fakeRunBlockTimeoutMs"); |
| | |
| | | return timeoutMs; |
| | | } |
| | | |
| | | @Override |
| | | public CommandResponse sendOriginCommand(String address, short[] data) { |
| | | return new CommandResponse(true, "原始命令已受理(异步执行)"); |
| | | } |
| | | |
| | | @Override |
| | | public byte[] readOriginCommand(String address, int length) { |
| | | return new byte[0]; |
| | | } |
| | | |
| | | private void handleCommand(Integer deviceNo, StationCommand command) { |
| | | News.info("[WCS Debug] 站点仿真模拟(V3)已启动,命令数据={}", JSON.toJSONString(command)); |
| | | Integer taskNo = command.getTaskNo(); |
| | | Integer stationId = command.getStationId(); |
| | | Integer targetStationId = command.getTargetStaNo(); |
| | | boolean generateBarcode = false; |
| | | |
| | | if (command.getCommandType() == StationCommandType.RESET) { |
| | | resetStation(deviceNo, stationId); |
| | | return; |
| | | } |
| | | |
| | | if (checkTaskNoInArea(taskNo)) { |
| | | generateBarcode = true; |
| | | } |
| | | |
| | | if (command.getCommandType() == StationCommandType.WRITE_INFO) { |
| | |
| | | return; |
| | | } |
| | | if (taskNo == 9998 && targetStationId == 0) { |
| | | // 生成出库站点仿真数据 |
| | | generateFakeOutStationData(deviceNo, stationId); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | if (taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId == targetStationId) { |
| | | if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null |
| | | && stationId.equals(targetStationId)) { |
| | | generateStationData(deviceNo, taskNo, stationId, targetStationId); |
| | | } |
| | | // 注意:MOVE 类型的命令现已在 sendCommand 中处理,handleCommand 仅处理非 MOVE 命令 |
| | | } |
| | | |
| | | private void generateFakeOutStationData(Integer deviceNo, Integer stationId) { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return; |
| | | } |
| | | ZyStationStatusEntity status = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null); |
| | | if (status == null) { |
| | | return; |
| | | } |
| | | |
| | | synchronized (status) { |
| | | status.setLoading(true); |
| | | } |
| | |
| | | |
| | | private void generateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return; |
| | | } |
| | | ZyStationStatusEntity status = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null); |
| | | if (status == null) { |
| | | return; |
| | | } |
| | | |
| | | synchronized (status) { |
| | | status.setTaskNo(taskNo); |
| | | status.setTargetStaNo(targetStationId); |
| | |
| | | |
| | | private void resetStation(Integer deviceNo, Integer stationId) { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return; |
| | | } |
| | | ZyStationStatusEntity status = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null); |
| | | if (status == null) { |
| | | return; |
| | | } |
| | | |
| | | synchronized (status) { |
| | | status.setTaskNo(0); |
| | | status.setLoading(false); |
| | |
| | | if (statusList == null) { |
| | | return; |
| | | } |
| | | |
| | | ZyStationStatusEntity status = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null); |
| | | if (status == null) { |
| | | return; |
| | | } |
| | | |
| | | synchronized (status) { |
| | | status.setBarcode(barcode); |
| | | } |
| | | } |
| | | |
| | | // segmentedPathCommand 方法已删除,功能已整合到 runTaskLoop |
| | | |
| | | private Integer getDeviceNoByStationId(Integer stationId) { |
| | | for (Integer devNo : deviceStatusMap.keySet()) { |
| | | List<ZyStationStatusEntity> list = deviceStatusMap.get(devNo); |
| | | for (Map.Entry<Integer, List<ZyStationStatusEntity>> entry : deviceStatusMap.entrySet()) { |
| | | List<ZyStationStatusEntity> list = entry.getValue(); |
| | | if (list == null) { |
| | | continue; |
| | | } |
| | | for (ZyStationStatusEntity e : list) { |
| | | if (e.getStationId() != null && e.getStationId().equals(stationId)) { |
| | | return devNo; |
| | | for (ZyStationStatusEntity entity : list) { |
| | | if (entity.getStationId() != null && entity.getStationId().equals(stationId)) { |
| | | return entry.getKey(); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | private Integer findCurrentStationIdByTask(Integer taskNo) { |
| | | for (Integer devNo : deviceStatusMap.keySet()) { |
| | | List<ZyStationStatusEntity> list = deviceStatusMap.get(devNo); |
| | | for (List<ZyStationStatusEntity> list : deviceStatusMap.values()) { |
| | | if (list == null) { |
| | | continue; |
| | | } |
| | | for (ZyStationStatusEntity e : list) { |
| | | if (e.getTaskNo() != null && e.getTaskNo().equals(taskNo) && e.isLoading()) { |
| | | return e.getStationId(); |
| | | for (ZyStationStatusEntity entity : list) { |
| | | if (entity.getTaskNo() != null && entity.getTaskNo().equals(taskNo) && entity.isLoading()) { |
| | | return entity.getStationId(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // stationMoveByPathIds 方法已删除,功能已整合到 runTaskLoop |
| | | |
| | | private void sleep(long ms) { |
| | | try { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取站点锁,如果不存在则创建 |
| | | */ |
| | | private ReentrantLock getStationLock(Integer stationId) { |
| | | return stationLocks.computeIfAbsent(stationId, k -> new ReentrantLock()); |
| | | return stationLocks.computeIfAbsent(stationId, key -> new ReentrantLock()); |
| | | } |
| | | |
| | | /** |
| | | * 按顺序锁定多个站点(避免死锁) |
| | | */ |
| | | private void lockStations(Integer... stationIds) { |
| | | Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length); |
| | | Arrays.sort(sorted); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 按逆序解锁多个站点 |
| | | */ |
| | | private void unlockStations(Integer... stationIds) { |
| | | Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length); |
| | | Arrays.sort(sorted); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新站点数据(调用前必须已持有该站点的锁) |
| | | */ |
| | | private boolean updateStationDataInternal(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo, |
| | | Boolean isLoading, String barcode, Boolean runBlock) { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return false; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null); |
| | | |
| | | if (currentStatus == null) { |
| | | return false; |
| | | } |
| | |
| | | if (taskNo != null) { |
| | | currentStatus.setTaskNo(taskNo); |
| | | } |
| | | |
| | | if (targetStaNo != null) { |
| | | currentStatus.setTargetStaNo(targetStaNo); |
| | | } |
| | | |
| | | if (isLoading != null) { |
| | | currentStatus.setLoading(isLoading); |
| | | } |
| | | |
| | | if (barcode != null) { |
| | | currentStatus.setBarcode(barcode); |
| | | } |
| | | |
| | | if (runBlock != null) { |
| | | currentStatus.setRunBlock(runBlock); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 初始化站点移动(使用站点级锁) |
| | | */ |
| | | public boolean initStationMove(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo, |
| | | Integer taskNo, Integer targetStationId, Boolean isLoading, String barcode) { |
| | | lockStations(currentStationId); |
| | |
| | | if (statusList == null) { |
| | | return false; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null); |
| | | |
| | | if (currentStatus == null) { |
| | | return false; |
| | | } |
| | | |
| | | if (currentStatus.getTaskNo() > 0) { |
| | | if (!currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) { |
| | | return false; |
| | | } |
| | | if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0 |
| | | && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) { |
| | | return false; |
| | | } |
| | | |
| | | return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, targetStationId, |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 站点移动到下一个位置(使用站点级锁,按ID顺序获取锁避免死锁) |
| | | */ |
| | | public boolean stationMoveToNext(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo, |
| | | Integer nextStationId, Integer nextStationDeviceNo, Integer taskNo, Integer targetStaNo) { |
| | | // 同时锁定当前站点和下一个站点(按ID顺序,避免死锁) |
| | | lockStations(currentStationId, nextStationId); |
| | | try { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo); |
| | | if (statusList == null) { |
| | | return false; |
| | | } |
| | | |
| | | List<ZyStationStatusEntity> nextStatusList = deviceStatusMap.get(nextStationDeviceNo); |
| | | if (nextStatusList == null) { |
| | | if (statusList == null || nextStatusList == null) { |
| | | return false; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null); |
| | | |
| | | ZyStationStatusEntity nextStatus = nextStatusList.stream() |
| | | .filter(item -> item.getStationId().equals(nextStationId)).findFirst().orElse(null); |
| | | |
| | | if (currentStatus == null || nextStatus == null) { |
| | | return false; |
| | | } |
| | | |
| | | if (nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) { |
| | | if (nextStatus.getTaskNo() != null && nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) { |
| | | return false; |
| | | } |
| | | |
| | |
| | | if (!result) { |
| | | return false; |
| | | } |
| | | |
| | | boolean result2 = updateStationDataInternal(currentStationId, currentStationDeviceNo, 0, 0, false, "", |
| | | false); |
| | | if (!result2) { |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | return updateStationDataInternal(currentStationId, currentStationDeviceNo, 0, 0, false, "", false); |
| | | } finally { |
| | | unlockStations(currentStationId, nextStationId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 生成站点条码(使用站点级锁) |
| | | */ |
| | | public boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId, |
| | | Integer currentStationDeviceNo) { |
| | | public boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo) { |
| | | lockStations(currentStationId); |
| | | try { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo); |
| | | if (statusList == null) { |
| | | return false; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null); |
| | | |
| | | if (currentStatus == null) { |
| | | return false; |
| | | } |
| | | |
| | | Random random = new Random(); |
| | | |
| | | String barcodeTime = String.valueOf(System.currentTimeMillis()); |
| | | String barcode = String.valueOf(random.nextInt(10)) + String.valueOf(random.nextInt(10)) |
| | | + barcodeTime.substring(7); |
| | | |
| | | return updateStationDataInternal(currentStationId, currentStationDeviceNo, null, null, null, barcode, null); |
| | | } finally { |
| | | unlockStations(currentStationId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清除站点数据(使用站点级锁) |
| | | */ |
| | | public boolean clearStation(Integer deviceNo, Integer lockTaskNo, Integer currentStationId) { |
| | | lockStations(currentStationId); |
| | | try { |
| | |
| | | if (statusList == null) { |
| | | return false; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null); |
| | | |
| | | if (currentStatus == null) { |
| | | return false; |
| | | } |
| | | |
| | | return updateStationDataInternal(currentStationId, deviceNo, 0, 0, false, "", false); |
| | | } finally { |
| | | unlockStations(currentStationId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 标记站点堵塞(使用站点级锁) |
| | | */ |
| | | public boolean runBlockStation(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo, |
| | | Integer taskNo, Integer blockStationId) { |
| | | lockStations(currentStationId); |
| | |
| | | if (statusList == null) { |
| | | return false; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null); |
| | | |
| | | if (currentStatus == null) { |
| | | return false; |
| | | } |
| | | |
| | | return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, blockStationId, true, "", |
| | | true); |
| | | } finally { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清除站点堵塞标记(堵塞恢复时使用) |
| | | */ |
| | | public void clearRunBlock(Integer stationId, Integer deviceNo) { |
| | | lockStations(stationId); |
| | | try { |
| | |
| | | if (statusList == null) { |
| | | return; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null); |
| | | |
| | | if (currentStatus == null) { |
| | | return; |
| | | } |
| | | |
| | | if (currentStatus.isRunBlock()) { |
| | | currentStatus.setRunBlock(false); |
| | | News.info("[WCS Debug] 站点{}堵塞标记已清除", stationId); |
| | |
| | | } |
| | | |
| | | private boolean checkTaskNoInArea(Integer taskNo) { |
| | | if (taskNo == null || redisUtil == null) { |
| | | return false; |
| | | } |
| | | |
| | | Object fakeTaskNoAreaObj = redisUtil.get(RedisKeyType.FAKE_TASK_NO_AREA.key); |
| | | if (fakeTaskNoAreaObj == null) { |
| | | return false; |
| | |
| | | JSONObject data = JSON.parseObject(String.valueOf(fakeTaskNoAreaObj)); |
| | | Integer start = data.getInteger("start"); |
| | | Integer end = data.getInteger("end"); |
| | | if (start == null || end == null) { |
| | | return false; |
| | | } |
| | | return taskNo >= start && taskNo <= end; |
| | | } |
| | | |
| | | if (taskNo >= start && taskNo <= end) { |
| | | return true; |
| | | private Map<String, Object> buildDetails(Object... keyValues) { |
| | | Map<String, Object> details = new LinkedHashMap<String, Object>(); |
| | | if (keyValues == null) { |
| | | return details; |
| | | } |
| | | for (int i = 0; i + 1 < keyValues.length; i += 2) { |
| | | Object key = keyValues[i]; |
| | | if (key != null) { |
| | | details.put(String.valueOf(key), keyValues[i + 1]); |
| | | } |
| | | } |
| | | return details; |
| | | } |
| | | |
| | | private void traceEvent(Integer deviceNo, TaskRuntimeContext context, String eventType, String message, |
| | | Map<String, Object> details, boolean terminal) { |
| | | if (context == null || context.taskNo == null) { |
| | | return; |
| | | } |
| | | try { |
| | | FakeTaskTraceRegistry registry = SpringUtils.getBean(FakeTaskTraceRegistry.class); |
| | | if (registry == null) { |
| | | return; |
| | | } |
| | | registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : getThreadImpl(deviceNo), |
| | | context.status, context.startStationId, context.currentStationId, context.finalTargetStationId, |
| | | context.blockedStationId, context.getStitchedPathStationIds(), context.getPassedStationIds(), |
| | | context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, details, |
| | | terminal); |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | |
| | | private String getThreadImpl(Integer deviceNo) { |
| | | DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo); |
| | | return deviceConfig == null ? "" : deviceConfig.getThreadImpl(); |
| | | } |
| | | |
| | | private boolean isTerminalStatus(String status) { |
| | | return STATUS_BLOCKED.equals(status) || STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status) |
| | | || STATUS_FINISHED.equals(status); |
| | | } |
| | | |
| | | private static class TaskRuntimeContext { |
| | | |
| | | private final Integer taskNo; |
| | | private final String threadImpl; |
| | | private final LinkedBlockingQueue<Integer> pendingPathQueue = new LinkedBlockingQueue<Integer>(); |
| | | private final List<Integer> stitchedPathStationIds = new ArrayList<Integer>(); |
| | | private final List<Integer> passedStationIds = new ArrayList<Integer>(); |
| | | private final List<Integer> latestAppendedPath = new ArrayList<Integer>(); |
| | | |
| | | private Integer startStationId; |
| | | private Integer currentStationId; |
| | | private Integer finalTargetStationId; |
| | | private Integer blockedStationId; |
| | | private boolean generateBarcode; |
| | | private boolean initialized; |
| | | private boolean arrivalHandled; |
| | | private long lastStepAt = System.currentTimeMillis(); |
| | | private long lastCommandAt = System.currentTimeMillis(); |
| | | private String status = STATUS_WAITING; |
| | | |
| | | private TaskRuntimeContext(Integer taskNo, String threadImpl) { |
| | | this.taskNo = taskNo; |
| | | this.threadImpl = threadImpl; |
| | | } |
| | | |
| | | return false; |
| | | private void setStartStationIdIfAbsent(Integer stationId) { |
| | | if (startStationId == null && stationId != null) { |
| | | startStationId = stationId; |
| | | } |
| | | } |
| | | |
| | | private void appendStitchedPath(List<Integer> path) { |
| | | latestAppendedPath.clear(); |
| | | if (path == null) { |
| | | return; |
| | | } |
| | | for (Integer stationId : path) { |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | latestAppendedPath.add(stationId); |
| | | if (stitchedPathStationIds.isEmpty() |
| | | || !stationId.equals(stitchedPathStationIds.get(stitchedPathStationIds.size() - 1))) { |
| | | stitchedPathStationIds.add(stationId); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void addPassedStation(Integer stationId) { |
| | | if (stationId == null) { |
| | | return; |
| | | } |
| | | if (passedStationIds.isEmpty() |
| | | || !stationId.equals(passedStationIds.get(passedStationIds.size() - 1))) { |
| | | passedStationIds.add(stationId); |
| | | } |
| | | } |
| | | |
| | | private List<Integer> getPendingStationIds() { |
| | | return new ArrayList<Integer>(pendingPathQueue); |
| | | } |
| | | |
| | | private List<Integer> getPassedStationIds() { |
| | | return new ArrayList<Integer>(passedStationIds); |
| | | } |
| | | |
| | | private List<Integer> getStitchedPathStationIds() { |
| | | return new ArrayList<Integer>(stitchedPathStationIds); |
| | | } |
| | | |
| | | private List<Integer> getLatestAppendedPath() { |
| | | return new ArrayList<Integer>(latestAppendedPath); |
| | | } |
| | | } |
| | | } |