| | |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | 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.core.model.StationObjModel; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.model.CommandResponse; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.network.api.ZyStationConnectApi; |
| | | import com.zy.core.network.entity.ZyStationStatusEntity; |
| | | |
| | |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Random; |
| | | import java.util.Set; |
| | | 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.LinkedBlockingQueue; |
| | | import java.util.concurrent.ScheduledExecutorService; |
| | | import java.util.concurrent.ThreadFactory; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.locks.ReentrantLock; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | |
| | | public class ZyStationFakeSegConnect implements ZyStationConnectApi { |
| | | |
| | | private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L; |
| | | private static final long WAIT_SEGMENT_TIMEOUT_MS = 30000L; |
| | | private long getWaitSegmentTimeoutMs() { |
| | | return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_SEGMENT_WAIT_TIMEOUT_MS, 30000L); |
| | | } |
| | | |
| | | private long getMoveStepDurationMs() { |
| | | return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_MOVE_STEP_DURATION_MS, 500L); |
| | | } |
| | | |
| | | private long getIdleLoopDelayMs() { |
| | | return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_IDLE_LOOP_DELAY_MS, 200L); |
| | | } |
| | | |
| | | private long getBlockedLoopDelayMs() { |
| | | return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_BLOCKED_LOOP_DELAY_MS, 1000L); |
| | | } |
| | | |
| | | private long getInitializeDelayMs() { |
| | | return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_INITIALIZE_DELAY_MS, 0L); |
| | | } |
| | | |
| | | private long getFinishDelayMs() { |
| | | return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_FINISH_DELAY_MS, 0L); |
| | | } |
| | | |
| | | private static final String STATUS_WAITING = "WAITING"; |
| | | private static final String STATUS_RUNNING = "RUNNING"; |
| | |
| | | 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 static final String SEGMENT_MERGE_APPEND = "APPEND"; |
| | | private static final String SEGMENT_MERGE_REPLACE_TARGET_CHANGED = "REPLACE_TARGET_CHANGED"; |
| | | private static final String SEGMENT_MERGE_REPLACE_REROUTE = "REPLACE_REROUTE"; |
| | | private static final String SEGMENT_MERGE_IGNORE_DISCONNECTED = "IGNORE_DISCONNECTED"; |
| | | private static final String SEGMENT_MERGE_IGNORE_CURRENT_MISSING = "IGNORE_CURRENT_MISSING"; |
| | | |
| | | private final FakeStationStateManager stateManager = new FakeStationStateManager(); |
| | | private final FakeStationMoveEngine moveEngine = new FakeStationMoveEngine(stateManager); |
| | | private final FakeStationBlockManager blockManager = new FakeStationBlockManager(stateManager); |
| | | private static final AtomicInteger DEVICE_EXECUTOR_THREAD_SEQ = new AtomicInteger(1); |
| | | private volatile ScheduledExecutorService loopScheduler = createLoopScheduler(); |
| | | private RedisUtil redisUtil; |
| | | private final Map<Integer, ExecutorService> deviceExecutors = new ConcurrentHashMap<Integer, ExecutorService>(); |
| | | |
| | | 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; |
| | | private final Map<Integer, TaskRuntimeContext> taskContexts = new ConcurrentHashMap<Integer, TaskRuntimeContext>(); |
| | | private final Map<Integer, AtomicInteger> taskLoopGenerations = new ConcurrentHashMap<Integer, AtomicInteger>(); |
| | | private final Map<Integer, Object> taskLifecycleLocks = new ConcurrentHashMap<Integer, Object>(); |
| | | private volatile Set<Integer> legalClearStationIds = new HashSet<Integer>(); |
| | | private volatile Set<Integer> barcodeStationIds = new HashSet<Integer>(); |
| | | |
| | | 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<ZyStationStatusEntity>()); |
| | | refreshLegalClearStationIds(); |
| | | refreshBarcodeStationIds(); |
| | | stateManager.registerDevice(deviceConfig); |
| | | } |
| | | |
| | | @Override |
| | | public boolean connect() { |
| | | if (loopScheduler == null || loopScheduler.isShutdown() || loopScheduler.isTerminated()) { |
| | | loopScheduler = createLoopScheduler(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean disconnect() { |
| | | ScheduledExecutorService scheduler = loopScheduler; |
| | | if (scheduler != null) { |
| | | scheduler.shutdownNow(); |
| | | } |
| | | for (ExecutorService executor : deviceExecutors.values()) { |
| | | executor.shutdownNow(); |
| | | } |
| | | deviceExecutors.clear(); |
| | | taskQueues.clear(); |
| | | taskLastUpdateTime.clear(); |
| | | taskRunning.clear(); |
| | | taskContexts.clear(); |
| | | taskLoopGenerations.clear(); |
| | | taskLifecycleLocks.clear(); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public List<ZyStationStatusEntity> getStatus(Integer deviceNo) { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return new ArrayList<ZyStationStatusEntity>(); |
| | | } |
| | | |
| | | DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo); |
| | | if (statusList.isEmpty() && deviceConfig != null) { |
| | | List<ZyStationStatusEntity> init = JSON.parseArray(deviceConfig.getFakeInitStatus(), |
| | | ZyStationStatusEntity.class); |
| | | if (init != null) { |
| | | statusList.addAll(init); |
| | | for (ZyStationStatusEntity status : statusList) { |
| | | status.setAutoing(true); |
| | | status.setLoading(false); |
| | | status.setInEnable(true); |
| | | status.setOutEnable(true); |
| | | status.setEmptyMk(false); |
| | | status.setFullPlt(false); |
| | | status.setRunBlock(false); |
| | | status.setPalletHeight(0); |
| | | status.setError(0); |
| | | status.setBarcode(""); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return statusList; |
| | | return stateManager.getStatus(deviceNo); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | if (command.getCommandType() != StationCommandType.MOVE) { |
| | | handleCommand(deviceNo, command); |
| | | return new CommandResponse(true, "命令已受理(异步执行)"); |
| | | } |
| | | |
| | | 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() { |
| | | News.info("[WCS Debug] fake sendCommand收到非MOVE命令,deviceNo={},taskNo={},stationId={},targetStaNo={},commandType={}", |
| | | deviceNo, taskNo, command.getStationId(), command.getTargetStaNo(), command.getCommandType()); |
| | | getDeviceExecutor(deviceNo).submit(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | runTaskLoop(deviceNo, taskNo); |
| | | handleCommand(deviceNo, command); |
| | | } |
| | | }); |
| | | return new CommandResponse(true, "命令已受理(异步执行)"); |
| | | } |
| | | |
| | | int loopGeneration = 0; |
| | | boolean shouldSchedule = false; |
| | | Object lifecycleLock = getTaskLifecycleLock(taskNo); |
| | | synchronized (lifecycleLock) { |
| | | BlockingQueue<StationCommand> queue = taskQueues.computeIfAbsent(taskNo, key -> new LinkedBlockingQueue<StationCommand>()); |
| | | queue.offer(command); |
| | | taskLastUpdateTime.put(taskNo, System.currentTimeMillis()); |
| | | News.info("[WCS Debug] fake sendCommand入队,deviceNo={},taskNo={},stationId={},targetStaNo={},segmentNo={},segmentCount={},queueSize={},running={}", |
| | | deviceNo, taskNo, command.getStationId(), command.getTargetStaNo(), command.getSegmentNo(), command.getSegmentCount(), |
| | | queue.size(), taskRunning.containsKey(taskNo)); |
| | | |
| | | if (taskRunning.putIfAbsent(taskNo, true) == null) { |
| | | TaskRuntimeContext context = new TaskRuntimeContext(taskNo, deviceNo, stateManager.getThreadImpl(deviceNo)); |
| | | taskContexts.put(taskNo, context); |
| | | AtomicInteger generation = taskLoopGenerations.computeIfAbsent(taskNo, key -> new AtomicInteger(0)); |
| | | loopGeneration = generation.incrementAndGet(); |
| | | context.loopGeneration = loopGeneration; |
| | | shouldSchedule = true; |
| | | News.info("[WCS Debug] fake task准备启动执行线程,deviceNo={},taskNo={},queueSize={},loopGeneration={}", |
| | | deviceNo, taskNo, queue.size(), loopGeneration); |
| | | } else { |
| | | News.info("[WCS Debug] fake task复用已存在执行线程,deviceNo={},taskNo={},queueSize={}", |
| | | deviceNo, taskNo, queue.size()); |
| | | } |
| | | } |
| | | if (shouldSchedule) { |
| | | scheduleTaskLoop(deviceNo, taskNo, loopGeneration); |
| | | } |
| | | |
| | | return new CommandResponse(true, "命令已受理(异步执行)"); |
| | |
| | | |
| | | @Override |
| | | public CommandResponse clearTaskBufferSlot(Integer deviceNo, Integer stationId, Integer slotIdx) { |
| | | if (deviceNo == null || stationId == null || slotIdx == null || slotIdx <= 0) { |
| | | return new CommandResponse(false, "清理缓存区槽位参数无效"); |
| | | } |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return new CommandResponse(false, "未找到设备状态"); |
| | | } |
| | | for (ZyStationStatusEntity status : statusList) { |
| | | if (status == null || !stationId.equals(status.getStationId())) { |
| | | continue; |
| | | } |
| | | List<StationTaskBufferItem> taskBufferItems = status.getTaskBufferItems(); |
| | | if (taskBufferItems == null || taskBufferItems.isEmpty()) { |
| | | return new CommandResponse(true, "缓存区槽位已为空"); |
| | | } |
| | | for (StationTaskBufferItem item : taskBufferItems) { |
| | | if (item != null && slotIdx.equals(item.getSlotIdx())) { |
| | | item.setTaskNo(0); |
| | | item.setTargetStaNo(0); |
| | | return new CommandResponse(true, "缓存区槽位清理成功"); |
| | | } |
| | | } |
| | | return new CommandResponse(false, "未找到缓存区槽位"); |
| | | } |
| | | return new CommandResponse(false, "未找到站点状态"); |
| | | return stateManager.clearTaskBufferSlot(deviceNo, stationId, slotIdx); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return new byte[0]; |
| | | } |
| | | |
| | | private boolean isDirectMoveCommand(StationCommand command) { |
| | | if (command == null || command.getCommandType() != StationCommandType.MOVE) { |
| | | return false; |
| | | |
| | | private ScheduledExecutorService createLoopScheduler() { |
| | | return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { |
| | | @Override |
| | | public Thread newThread(Runnable runnable) { |
| | | Thread thread = new Thread(runnable, "fake-station-loop-scheduler"); |
| | | thread.setDaemon(true); |
| | | return thread; |
| | | } |
| | | 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); |
| | | private ExecutorService getDeviceExecutor(Integer deviceNo) { |
| | | return deviceExecutors.computeIfAbsent(deviceNo, key -> Executors.newSingleThreadExecutor(new ThreadFactory() { |
| | | @Override |
| | | public Thread newThread(Runnable runnable) { |
| | | Thread thread = new Thread(runnable, |
| | | "fake-station-device-" + key + "-" + DEVICE_EXECUTOR_THREAD_SEQ.getAndIncrement()); |
| | | thread.setDaemon(true); |
| | | return thread; |
| | | } |
| | | })); |
| | | } |
| | | |
| | | 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 scheduleTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration) { |
| | | scheduleTaskLoop(deviceNo, taskNo, loopGeneration, 0L); |
| | | } |
| | | |
| | | private void runTaskLoop(Integer deviceNo, Integer taskNo) { |
| | | TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo)); |
| | | private void scheduleTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration, long delayMs) { |
| | | Runnable task = new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | getDeviceExecutor(deviceNo).submit(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | runTaskLoop(deviceNo, taskNo, loopGeneration); |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | if (delayMs <= 0L) { |
| | | task.run(); |
| | | return; |
| | | } |
| | | loopScheduler.schedule(task, delayMs, TimeUnit.MILLISECONDS); |
| | | } |
| | | |
| | | private void scheduleTaskLoopByDelay(Integer deviceNo, Integer taskNo, int loopGeneration, long delayMs) { |
| | | scheduleTaskLoop(deviceNo, taskNo, loopGeneration, Math.max(delayMs, 1L)); |
| | | } |
| | | |
| | | private void runTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration) { |
| | | TaskRuntimeContext context = taskContexts.get(taskNo); |
| | | if (context == null) { |
| | | News.info("[WCS Debug] fake task忽略无上下文续跑片段,deviceNo={},taskNo={},loopGeneration={}", |
| | | deviceNo, taskNo, loopGeneration); |
| | | return; |
| | | } |
| | | if (!deviceNo.equals(context.deviceNo)) { |
| | | News.info("[WCS Debug] fake task忽略跨设备续跑片段,taskNo={},expectedDeviceNo={},actualDeviceNo={},loopGeneration={}", |
| | | taskNo, context.deviceNo, deviceNo, loopGeneration); |
| | | return; |
| | | } |
| | | if (context.loopGeneration != loopGeneration || !isCurrentLoopGeneration(taskNo, loopGeneration)) { |
| | | News.info("[WCS Debug] fake task忽略过期续跑片段,deviceNo={},taskNo={},expectedLoopGeneration={},actualLoopGeneration={}", |
| | | deviceNo, taskNo, loopGeneration, context.loopGeneration); |
| | | return; |
| | | } |
| | | News.info("[WCS Debug] fake task进入runTaskLoop,deviceNo={},taskNo={},threadImpl={},queueExists={},queueSize={},loopGeneration={},status={},initialized={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}", |
| | | deviceNo, taskNo, context.threadImpl, taskQueues.containsKey(taskNo), |
| | | taskQueues.containsKey(taskNo) && taskQueues.get(taskNo) != null ? taskQueues.get(taskNo).size() : null, loopGeneration, |
| | | context.status, context.initialized, context.currentStationId, context.finalTargetStationId, |
| | | context.blockedStationId, context.getPendingStationIds()); |
| | | long nextDelayMs = 0L; |
| | | boolean shouldContinue = false; |
| | | try { |
| | | while (true) { |
| | | if (Thread.currentThread().isInterrupted()) { |
| | | if (!isTerminalStatus(context.status)) { |
| | | context.status = STATUS_CANCELLED; |
| | | } |
| | | break; |
| | | } |
| | | |
| | | if (hasTaskReset(taskNo)) { |
| | | } else if (hasTaskReset(taskNo)) { |
| | | context.status = STATUS_CANCELLED; |
| | | break; |
| | | } |
| | | |
| | | } else { |
| | | BlockingQueue<StationCommand> commandQueue = taskQueues.get(taskNo); |
| | | if (commandQueue == null) { |
| | | break; |
| | | } |
| | | |
| | | StationCommand command = commandQueue.poll(100, TimeUnit.MILLISECONDS); |
| | | context.status = STATUS_FINISHED; |
| | | } else { |
| | | StationCommand command = commandQueue.poll(); |
| | | if (command != null) { |
| | | taskLastUpdateTime.put(taskNo, System.currentTimeMillis()); |
| | | context.lastCommandAt = System.currentTimeMillis(); |
| | | handleIncomingSegment(deviceNo, context, command); |
| | | } |
| | | |
| | | if (!context.pendingPathQueue.isEmpty()) { |
| | | if (!context.initialized || context.currentStationId == null) { |
| | | initializeTaskPosition(deviceNo, context); |
| | | continue; |
| | | } |
| | | |
| | | 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; |
| | | if (!context.pendingPathQueue.isEmpty()) { |
| | | News.info("[WCS Debug] fake task准备推进,deviceNo={},taskNo={},initialized={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}", |
| | | deviceNo, taskNo, context.initialized, context.currentStationId, |
| | | context.finalTargetStationId, context.blockedStationId, context.getPendingStationIds()); |
| | | if (!context.initialized || context.currentStationId == null) { |
| | | nextDelayMs = initializeTaskPosition(deviceNo, context); |
| | | } else { |
| | | MoveStepResult moveResult = executeNextMove(deviceNo, context); |
| | | if (!moveResult.shouldContinue()) { |
| | | context.status = STATUS_FINISHED; |
| | | } |
| | | } finally { |
| | | nextDelayMs = moveResult.getNextDelayMs(); |
| | | } |
| | | } else { |
| | | News.info("[WCS Debug] fake task进入空闲态,deviceNo={},taskNo={},status={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}", |
| | | deviceNo, taskNo, context.status, context.currentStationId, |
| | | context.finalTargetStationId, context.blockedStationId, context.getPendingStationIds()); |
| | | IdleStepResult idleResult = handleIdleState(deviceNo, context); |
| | | if (idleResult.isFinished() && !isTerminalStatus(context.status)) { |
| | | context.status = STATUS_FINISHED; |
| | | } |
| | | nextDelayMs = idleResult.getNextDelayMs(); |
| | | } |
| | | } |
| | | shouldContinue = !isTerminalStatus(context.status) |
| | | && taskQueues.containsKey(taskNo) |
| | | && (commandQueue != null); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | context.status = STATUS_CANCELLED; |
| | | if (e instanceof InterruptedException) { |
| | | Thread.currentThread().interrupt(); |
| | | } |
| | | News.info("[WCS Debug] 任务{}执行异常,当前站点={},目标站={},待执行路径={},异常类型={},异常信息={}", |
| | | taskNo, context.currentStationId, context.finalTargetStationId, context.getPendingStationIds(), |
| | | e.getClass().getSimpleName(), e.getMessage()); |
| | | } |
| | | |
| | | if (shouldContinue) { |
| | | if (nextDelayMs > 0L) { |
| | | scheduleTaskLoopByDelay(deviceNo, taskNo, loopGeneration, nextDelayMs); |
| | | } else { |
| | | scheduleTaskLoop(deviceNo, taskNo, loopGeneration); |
| | | } |
| | | return; |
| | | } |
| | | finishTaskLoop(deviceNo, taskNo, context, loopGeneration); |
| | | } |
| | | |
| | | private void finishTaskLoop(Integer deviceNo, Integer taskNo, TaskRuntimeContext context, int loopGeneration) { |
| | | Object lifecycleLock = getTaskLifecycleLock(taskNo); |
| | | synchronized (lifecycleLock) { |
| | | if (context.loopGeneration != loopGeneration || !isCurrentLoopGeneration(taskNo, loopGeneration)) { |
| | | News.info("[WCS Debug] fake task忽略过期结束清理,deviceNo={},taskNo={},expectedLoopGeneration={},actualLoopGeneration={}", |
| | | deviceNo, taskNo, loopGeneration, context.loopGeneration); |
| | | return; |
| | | } |
| | | BlockingQueue<StationCommand> queue = taskQueues.get(taskNo); |
| | | if (queue != null && !queue.isEmpty()) { |
| | | News.info("[WCS Debug] fake task结束清理前发现新命令,恢复续跑,deviceNo={},taskNo={},queueSize={},loopGeneration={}", |
| | | deviceNo, taskNo, queue.size(), loopGeneration); |
| | | scheduleTaskLoop(deviceNo, taskNo, loopGeneration); |
| | | return; |
| | | } |
| | | News.info("[WCS Debug] fake task即将退出runTaskLoop,deviceNo={},taskNo={},status={},queueSizeBeforeCleanup={},lastCurrentStationId={},targetStationId={},loopGeneration={}", |
| | | deviceNo, taskNo, context.status, queue == null ? null : queue.size(), context.currentStationId, context.finalTargetStationId, loopGeneration); |
| | | taskQueues.remove(taskNo); |
| | | taskLastUpdateTime.remove(taskNo); |
| | | taskRunning.remove(taskNo); |
| | | taskContexts.remove(taskNo); |
| | | taskLoopGenerations.remove(taskNo); |
| | | taskLifecycleLocks.remove(taskNo, lifecycleLock); |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | buildDetails("reason", context.status, "loopGeneration", loopGeneration), true); |
| | | News.info("[WCS Debug] 任务{}执行结束并清理资源,状态={},loopGeneration={}", taskNo, context.status, loopGeneration); |
| | | } |
| | | |
| | | private void handleIncomingSegment(Integer deviceNo, TaskRuntimeContext context, StationCommand command) { |
| | | if (!deviceNo.equals(context.deviceNo)) { |
| | | traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段来自不同设备车道,已忽略", |
| | | buildDetails("expectedDeviceNo", context.deviceNo, "actualDeviceNo", deviceNo, |
| | | "segmentNo", command.getSegmentNo(), "segmentCount", command.getSegmentCount()), false); |
| | | return; |
| | | } |
| | | List<Integer> newPath = normalizePath(command.getNavigatePath()); |
| | | Integer lastInQueue = getLastInQueue(context.pendingPathQueue); |
| | | int startIndex = getPathAppendStartIndex(newPath, context.currentStationId, lastInQueue); |
| | | Integer previousTargetStationId = context.finalTargetStationId; |
| | | Integer commandTargetStationId = command.getTargetStaNo(); |
| | | boolean targetChanged = commandTargetStationId != null |
| | | && !commandTargetStationId.equals(previousTargetStationId); |
| | | boolean queueEmpty = context.pendingPathQueue.isEmpty(); |
| | | boolean newPathContainsCurrent = context.currentStationId != null && newPath.contains(context.currentStationId); |
| | | boolean pathConnectedToTail = startIndex >= 0; |
| | | List<Integer> oldPendingStations = context.getPendingStationIds(); |
| | | boolean shouldClearBarcodeSourceOnReroute = context.currentStationId != null |
| | | && context.currentStationId.equals(command.getStationId()) |
| | | && isBarcodeStation(context.currentStationId) |
| | | && previousTargetStationId != null |
| | | && previousTargetStationId.equals(context.currentStationId) |
| | | && commandTargetStationId != null |
| | | && !commandTargetStationId.equals(context.currentStationId); |
| | | |
| | | context.setStartStationIdIfAbsent(command.getStationId()); |
| | | if (!context.generateBarcode && checkTaskNoInArea(context.taskNo)) { |
| | |
| | | traceEvent(deviceNo, context, "SEGMENT_RECEIVED", "收到新的路径分段命令", |
| | | buildDetails("segmentPath", newPath, "appendStartIndex", startIndex, "currentStationId", |
| | | context.currentStationId, "queueTailStationId", lastInQueue, "commandStationId", |
| | | command.getStationId(), "commandTargetStationId", command.getTargetStaNo()), |
| | | command.getStationId(), "commandTargetStationId", commandTargetStationId, |
| | | "previousTargetStationId", previousTargetStationId), |
| | | false); |
| | | |
| | | Integer commandTargetStationId = command.getTargetStaNo(); |
| | | if (commandTargetStationId != null) { |
| | | if (!commandTargetStationId.equals(context.finalTargetStationId)) { |
| | | if (targetChanged) { |
| | | traceEvent(deviceNo, context, "TARGET_SWITCHED", |
| | | "任务目标站发生切换: " + context.finalTargetStationId + " -> " + commandTargetStationId, |
| | | buildDetails("fromTargetStationId", context.finalTargetStationId, "toTargetStationId", |
| | | "任务目标站发生切换: " + previousTargetStationId + " -> " + commandTargetStationId, |
| | | buildDetails("fromTargetStationId", previousTargetStationId, "toTargetStationId", |
| | | commandTargetStationId), |
| | | false); |
| | | context.arrivalHandled = false; |
| | |
| | | context.finalTargetStationId = commandTargetStationId; |
| | | syncCurrentStationTarget(context.taskNo, context.currentStationId, context.finalTargetStationId); |
| | | } |
| | | context.segmentNo = command.getSegmentNo(); |
| | | context.segmentCount = command.getSegmentCount(); |
| | | |
| | | if (!newPath.isEmpty() && startIndex < 0) { |
| | | traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法与当前运行上下文衔接,已忽略", |
| | | boolean tailConnectedAppend = !queueEmpty && pathConnectedToTail; |
| | | boolean shouldReplace = !tailConnectedAppend && !queueEmpty |
| | | && (targetChanged || (!pathConnectedToTail && newPathContainsCurrent)); |
| | | String ignoreReason = null; |
| | | if (!shouldReplace && !tailConnectedAppend && !newPath.isEmpty() && !pathConnectedToTail) { |
| | | ignoreReason = newPathContainsCurrent ? SEGMENT_MERGE_IGNORE_DISCONNECTED : SEGMENT_MERGE_IGNORE_CURRENT_MISSING; |
| | | if (queueEmpty && context.currentStationId == null) { |
| | | startIndex = 0; |
| | | } else { |
| | | traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法接入当前运行上下文,已忽略", |
| | | buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId, |
| | | "queueTailStationId", lastInQueue, "ignoreReason", "PATH_NOT_CONNECTED"), |
| | | "queueTailStationId", lastInQueue, "ignoreReason", ignoreReason, |
| | | "queueEmpty", queueEmpty, "tailConnectedAppend", tailConnectedAppend, |
| | | "targetChanged", targetChanged), |
| | | false); |
| | | context.latestAppendedPath.clear(); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | if (tailConnectedAppend && startIndex < 0) { |
| | | startIndex = 0; |
| | | } |
| | | |
| | | List<Integer> appendedPath = new ArrayList<Integer>(); |
| | | List<Integer> replacedFuturePath = new ArrayList<Integer>(); |
| | | Integer duplicateHeadStationId = null; |
| | | String mergeMode = SEGMENT_MERGE_APPEND; |
| | | if (shouldReplace) { |
| | | replacedFuturePath = rebuildPendingPathFromCurrent(context, newPath); |
| | | boolean atTargetAlready = context.currentStationId != null && context.currentStationId.equals(commandTargetStationId); |
| | | if (context.currentStationId != null && replacedFuturePath.isEmpty() && !atTargetAlready) { |
| | | traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法接入当前运行上下文,已忽略", |
| | | buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId, |
| | | "queueTailStationId", lastInQueue, "ignoreReason", SEGMENT_MERGE_IGNORE_CURRENT_MISSING), |
| | | false); |
| | | context.latestAppendedPath.clear(); |
| | | return; |
| | | } |
| | | replacePendingPathQueue(context, replacedFuturePath); |
| | | context.replaceLatestPath(replacedFuturePath); |
| | | mergeMode = targetChanged ? SEGMENT_MERGE_REPLACE_TARGET_CHANGED : SEGMENT_MERGE_REPLACE_REROUTE; |
| | | traceEvent(deviceNo, context, "SEGMENT_REPLACED", |
| | | "新的 reroute 路径已覆盖旧 future queue", |
| | | buildDetails("mergeMode", mergeMode, "currentStationId", context.currentStationId, |
| | | "previousTargetStationId", previousTargetStationId, "newTargetStationId", commandTargetStationId, |
| | | "oldQueue", oldPendingStations, "replacedFuturePath", replacedFuturePath, |
| | | "queueSize", context.pendingPathQueue.size()), |
| | | false); |
| | | } else { |
| | | for (int i = startIndex; i < newPath.size(); i++) { |
| | | Integer stationId = newPath.get(i); |
| | | context.pendingPathQueue.offer(stationId); |
| | | appendedPath.add(stationId); |
| | | } |
| | | if (context.currentStationId != null && context.currentStationId.equals(context.pendingPathQueue.peek())) { |
| | | duplicateHeadStationId = context.pendingPathQueue.poll(); |
| | | if (!appendedPath.isEmpty() && duplicateHeadStationId.equals(appendedPath.get(0))) { |
| | | appendedPath.remove(0); |
| | | } |
| | | } |
| | | 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()), |
| | | buildDetails("mergeMode", mergeMode, "segmentPath", newPath, "appendedPath", appendedPath, |
| | | "appendStartIndex", startIndex, "queueSize", context.pendingPathQueue.size(), |
| | | "tailConnectedAppend", tailConnectedAppend, "targetChanged", targetChanged, |
| | | "duplicateHeadStationId", duplicateHeadStationId), |
| | | false); |
| | | } |
| | | |
| | | if (duplicateHeadStationId != null) { |
| | | traceEvent(deviceNo, context, "SEGMENT_TRIMMED", "待执行队列头部重复当前站点,已移除", |
| | | buildDetails("currentStationId", context.currentStationId, |
| | | "duplicateHeadStationId", duplicateHeadStationId, |
| | | "queueSize", context.pendingPathQueue.size(), |
| | | "remainingPendingPath", context.getPendingStationIds()), |
| | | false); |
| | | } |
| | | |
| | | if (context.currentStationId != null && context.currentStationId.equals(context.pendingPathQueue.peek())) { |
| | | Integer trimmedHead = context.pendingPathQueue.poll(); |
| | | traceEvent(deviceNo, context, "SEGMENT_TRIMMED", "待执行队列头部仍与当前站点重复,已再次移除", |
| | | buildDetails("currentStationId", context.currentStationId, |
| | | "duplicateHeadStationId", trimmedHead, |
| | | "queueSize", context.pendingPathQueue.size(), |
| | | "remainingPendingPath", context.getPendingStationIds()), |
| | | false); |
| | | } |
| | | |
| | | context.lastCommandAt = System.currentTimeMillis(); |
| | | |
| | | boolean segmentAccepted = !appendedPath.isEmpty() || !replacedFuturePath.isEmpty() |
| | | || (context.currentStationId != null && context.currentStationId.equals(commandTargetStationId)) |
| | | || (context.currentStationId != null && context.currentStationId.equals(command.getStationId()) |
| | | && commandTargetStationId != null && commandTargetStationId.equals(context.finalTargetStationId)); |
| | | if (segmentAccepted) { |
| | | if (shouldClearBarcodeSourceOnReroute && context.currentStationId != null) { |
| | | Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId); |
| | | if (currentDeviceNo != null) { |
| | | guardedClearStationForDispatch(currentDeviceNo, context.currentStationId, context.taskNo, "barcodeSourceRerouteAccepted"); |
| | | traceEvent(deviceNo, context, "SOURCE_STATION_CLEARED", "条码源站已完成任务交接,源站状态已清除", |
| | | buildDetails("stationId", context.currentStationId, |
| | | "previousTargetStationId", previousTargetStationId, |
| | | "newTargetStationId", commandTargetStationId, |
| | | "mergeMode", mergeMode), |
| | | false); |
| | | } |
| | | } |
| | | resumeFromCurrentStation(deviceNo, context, mergeMode, appendedPath, replacedFuturePath, |
| | | shouldClearBarcodeSourceOnReroute); |
| | | } |
| | | } |
| | | |
| | | private void initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) { |
| | | private long initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) { |
| | | Integer nextStationId = context.pendingPathQueue.peek(); |
| | | if (nextStationId == null) { |
| | | return; |
| | | return 0L; |
| | | } |
| | | |
| | | if (context.currentStationId == null) { |
| | | Integer actualCurrentStationId = findCurrentStationIdByTask(context.taskNo); |
| | | Integer actualCurrentStationId = stateManager.findCurrentStationIdByTask(context.taskNo); |
| | | if (actualCurrentStationId != null) { |
| | | context.currentStationId = actualCurrentStationId; |
| | | context.initialized = true; |
| | | context.status = STATUS_RUNNING; |
| | | context.blockedStationId = null; |
| | | |
| | | Integer actualDeviceNo = getDeviceNoByStationId(actualCurrentStationId); |
| | | Integer actualDeviceNo = stateManager.getDeviceNoByStationId(actualCurrentStationId); |
| | | if (actualDeviceNo != null) { |
| | | clearRunBlock(actualCurrentStationId, actualDeviceNo); |
| | | guardedClearRunBlock(context.taskNo, actualCurrentStationId, actualDeviceNo); |
| | | } |
| | | |
| | | trimPendingPathToCurrent(context.pendingPathQueue, actualCurrentStationId); |
| | |
| | | |
| | | context.addPassedStation(actualCurrentStationId); |
| | | context.lastStepAt = System.currentTimeMillis(); |
| | | context.lastProgressAt = context.lastStepAt; |
| | | context.lastProgressStationId = context.currentStationId; |
| | | guardedPublishTaskLocation(context.taskNo, actualDeviceNo, actualCurrentStationId, true, false); |
| | | traceEvent(deviceNo, context, "MOVE_INIT", "任务从当前实际站点恢复执行", |
| | | buildDetails("stationId", actualCurrentStationId, "recovered", true), false); |
| | | return; |
| | | return 0L; |
| | | } |
| | | } |
| | | |
| | | context.currentStationId = nextStationId; |
| | | Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId); |
| | | Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId); |
| | | if (currentDeviceNo == null) { |
| | | context.pendingPathQueue.poll(); |
| | | return; |
| | | return 0L; |
| | | } |
| | | |
| | | boolean result = initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo, |
| | | boolean result = moveEngine.initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo, |
| | | context.finalTargetStationId, true, null); |
| | | if (!result) { |
| | | sleep(200); |
| | | return; |
| | | return getIdleLoopDelayMs(); |
| | | } |
| | | |
| | | context.initialized = true; |
| | |
| | | context.pendingPathQueue.poll(); |
| | | context.addPassedStation(context.currentStationId); |
| | | context.lastStepAt = System.currentTimeMillis(); |
| | | context.lastProgressAt = context.lastStepAt; |
| | | context.lastProgressStationId = context.currentStationId; |
| | | guardedPublishTaskLocation(context.taskNo, currentDeviceNo, context.currentStationId, true, false); |
| | | traceEvent(deviceNo, context, "MOVE_INIT", "任务初始化起点站点", |
| | | buildDetails("stationId", context.currentStationId, "recovered", false), false); |
| | | sleep(500); |
| | | return Math.max(getInitializeDelayMs(), getMoveStepDurationMs()); |
| | | } |
| | | |
| | | private boolean executeNextMove(Integer deviceNo, TaskRuntimeContext context) { |
| | | private MoveStepResult executeNextMove(Integer deviceNo, TaskRuntimeContext context) { |
| | | Integer nextStationId = context.pendingPathQueue.peek(); |
| | | if (nextStationId == null || context.currentStationId == null) { |
| | | return true; |
| | | return MoveStepResult.continueNow(); |
| | | } |
| | | |
| | | Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId); |
| | | Integer nextDeviceNo = getDeviceNoByStationId(nextStationId); |
| | | Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId); |
| | | Integer nextDeviceNo = stateManager.getDeviceNoByStationId(nextStationId); |
| | | if (currentDeviceNo == null || nextDeviceNo == null) { |
| | | context.pendingPathQueue.poll(); |
| | | return true; |
| | | return MoveStepResult.continueNow(); |
| | | } |
| | | |
| | | boolean moveSuccess = stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo, |
| | | boolean moveSuccess = moveEngine.stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo, |
| | | nextStationId, nextDeviceNo, context.taskNo, context.finalTargetStationId); |
| | | if (moveSuccess) { |
| | | Integer previousStationId = context.currentStationId; |
| | |
| | | context.blockedStationId = null; |
| | | context.status = STATUS_RUNNING; |
| | | context.lastStepAt = System.currentTimeMillis(); |
| | | context.lastProgressAt = context.lastStepAt; |
| | | context.lastProgressStationId = context.currentStationId; |
| | | blockManager.clearBlocked(previousStationId); |
| | | blockManager.clearBlocked(nextStationId); |
| | | traceEvent(deviceNo, context, "MOVE_STEP_OK", "任务完成一步站点移动", |
| | | buildDetails("fromStationId", previousStationId, "toStationId", nextStationId, |
| | | "remainingPendingPath", context.getPendingStationIds()), |
| | | false); |
| | | sleep(1000); |
| | | return true; |
| | | return MoveStepResult.continueAfter(getBlockedLoopDelayMs()); |
| | | } |
| | | |
| | | if (!checkTaskNoInArea(context.taskNo) && getFakeAllowCheckBlock() |
| | | && !isSpecialStation(context.currentStationId) |
| | | && System.currentTimeMillis() - context.lastStepAt > getFakeRunBlockTimeoutMs()) { |
| | | boolean blocked = runBlockStation(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo, |
| | | long now = System.currentTimeMillis(); |
| | | boolean sameStationNoProgress = context.currentStationId != null |
| | | && context.currentStationId.equals(context.lastProgressStationId); |
| | | long timeoutMs = blockManager.getFakeRunBlockTimeoutMs(redisUtil); |
| | | long dwellMs = now - context.lastProgressAt; |
| | | if (!checkTaskNoInArea(context.taskNo) && blockManager.getFakeAllowCheckBlock(redisUtil) |
| | | && !blockManager.isSpecialStation(context.currentStationId) |
| | | && !blockManager.isBlocked(context.currentStationId) |
| | | && sameStationNoProgress |
| | | && dwellMs > timeoutMs) { |
| | | List<Integer> clearedPendingPath = context.getPendingStationIds(); |
| | | News.info("[WCS Debug] fake task清空待执行路径,原因=RUN_BLOCKED,deviceNo={},taskNo={},currentStationId={},targetStationId={},blockedStationId={},pendingBeforeClear={}", |
| | | deviceNo, context.taskNo, context.currentStationId, context.finalTargetStationId, |
| | | context.blockedStationId, clearedPendingPath); |
| | | context.clearPendingPathWithLog(deviceNo, context.taskNo, context.currentStationId, |
| | | context.finalTargetStationId, context.blockedStationId, "RUN_BLOCKED"); |
| | | guardedRunBlockStation(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; |
| | | } |
| | | traceEvent(deviceNo, context, "RUN_BLOCKED", "任务在当前站点停留超时,被标记为堵塞,待执行路径已清空", |
| | | buildDetails("blockedStationId", context.currentStationId, |
| | | "lastProgressStationId", context.lastProgressStationId, |
| | | "dwellMs", dwellMs, |
| | | "timeoutMs", timeoutMs, |
| | | "clearedPendingPath", clearedPendingPath), false); |
| | | return MoveStepResult.continueAfter(getMoveStepDurationMs()); |
| | | } |
| | | |
| | | sleep(500); |
| | | return true; |
| | | context.status = STATUS_WAITING; |
| | | traceEvent(deviceNo, context, "MOVE_STEP_WAIT", "当前站已更新目标但尚未完成落站,保持当前位置等待下一次推进", |
| | | buildDetails("currentStationId", context.currentStationId, |
| | | "nextStationId", nextStationId, |
| | | "targetStationId", context.finalTargetStationId, |
| | | "blockedStationId", context.blockedStationId, |
| | | "pendingPath", context.getPendingStationIds()), false); |
| | | return MoveStepResult.continueAfter(500L); |
| | | } |
| | | |
| | | private boolean handleIdleState(Integer deviceNo, TaskRuntimeContext context) { |
| | | private void resumeFromCurrentStation(Integer deviceNo, TaskRuntimeContext context, String mergeMode, |
| | | List<Integer> appendedPath, List<Integer> replacedFuturePath, boolean sourceCleared) { |
| | | if (context.currentStationId != null) { |
| | | Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId); |
| | | if (currentDeviceNo != null) { |
| | | guardedClearRunBlock(context.taskNo, context.currentStationId, currentDeviceNo); |
| | | guardedPublishTaskLocation(context.taskNo, currentDeviceNo, context.currentStationId, true, false); |
| | | } |
| | | } |
| | | context.blockedStationId = null; |
| | | context.status = STATUS_RUNNING; |
| | | context.lastProgressAt = System.currentTimeMillis(); |
| | | context.lastProgressStationId = context.currentStationId; |
| | | traceEvent(deviceNo, context, "BLOCK_RESET", "收到新的路径分段,已清除堵塞并重新计时", |
| | | buildDetails("mergeMode", mergeMode, "currentStationId", context.currentStationId, |
| | | "targetStationId", context.finalTargetStationId, "queueSize", context.pendingPathQueue.size(), |
| | | "appendedPath", appendedPath, "replacedFuturePath", replacedFuturePath, |
| | | "sourceCleared", sourceCleared), |
| | | false); |
| | | } |
| | | |
| | | private boolean shouldAdvanceFromCurrentState(TaskRuntimeContext context) { |
| | | return context != null |
| | | && context.initialized |
| | | && context.currentStationId != null |
| | | && !context.pendingPathQueue.isEmpty() |
| | | && !STATUS_BLOCKED.equals(context.status) |
| | | && !STATUS_CANCELLED.equals(context.status) |
| | | && !STATUS_TIMEOUT.equals(context.status) |
| | | && !STATUS_FINISHED.equals(context.status); |
| | | } |
| | | |
| | | private IdleStepResult handleIdleState(Integer deviceNo, TaskRuntimeContext context) { |
| | | if (shouldAdvanceFromCurrentState(context)) { |
| | | traceEvent(deviceNo, context, "IDLE_RESUME", "当前站点存在未完成路径,立即从当前站恢复推进", |
| | | buildDetails("currentStationId", context.currentStationId, |
| | | "targetStationId", context.finalTargetStationId, |
| | | "pendingPath", context.getPendingStationIds()), false); |
| | | context.status = STATUS_RUNNING; |
| | | return IdleStepResult.waitNext(1L); |
| | | } |
| | | |
| | | 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); |
| | | Integer targetDeviceNo = stateManager.getDeviceNoByStationId(context.finalTargetStationId); |
| | | if (targetDeviceNo != null) { |
| | | barcodeGenerated = generateStationBarcode(context.taskNo, context.finalTargetStationId, |
| | | barcodeGenerated = guardedGenerateStationBarcode(context.taskNo, context.finalTargetStationId, |
| | | targetDeviceNo); |
| | | } |
| | | } |
| | | context.arrivalHandled = true; |
| | | context.lastProgressAt = System.currentTimeMillis(); |
| | | context.lastProgressStationId = context.currentStationId; |
| | | traceEvent(deviceNo, context, "ARRIVED", "任务到达最终目标站点", |
| | | buildDetails("stationId", context.currentStationId, "barcodeGenerated", barcodeGenerated), |
| | | false); |
| | | } |
| | | |
| | | Integer targetDeviceNo = stateManager.getDeviceNoByStationId(context.finalTargetStationId); |
| | | if (targetDeviceNo != null) { |
| | | boolean ownerConflict = stateManager.isFinalStationOwnerConflict(targetDeviceNo, context.finalTargetStationId, |
| | | context.taskNo); |
| | | if (ownerConflict) { |
| | | logFinalStationOwnershipLost(deviceNo, context); |
| | | context.status = STATUS_BLOCKED; |
| | | return IdleStepResult.waitNext(getMoveStepDurationMs()); |
| | | } |
| | | boolean stationCleared = stateManager.isStationClearedForTask(targetDeviceNo, context.finalTargetStationId, |
| | | context.taskNo); |
| | | boolean crnTaken = isCrnTakenByTask(context.taskNo); |
| | | if (stationCleared || crnTaken) { |
| | | context.status = STATUS_FINISHED; |
| | | return true; |
| | | traceEvent(deviceNo, context, "TASK_COMPLETE", "堆垛机已取走货物,任务完成", |
| | | buildDetails("stationId", context.finalTargetStationId, "stationCleared", stationCleared, "crnTaken", crnTaken), false); |
| | | return IdleStepResult.finish(); |
| | | } |
| | | } |
| | | if (targetDeviceNo != null && !guardedArrivalCompletion(targetDeviceNo, context)) { |
| | | context.status = STATUS_BLOCKED; |
| | | return IdleStepResult.waitNext(getMoveStepDurationMs()); |
| | | } |
| | | |
| | | long dwellMs = System.currentTimeMillis() - context.lastProgressAt; |
| | | long timeoutMs = blockManager.getFakeRunBlockTimeoutMs(redisUtil); |
| | | if (!checkTaskNoInArea(context.taskNo) && blockManager.getFakeAllowCheckBlock(redisUtil) |
| | | && !blockManager.isSpecialStation(context.currentStationId) |
| | | && !blockManager.isBlocked(context.currentStationId) |
| | | && dwellMs > timeoutMs) { |
| | | context.status = STATUS_BLOCKED; |
| | | context.clearPendingPathWithLog(deviceNo, context.taskNo, context.currentStationId, |
| | | context.finalTargetStationId, context.blockedStationId, "TARGET_RUN_BLOCKED"); |
| | | guardedRunBlockStation(context.taskNo, context.currentStationId, targetDeviceNo, context.taskNo, |
| | | context.currentStationId); |
| | | context.blockedStationId = context.currentStationId; |
| | | traceEvent(deviceNo, context, "RUN_BLOCKED", "任务到达终点站后停留超时,被标记为堵塞,等待堆垛机取货", |
| | | buildDetails("blockedStationId", context.currentStationId, |
| | | "lastProgressStationId", context.lastProgressStationId, |
| | | "dwellMs", dwellMs, |
| | | "timeoutMs", timeoutMs), false); |
| | | return IdleStepResult.waitNext(getMoveStepDurationMs()); |
| | | } |
| | | |
| | | return IdleStepResult.waitNext(getFinishDelayMs()); |
| | | } |
| | | |
| | | Long lastTime = taskLastUpdateTime.get(context.taskNo); |
| | | if (lastTime != null && System.currentTimeMillis() - lastTime > WAIT_SEGMENT_TIMEOUT_MS) { |
| | | context.status = STATUS_TIMEOUT; |
| | | long waitSegmentTimeoutMs = getWaitSegmentTimeoutMs(); |
| | | if (lastTime != null && System.currentTimeMillis() - lastTime > waitSegmentTimeoutMs) { |
| | | traceEvent(deviceNo, context, "WAIT_TIMEOUT", "等待新的路径分段超时", |
| | | buildDetails("timeoutMs", WAIT_SEGMENT_TIMEOUT_MS, "currentStationId", context.currentStationId, |
| | | buildDetails("timeoutMs", waitSegmentTimeoutMs, "currentStationId", context.currentStationId, |
| | | "targetStationId", context.finalTargetStationId), |
| | | false); |
| | | return true; |
| | | taskLastUpdateTime.put(context.taskNo, System.currentTimeMillis()); |
| | | } |
| | | return false; |
| | | return IdleStepResult.waitNext(getIdleLoopDelayMs()); |
| | | } |
| | | |
| | | private List<Integer> normalizePath(List<Integer> path) { |
| | |
| | | } |
| | | } |
| | | |
| | | private boolean hasTaskReset(Integer taskNo) { |
| | | if (redisUtil == null || taskNo == null) { |
| | | return false; |
| | | private List<Integer> rebuildPendingPathFromCurrent(TaskRuntimeContext context, List<Integer> newPath) { |
| | | List<Integer> rebuilt = new ArrayList<Integer>(); |
| | | if (newPath == null || newPath.isEmpty()) { |
| | | return rebuilt; |
| | | } |
| | | Object cancel = redisUtil.get(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo); |
| | | return cancel != null; |
| | | Integer currentStationId = context.currentStationId; |
| | | int startIndex = 0; |
| | | if (currentStationId != null) { |
| | | int currentIndex = newPath.indexOf(currentStationId); |
| | | if (currentIndex < 0) { |
| | | return rebuilt; |
| | | } |
| | | startIndex = currentIndex + 1; |
| | | } |
| | | for (int i = startIndex; i < newPath.size(); i++) { |
| | | rebuilt.add(newPath.get(i)); |
| | | } |
| | | return rebuilt; |
| | | } |
| | | |
| | | private void replacePendingPathQueue(TaskRuntimeContext context, List<Integer> futurePath) { |
| | | context.pendingPathQueue.clear(); |
| | | if (futurePath == null) { |
| | | return; |
| | | } |
| | | for (Integer stationId : futurePath) { |
| | | context.pendingPathQueue.offer(stationId); |
| | | } |
| | | } |
| | | |
| | | private boolean hasTaskReset(Integer taskNo) { |
| | | // 仿真系统不响应外部取消信号(如堵塞重路由触发的 signalSegmentReset), |
| | | // 避免任务在站点行走过程中被意外清除 |
| | | return false; |
| | | } |
| | | |
| | | private Integer getLastInQueue(LinkedBlockingQueue<Integer> queue) { |
| | |
| | | if (currentStationId == null || targetStationId == null) { |
| | | return; |
| | | } |
| | | Integer currentDeviceNo = getDeviceNoByStationId(currentStationId); |
| | | Integer currentDeviceNo = stateManager.getDeviceNoByStationId(currentStationId); |
| | | if (currentDeviceNo == null) { |
| | | return; |
| | | } |
| | | |
| | | lockStations(currentStationId); |
| | | moveEngine.lockStations(currentStationId); |
| | | try { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentDeviceNo); |
| | | if (statusList == null) { |
| | | return; |
| | | } |
| | | |
| | | ZyStationStatusEntity currentStatus = statusList.stream() |
| | | .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null); |
| | | ZyStationStatusEntity currentStatus = stateManager.findStationStatus(currentDeviceNo, currentStationId); |
| | | if (currentStatus == null) { |
| | | return; |
| | | } |
| | | |
| | | if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0 |
| | | && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) { |
| | | if (hasOwnerConflict(currentStatus, taskNo)) { |
| | | logOwnerConflict("syncCurrentStationTarget", currentStationId, currentStatus, taskNo, targetStationId, false, |
| | | "owner_conflict"); |
| | | return; |
| | | } |
| | | |
| | | updateStationDataInternal(currentStationId, currentDeviceNo, taskNo, targetStationId, null, null, null); |
| | | stateManager.updateStationDataInternal(currentStationId, currentDeviceNo, taskNo, targetStationId, null, null, null); |
| | | } finally { |
| | | unlockStations(currentStationId); |
| | | moveEngine.unlockStations(currentStationId); |
| | | } |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private boolean getFakeAllowCheckBlock() { |
| | | boolean fakeAllowCheckBlock = true; |
| | | 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 && !"Y".equals(value)) { |
| | | fakeAllowCheckBlock = false; |
| | | } |
| | | } |
| | | return fakeAllowCheckBlock; |
| | | private boolean hasOwnerConflict(ZyStationStatusEntity currentStatus, Integer incomingTaskNo) { |
| | | return currentStatus != null |
| | | && currentStatus.isLoading() |
| | | && currentStatus.getTaskNo() != null |
| | | && currentStatus.getTaskNo() > 0 |
| | | && (incomingTaskNo == null || !currentStatus.getTaskNo().equals(incomingTaskNo)); |
| | | } |
| | | |
| | | private boolean isSpecialStation(Integer stationId) { |
| | | private void logOwnerConflict(String operation, Integer stationId, ZyStationStatusEntity currentStatus, |
| | | Integer incomingTaskNo, Integer incomingTargetStationId, boolean finalStation, String reason) { |
| | | News.info("[WCS Debug] fake station owner冲突,operation={},stationId={},currentTaskNo={},currentLoading={},currentTargetStaNo={},incomingTaskNo={},incomingTargetStaNo={},finalStation={},reason={}", |
| | | operation, |
| | | stationId, |
| | | currentStatus == null ? null : currentStatus.getTaskNo(), |
| | | currentStatus == null ? null : currentStatus.isLoading(), |
| | | currentStatus == null ? null : currentStatus.getTargetStaNo(), |
| | | incomingTaskNo, |
| | | incomingTargetStationId, |
| | | finalStation, |
| | | reason); |
| | | } |
| | | |
| | | private boolean ensureStationWritable(String operation, Integer deviceNo, Integer stationId, |
| | | Integer incomingTaskNo, Integer incomingTargetStationId, boolean finalStation) { |
| | | if (deviceNo == null || stationId == null) { |
| | | return false; |
| | | } |
| | | ZyStationStatusEntity currentStatus = stateManager.findStationStatus(deviceNo, stationId); |
| | | if (currentStatus == null) { |
| | | return false; |
| | | } |
| | | if (!hasOwnerConflict(currentStatus, incomingTaskNo)) { |
| | | return true; |
| | | } |
| | | logOwnerConflict(operation, stationId, currentStatus, incomingTaskNo, incomingTargetStationId, finalStation, |
| | | finalStation ? "final_station_owner_conflict" : "owner_conflict"); |
| | | return false; |
| | | } |
| | | |
| | | private boolean ensureStationClearable(String operation, Integer deviceNo, Integer stationId, Integer expectedTaskNo, |
| | | boolean finalStation) { |
| | | if (deviceNo == null || stationId == null) { |
| | | return false; |
| | | } |
| | | ZyStationStatusEntity currentStatus = stateManager.findStationStatus(deviceNo, stationId); |
| | | if (currentStatus == null) { |
| | | return false; |
| | | } |
| | | if (!hasOwnerConflict(currentStatus, expectedTaskNo)) { |
| | | return true; |
| | | } |
| | | if (isLegalClearStation(stationId)) { |
| | | return true; |
| | | } |
| | | logOwnerConflict(operation, stationId, currentStatus, expectedTaskNo, currentStatus.getTargetStaNo(), finalStation, |
| | | "illegal_clear_attempt"); |
| | | return false; |
| | | } |
| | | |
| | | private boolean isLegalClearStation(Integer stationId) { |
| | | if (stationId == null) { |
| | | return false; |
| | | } |
| | | BasDevpService basDevpService = SpringUtils.getBean(BasDevpService.class); |
| | | List<BasDevp> basDevps = basDevpService.list(); |
| | | Set<Integer> specialStationIds = new HashSet<>(); |
| | | for (BasDevp basDevp : basDevps) { |
| | | for (StationObjModel station : basDevp.getInStationList$()) { |
| | | specialStationIds.add(station.getStationId()); |
| | | Set<Integer> stationIds = legalClearStationIds; |
| | | if (stationIds == null || stationIds.isEmpty()) { |
| | | refreshLegalClearStationIds(); |
| | | stationIds = legalClearStationIds; |
| | | } |
| | | for (StationObjModel station : basDevp.getOutStationList$()) { |
| | | specialStationIds.add(station.getStationId()); |
| | | } |
| | | for (StationObjModel station : basDevp.getBarcodeStationList$()) { |
| | | specialStationIds.add(station.getStationId()); |
| | | } |
| | | } |
| | | return specialStationIds.contains(stationId); |
| | | return stationIds != null && stationIds.contains(stationId); |
| | | } |
| | | |
| | | private long getFakeRunBlockTimeoutMs() { |
| | | long timeoutMs = DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS; |
| | | 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"); |
| | | if (value != null) { |
| | | private boolean isBarcodeStation(Integer stationId) { |
| | | if (stationId == null) { |
| | | return false; |
| | | } |
| | | Set<Integer> stationIds = barcodeStationIds; |
| | | if (stationIds == null || stationIds.isEmpty()) { |
| | | refreshBarcodeStationIds(); |
| | | stationIds = barcodeStationIds; |
| | | } |
| | | return stationIds != null && stationIds.contains(stationId); |
| | | } |
| | | |
| | | private void refreshLegalClearStationIds() { |
| | | try { |
| | | long parsed = Long.parseLong(String.valueOf(value).trim()); |
| | | if (parsed > 0) { |
| | | timeoutMs = parsed; |
| | | Set<Integer> stationIds = new HashSet<Integer>(); |
| | | |
| | | com.zy.asrs.service.BasCrnpService basCrnpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasCrnpService.class); |
| | | List<com.zy.asrs.entity.BasCrnp> basCrnps = basCrnpService.list(); |
| | | for (com.zy.asrs.entity.BasCrnp basCrnp : basCrnps) { |
| | | if (basCrnp == null) { |
| | | continue; |
| | | } |
| | | } catch (Exception ignore) { |
| | | collectStationIds(stationIds, basCrnp.getInStationList$()); |
| | | collectStationIds(stationIds, basCrnp.getOutStationList$()); |
| | | } |
| | | |
| | | com.zy.asrs.service.BasDevpService basDevpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasDevpService.class); |
| | | List<com.zy.asrs.entity.BasDevp> basDevps = basDevpService.list(); |
| | | for (com.zy.asrs.entity.BasDevp basDevp : basDevps) { |
| | | if (basDevp == null) { |
| | | continue; |
| | | } |
| | | collectStationIds(stationIds, basDevp.getBarcodeStationList$()); |
| | | collectStationIds(stationIds, basDevp.getInStationList$()); |
| | | collectStationIds(stationIds, basDevp.getOutStationList$()); |
| | | } |
| | | |
| | | legalClearStationIds = stationIds; |
| | | } catch (Exception e) { |
| | | News.info("[WCS Debug] fake 合法清站白名单刷新失败,异常类型={},异常信息={}", |
| | | e.getClass().getSimpleName(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void refreshBarcodeStationIds() { |
| | | try { |
| | | Set<Integer> stationIds = new HashSet<Integer>(); |
| | | com.zy.asrs.service.BasDevpService basDevpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasDevpService.class); |
| | | List<com.zy.asrs.entity.BasDevp> basDevps = basDevpService.list(); |
| | | for (com.zy.asrs.entity.BasDevp basDevp : basDevps) { |
| | | if (basDevp == null) { |
| | | continue; |
| | | } |
| | | collectStationIds(stationIds, basDevp.getBarcodeStationList$()); |
| | | } |
| | | barcodeStationIds = stationIds; |
| | | } catch (Exception e) { |
| | | News.info("[WCS Debug] fake 条码站白名单刷新失败,异常类型={},异常信息={}", |
| | | e.getClass().getSimpleName(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void collectStationIds(Set<Integer> stationIds, List<com.zy.core.model.StationObjModel> stations) { |
| | | if (stationIds == null || stations == null || stations.isEmpty()) { |
| | | return; |
| | | } |
| | | for (com.zy.core.model.StationObjModel station : stations) { |
| | | if (station != null && station.getStationId() != null) { |
| | | stationIds.add(station.getStationId()); |
| | | } |
| | | } |
| | | } |
| | | return timeoutMs; |
| | | |
| | | private boolean isFinalStation(Integer taskNo, Integer stationId) { |
| | | TaskRuntimeContext context = taskNo == null ? null : taskContexts.get(taskNo); |
| | | return context != null && stationId != null && stationId.equals(context.finalTargetStationId); |
| | | } |
| | | |
| | | private void logFinalStationOwnershipLost(Integer deviceNo, TaskRuntimeContext context) { |
| | | Integer stationId = context == null ? null : context.finalTargetStationId; |
| | | Integer targetDeviceNo = stateManager.getDeviceNoByStationId(stationId); |
| | | ZyStationStatusEntity currentStatus = targetDeviceNo == null ? null : stateManager.snapshotStation(targetDeviceNo, stationId); |
| | | News.info("[WCS Debug] fake 最终站所有权丢失,taskNo={},stationId={},currentTaskNo={},currentLoading={},currentTargetStaNo={},reason={}", |
| | | context == null ? null : context.taskNo, |
| | | stationId, |
| | | currentStatus == null ? null : currentStatus.getTaskNo(), |
| | | currentStatus == null ? null : currentStatus.isLoading(), |
| | | currentStatus == null ? null : currentStatus.getTargetStaNo(), |
| | | "final_station_owner_conflict"); |
| | | traceEvent(deviceNo, context, "FINAL_STATION_OWNER_CONFLICT", "最终站仍有物但所有者已被其他任务覆盖", |
| | | buildDetails("stationId", stationId, |
| | | "currentTaskNo", currentStatus == null ? null : currentStatus.getTaskNo(), |
| | | "currentLoading", currentStatus == null ? null : currentStatus.isLoading(), |
| | | "currentTargetStaNo", currentStatus == null ? null : currentStatus.getTargetStaNo()), false); |
| | | } |
| | | |
| | | private boolean guardedClearStationForDispatch(Integer deviceNo, Integer stationId, Integer expectedTaskNo, String reason) { |
| | | if (!ensureStationClearable("clearStationForDispatch", deviceNo, stationId, expectedTaskNo, |
| | | isFinalStation(expectedTaskNo, stationId))) { |
| | | return false; |
| | | } |
| | | stateManager.clearStationForDispatch(deviceNo, stationId, reason); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedResetStation(Integer deviceNo, Integer stationId, Integer expectedTaskNo) { |
| | | if (!ensureStationClearable("resetStation", deviceNo, stationId, expectedTaskNo, |
| | | isFinalStation(expectedTaskNo, stationId))) { |
| | | return false; |
| | | } |
| | | stateManager.resetStation(deviceNo, stationId); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedUpdateStationBarcode(Integer deviceNo, Integer stationId, Integer taskNo, String barcode) { |
| | | if (!ensureStationWritable("updateStationBarcode", deviceNo, stationId, taskNo, null, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | stateManager.updateStationBarcode(deviceNo, stationId, barcode); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedGenerateFakeOutStationData(Integer deviceNo, Integer stationId, Integer taskNo) { |
| | | if (!ensureStationWritable("generateFakeOutStationData", deviceNo, stationId, taskNo, null, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | stateManager.generateFakeOutStationData(deviceNo, stationId); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedHandoffBarcodeStation(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) { |
| | | if (!ensureStationWritable("handoffBarcodeStation", deviceNo, stationId, taskNo, targetStationId, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | stateManager.handoffBarcodeStation(deviceNo, taskNo, stationId, targetStationId); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedRunBlockStation(Integer taskNo, Integer stationId, Integer deviceNo, Integer ownerTaskNo, |
| | | Integer targetStationId) { |
| | | if (!ensureStationWritable("runBlockStation", deviceNo, stationId, ownerTaskNo, targetStationId, |
| | | isFinalStation(ownerTaskNo, stationId))) { |
| | | return false; |
| | | } |
| | | blockManager.runBlockStation(taskNo, stationId, deviceNo, ownerTaskNo, targetStationId); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedClearRunBlock(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | if (!ensureStationWritable("clearRunBlock", deviceNo, stationId, taskNo, null, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | blockManager.clearRunBlock(stationId, deviceNo); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedPublishTaskLocation(Integer taskNo, Integer deviceNo, Integer stationId, boolean loading, boolean runBlock) { |
| | | if (loading && !ensureStationWritable("publishTaskLocation", deviceNo, stationId, taskNo, null, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | stateManager.publishTaskLocation(taskNo, deviceNo, stationId, loading, runBlock); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedGenerateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) { |
| | | if (!ensureStationWritable("generateStationData", deviceNo, stationId, taskNo, targetStationId, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | stateManager.generateStationData(deviceNo, taskNo, stationId, targetStationId); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedSyncCurrentStationTarget(Integer taskNo, Integer currentStationId, Integer targetStationId) { |
| | | syncCurrentStationTarget(taskNo, currentStationId, targetStationId); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedClearTaskLocationIfMatches(Integer taskNo, Integer deviceNo, Integer stationId) { |
| | | stateManager.clearTaskLocationIfMatches(taskNo, deviceNo, stationId); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedPublishCurrentLocation(Integer taskNo, Integer deviceNo, Integer stationId) { |
| | | return guardedPublishTaskLocation(taskNo, deviceNo, stationId, true, false); |
| | | } |
| | | |
| | | private boolean guardedGenerateStationBarcode(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | if (!ensureStationWritable("generateStationBarcode", deviceNo, stationId, taskNo, null, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | return stateManager.generateStationBarcode(taskNo, stationId, deviceNo); |
| | | } |
| | | |
| | | private boolean guardedUpdateStationData(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStationId, |
| | | Boolean loading, String barcode, Boolean runBlock) { |
| | | if ((taskNo != null || Boolean.TRUE.equals(loading)) |
| | | && !ensureStationWritable("updateStationData", deviceNo, stationId, taskNo, targetStationId, |
| | | isFinalStation(taskNo, stationId))) { |
| | | return false; |
| | | } |
| | | return stateManager.updateStationDataInternal(stationId, deviceNo, taskNo, targetStationId, loading, barcode, runBlock); |
| | | } |
| | | |
| | | private boolean isFinalStationConflict(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | return isFinalStation(taskNo, stationId) && stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo); |
| | | } |
| | | |
| | | private boolean guardedResetOrClearBlockedStation(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | return ensureStationClearable("blockedStationClear", deviceNo, stationId, taskNo, isFinalStation(taskNo, stationId)); |
| | | } |
| | | |
| | | private boolean guardedStationOccupied(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo); |
| | | } |
| | | |
| | | private boolean guardedFinalStationOwnership(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | return !stateManager.isFinalStationOwnerConflict(deviceNo, stationId, taskNo); |
| | | } |
| | | |
| | | private boolean guardedFinalStationWritable(Integer taskNo, Integer stationId, Integer deviceNo, String operation) { |
| | | if (!guardedFinalStationOwnership(taskNo, stationId, deviceNo)) { |
| | | ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId); |
| | | logOwnerConflict(operation, stationId, currentStatus, taskNo, |
| | | currentStatus == null ? null : currentStatus.getTargetStaNo(), true, |
| | | "final_station_owner_conflict"); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedFinalStationClearable(Integer taskNo, Integer stationId, Integer deviceNo, String operation) { |
| | | if (!guardedFinalStationOwnership(taskNo, stationId, deviceNo)) { |
| | | ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId); |
| | | logOwnerConflict(operation, stationId, currentStatus, taskNo, |
| | | currentStatus == null ? null : currentStatus.getTargetStaNo(), true, |
| | | "illegal_clear_attempt"); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean shouldTreatAsFinalStationConflict(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | return isFinalStation(taskNo, stationId) && stateManager.isFinalStationOwnerConflict(deviceNo, stationId, taskNo); |
| | | } |
| | | |
| | | private boolean guardedFinalStationMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) { |
| | | if (shouldTreatAsFinalStationConflict(taskNo, stationId, deviceNo)) { |
| | | ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId); |
| | | logOwnerConflict(operation, stationId, currentStatus, taskNo, targetStationId, true, |
| | | "final_station_owner_conflict"); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedFinalStationClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | if (shouldTreatAsFinalStationConflict(taskNo, stationId, deviceNo)) { |
| | | ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId); |
| | | logOwnerConflict(operation, stationId, currentStatus, taskNo, |
| | | currentStatus == null ? null : currentStatus.getTargetStaNo(), true, |
| | | "illegal_clear_attempt"); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedStationWrite(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) { |
| | | if (!guardedFinalStationMutation(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | return ensureStationWritable(operation, deviceNo, stationId, taskNo, targetStationId, isFinalStation(taskNo, stationId)); |
| | | } |
| | | |
| | | private boolean guardedStationClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | if (!guardedFinalStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | return ensureStationClearable(operation, deviceNo, stationId, taskNo, isFinalStation(taskNo, stationId)); |
| | | } |
| | | |
| | | private boolean guardedStateMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, |
| | | Runnable mutation) { |
| | | if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedStateClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Runnable mutation) { |
| | | if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedFinalStationCompletion(Integer deviceNo, TaskRuntimeContext context) { |
| | | if (context == null || context.finalTargetStationId == null || context.taskNo == null) { |
| | | return false; |
| | | } |
| | | return !stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo); |
| | | } |
| | | |
| | | private boolean guardedCurrentStationOwnership(Integer taskNo, Integer currentStationId, Integer currentDeviceNo) { |
| | | return !stateManager.isOccupiedByOtherLoadingTask(currentDeviceNo, currentStationId, taskNo); |
| | | } |
| | | |
| | | private boolean guardedTargetStationOwnership(Integer taskNo, Integer stationId, Integer deviceNo) { |
| | | return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo); |
| | | } |
| | | |
| | | private boolean guardedCommandWrite(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, Runnable mutation) { |
| | | if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedCommandClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Runnable mutation) { |
| | | if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedRunBlockMutation(Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) { |
| | | return guardedStationWrite("runBlockStation", taskNo, stationId, deviceNo, targetStationId); |
| | | } |
| | | |
| | | private boolean guardedClearMutation(Integer taskNo, Integer stationId, Integer deviceNo, String operation) { |
| | | return guardedStationClear(operation, taskNo, stationId, deviceNo); |
| | | } |
| | | |
| | | private boolean guardedArrivalOwnership(Integer deviceNo, TaskRuntimeContext context) { |
| | | return context != null && context.finalTargetStationId != null && context.taskNo != null |
| | | && !stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo); |
| | | } |
| | | |
| | | private boolean guardedArrivalStationWritable(Integer deviceNo, TaskRuntimeContext context, String operation) { |
| | | if (!guardedArrivalOwnership(deviceNo, context)) { |
| | | logFinalStationOwnershipLost(deviceNo, context); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedArrivalStationClearable(Integer deviceNo, TaskRuntimeContext context, String operation) { |
| | | if (!guardedArrivalOwnership(deviceNo, context)) { |
| | | logFinalStationOwnershipLost(deviceNo, context); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedStationMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, |
| | | Runnable mutation, boolean clearOperation) { |
| | | boolean allowed = clearOperation |
| | | ? guardedStationClear(operation, taskNo, stationId, deviceNo) |
| | | : guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId); |
| | | if (!allowed) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedFinalArrivalState(Integer deviceNo, TaskRuntimeContext context) { |
| | | if (context == null || context.finalTargetStationId == null || context.taskNo == null) { |
| | | return false; |
| | | } |
| | | if (stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo)) { |
| | | logFinalStationOwnershipLost(deviceNo, context); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedArrivalCompletion(Integer deviceNo, TaskRuntimeContext context) { |
| | | return guardedFinalArrivalState(deviceNo, context); |
| | | } |
| | | |
| | | private boolean guardedStationOwnership(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, |
| | | boolean clearOperation) { |
| | | return clearOperation |
| | | ? guardedStationClear(operation, taskNo, stationId, deviceNo) |
| | | : guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId); |
| | | } |
| | | |
| | | private boolean guardedStationMutationIfAllowed(String operation, Integer taskNo, Integer stationId, Integer deviceNo, |
| | | Integer targetStationId, boolean clearOperation, Runnable mutation) { |
| | | if (!guardedStationOwnership(operation, taskNo, stationId, deviceNo, targetStationId, clearOperation)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedArrivalStationState(Integer deviceNo, TaskRuntimeContext context) { |
| | | return guardedFinalArrivalState(deviceNo, context); |
| | | } |
| | | |
| | | private boolean guardedMutateFinalStation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, |
| | | Integer targetStationId, Runnable mutation) { |
| | | if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedClearFinalStation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, |
| | | Runnable mutation) { |
| | | if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedArrivalState(Integer deviceNo, TaskRuntimeContext context) { |
| | | if (!guardedFinalArrivalState(deviceNo, context)) { |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedCurrentStationWrite(Integer taskNo, Integer currentStationId, Integer currentDeviceNo, Integer targetStationId, |
| | | String operation) { |
| | | return guardedStationWrite(operation, taskNo, currentStationId, currentDeviceNo, targetStationId); |
| | | } |
| | | |
| | | private boolean guardedCurrentStationClear(Integer taskNo, Integer currentStationId, Integer currentDeviceNo, String operation) { |
| | | return guardedStationClear(operation, taskNo, currentStationId, currentDeviceNo); |
| | | } |
| | | |
| | | private boolean guardedFinalStationWrite(Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, String operation) { |
| | | return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId); |
| | | } |
| | | |
| | | private boolean guardedFinalStationClear(Integer taskNo, Integer stationId, Integer deviceNo, String operation) { |
| | | return guardedStationClear(operation, taskNo, stationId, deviceNo); |
| | | } |
| | | |
| | | private boolean guardedStationBarcodeWrite(Integer deviceNo, Integer stationId, Integer taskNo, String barcode) { |
| | | return guardedUpdateStationBarcode(deviceNo, stationId, taskNo, barcode); |
| | | } |
| | | |
| | | private boolean guardedStationOccupancyWrite(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId, |
| | | Runnable mutation, String operation) { |
| | | if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedStationOccupancyClear(Integer deviceNo, Integer stationId, Integer taskNo, |
| | | Runnable mutation, String operation) { |
| | | if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedStationWriteForTask(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId, |
| | | String operation) { |
| | | return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId); |
| | | } |
| | | |
| | | private boolean guardedStationClearForTask(Integer deviceNo, Integer stationId, Integer taskNo, String operation) { |
| | | return guardedStationClear(operation, taskNo, stationId, deviceNo); |
| | | } |
| | | |
| | | private boolean guardedMutateStationIfOwned(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId, |
| | | String operation, Runnable mutation) { |
| | | if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedClearStationIfOwned(Integer deviceNo, Integer stationId, Integer taskNo, String operation, |
| | | Runnable mutation) { |
| | | if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedArrivalVisibility(Integer deviceNo, TaskRuntimeContext context) { |
| | | return guardedFinalArrivalState(deviceNo, context); |
| | | } |
| | | |
| | | private boolean guardedFinalStationNotOverwritten(Integer deviceNo, TaskRuntimeContext context) { |
| | | return guardedFinalArrivalState(deviceNo, context); |
| | | } |
| | | |
| | | private boolean guardedMutateOccupiedStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo, |
| | | Integer targetStationId, Runnable mutation) { |
| | | if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedClearOccupiedStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo, |
| | | Runnable mutation) { |
| | | if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedFinalStationStillOwned(Integer deviceNo, TaskRuntimeContext context) { |
| | | return guardedFinalArrivalState(deviceNo, context); |
| | | } |
| | | |
| | | private boolean guardedStateWrite(String operation, Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId) { |
| | | return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId); |
| | | } |
| | | |
| | | private boolean guardedStateClear(String operation, Integer deviceNo, Integer stationId, Integer taskNo) { |
| | | return guardedStationClear(operation, taskNo, stationId, deviceNo); |
| | | } |
| | | |
| | | private boolean guardedArrivalOwnerCheck(Integer deviceNo, TaskRuntimeContext context) { |
| | | return guardedFinalArrivalState(deviceNo, context); |
| | | } |
| | | |
| | | private boolean guardedArrivalConflict(Integer deviceNo, TaskRuntimeContext context) { |
| | | return stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo); |
| | | } |
| | | |
| | | private boolean guardedCurrentOwnerCheck(Integer deviceNo, Integer stationId, Integer taskNo) { |
| | | return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo); |
| | | } |
| | | |
| | | private boolean guardedMutateStationState(String operation, Integer deviceNo, Integer stationId, Integer taskNo, |
| | | Integer targetStationId, Runnable mutation) { |
| | | if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedClearStationState(String operation, Integer deviceNo, Integer stationId, Integer taskNo, |
| | | Runnable mutation) { |
| | | if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) { |
| | | return false; |
| | | } |
| | | mutation.run(); |
| | | return true; |
| | | } |
| | | |
| | | private boolean guardedArrivalCompletionState(Integer deviceNo, TaskRuntimeContext context) { |
| | | return guardedFinalArrivalState(deviceNo, context); |
| | | } |
| | | |
| | | private boolean guardedWriteToStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId) { |
| | | return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId); |
| | | } |
| | | |
| | | private boolean guardedClearFromStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo) { |
| | | return guardedStationClear(operation, taskNo, stationId, deviceNo); |
| | | } |
| | | |
| | | private void handleCommand(Integer deviceNo, StationCommand command) { |
| | | News.info("[WCS Debug] 站点仿真模拟(V3)已启动,命令数据={}", JSON.toJSONString(command)); |
| | | News.info("[WCS Debug] fake 非MOVE命令进入device串行执行,deviceNo={},stationId={},targetStaNo={},commandType={},命令数据={}", |
| | | deviceNo, command.getStationId(), command.getTargetStaNo(), command.getCommandType(), JSON.toJSONString(command)); |
| | | Integer taskNo = command.getTaskNo(); |
| | | Integer stationId = command.getStationId(); |
| | | Integer targetStationId = command.getTargetStaNo(); |
| | | |
| | | if (command.getCommandType() == StationCommandType.RESET) { |
| | | resetStation(deviceNo, stationId); |
| | | boolean reset = guardedResetStation(deviceNo, stationId, taskNo); |
| | | News.info("[WCS Debug] fake RESET已通过统一站点锁执行,deviceNo={},stationId={},accepted={}", deviceNo, stationId, reset); |
| | | return; |
| | | } |
| | | |
| | | if (command.getCommandType() == StationCommandType.WRITE_INFO) { |
| | | if (command.getBarcode() != null) { |
| | | updateStationBarcode(deviceNo, stationId, command.getBarcode()); |
| | | boolean updated = guardedUpdateStationBarcode(deviceNo, stationId, taskNo, command.getBarcode()); |
| | | News.info("[WCS Debug] fake WRITE_INFO条码写入已通过统一站点锁执行,deviceNo={},stationId={},barcode={},accepted={}", |
| | | deviceNo, stationId, command.getBarcode(), updated); |
| | | return; |
| | | } |
| | | if (taskNo == 9998 && targetStationId == 0) { |
| | | generateFakeOutStationData(deviceNo, stationId); |
| | | boolean generated = guardedGenerateFakeOutStationData(deviceNo, stationId, taskNo); |
| | | News.info("[WCS Debug] fake WRITE_INFO有物写入已通过统一站点锁执行,deviceNo={},stationId={},accepted={}", deviceNo, stationId, generated); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null |
| | | && stationId.equals(targetStationId)) { |
| | | generateStationData(deviceNo, taskNo, stationId, targetStationId); |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | status.setBarcode(""); |
| | | } |
| | | } |
| | | |
| | | private void updateStationBarcode(Integer deviceNo, Integer stationId, String barcode) { |
| | | 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.setBarcode(barcode); |
| | | } |
| | | } |
| | | |
| | | private Integer getDeviceNoByStationId(Integer stationId) { |
| | | for (Map.Entry<Integer, List<ZyStationStatusEntity>> entry : deviceStatusMap.entrySet()) { |
| | | List<ZyStationStatusEntity> list = entry.getValue(); |
| | | if (list == null) { |
| | | continue; |
| | | } |
| | | for (ZyStationStatusEntity entity : list) { |
| | | if (entity.getStationId() != null && entity.getStationId().equals(stationId)) { |
| | | return entry.getKey(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Integer findCurrentStationIdByTask(Integer taskNo) { |
| | | for (List<ZyStationStatusEntity> list : deviceStatusMap.values()) { |
| | | if (list == null) { |
| | | continue; |
| | | } |
| | | for (ZyStationStatusEntity entity : list) { |
| | | if (entity.getTaskNo() != null && entity.getTaskNo().equals(taskNo) && entity.isLoading()) { |
| | | return entity.getStationId(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void sleep(long ms) { |
| | | try { |
| | | Thread.sleep(ms); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | } |
| | | } |
| | | |
| | | private ReentrantLock getStationLock(Integer stationId) { |
| | | return stationLocks.computeIfAbsent(stationId, key -> new ReentrantLock()); |
| | | } |
| | | |
| | | private void lockStations(Integer... stationIds) { |
| | | Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length); |
| | | Arrays.sort(sorted); |
| | | for (Integer stationId : sorted) { |
| | | getStationLock(stationId).lock(); |
| | | } |
| | | } |
| | | |
| | | private void unlockStations(Integer... stationIds) { |
| | | Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length); |
| | | Arrays.sort(sorted); |
| | | for (int i = sorted.length - 1; i >= 0; i--) { |
| | | getStationLock(sorted[i]).unlock(); |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | 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; |
| | | } |
| | | if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0 |
| | | && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) { |
| | | return false; |
| | | } |
| | | |
| | | return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, targetStationId, |
| | | isLoading, barcode, false); |
| | | } finally { |
| | | unlockStations(currentStationId); |
| | | } |
| | | } |
| | | |
| | | public boolean stationMoveToNext(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo, |
| | | Integer nextStationId, Integer nextStationDeviceNo, Integer taskNo, Integer targetStaNo) { |
| | | lockStations(currentStationId, nextStationId); |
| | | try { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo); |
| | | List<ZyStationStatusEntity> nextStatusList = deviceStatusMap.get(nextStationDeviceNo); |
| | | 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() != null && nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) { |
| | | return false; |
| | | } |
| | | |
| | | boolean result = updateStationDataInternal(nextStationId, nextStationDeviceNo, taskNo, targetStaNo, true, |
| | | null, false); |
| | | if (!result) { |
| | | return false; |
| | | } |
| | | return updateStationDataInternal(currentStationId, currentStationDeviceNo, 0, 0, false, "", false); |
| | | } finally { |
| | | unlockStations(currentStationId, nextStationId); |
| | | } |
| | | } |
| | | |
| | | 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 { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | 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); |
| | | 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; |
| | | } |
| | | return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, blockStationId, true, "", |
| | | true); |
| | | } finally { |
| | | unlockStations(currentStationId); |
| | | } |
| | | } |
| | | |
| | | public void clearRunBlock(Integer stationId, Integer deviceNo) { |
| | | lockStations(stationId); |
| | | try { |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | 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); |
| | | } |
| | | } finally { |
| | | unlockStations(stationId); |
| | | boolean handedOff = guardedHandoffBarcodeStation(deviceNo, taskNo, stationId, targetStationId); |
| | | News.info("[WCS Debug] fake 条码站任务交接完成,deviceNo={},stationId={},taskNo={},targetStationId={},动作=barcodeTaskHandoff,accepted={}", |
| | | deviceNo, stationId, taskNo, targetStationId, handedOff); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | private void traceEvent(Integer deviceNo, TaskRuntimeContext context, String eventType, String message, |
| | | Map<String, Object> details, boolean terminal) { |
| | | if (context != null && context.deviceNo != null && deviceNo != null && !context.deviceNo.equals(deviceNo)) { |
| | | deviceNo = context.deviceNo; |
| | | } |
| | | if (context == null || context.taskNo == null) { |
| | | return; |
| | | } |
| | | try { |
| | | FakeTaskTraceRegistry registry = SpringUtils.getBean(FakeTaskTraceRegistry.class); |
| | | if (registry == null) { |
| | | return; |
| | | Map<String, Object> safeDetails = details == null ? new LinkedHashMap<String, Object>() : new LinkedHashMap<String, Object>(details); |
| | | if (!safeDetails.containsKey("segmentNo") && context.segmentNo != null) { |
| | | safeDetails.put("segmentNo", context.segmentNo); |
| | | } |
| | | registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : getThreadImpl(deviceNo), |
| | | if (!safeDetails.containsKey("segmentCount") && context.segmentCount != null) { |
| | | safeDetails.put("segmentCount", context.segmentCount); |
| | | } |
| | | try { |
| | | FakeTaskTraceRegistry registry = com.core.common.SpringUtils.getBean(FakeTaskTraceRegistry.class); |
| | | if (registry != null) { |
| | | registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : stateManager.getThreadImpl(deviceNo), |
| | | context.status, context.startStationId, context.currentStationId, context.finalTargetStationId, |
| | | context.blockedStationId, context.getStitchedPathStationIds(), context.getPassedStationIds(), |
| | | context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, details, |
| | | context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, safeDetails, |
| | | terminal); |
| | | } catch (Exception ignore) { |
| | | } |
| | | } catch (Exception e) { |
| | | News.info("[WCS Debug] fake trace记录失败,taskNo={},eventType={},异常类型={},异常信息={}", |
| | | context.taskNo, eventType, e.getClass().getSimpleName(), e.getMessage()); |
| | | } |
| | | |
| | | private String getThreadImpl(Integer deviceNo) { |
| | | DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo); |
| | | return deviceConfig == null ? "" : deviceConfig.getThreadImpl(); |
| | | News.info("[WCS Debug] fake task event,taskNo={},eventType={},status={},currentStationId={},targetStationId={},pending={},details={}", |
| | | context.taskNo, eventType, context.status, context.currentStationId, context.finalTargetStationId, |
| | | context.getPendingStationIds(), JSON.toJSONString(safeDetails)); |
| | | } |
| | | |
| | | private boolean isTerminalStatus(String status) { |
| | | return STATUS_BLOCKED.equals(status) || STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status) |
| | | return STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status) |
| | | || STATUS_FINISHED.equals(status); |
| | | } |
| | | |
| | | private boolean isCurrentLoopGeneration(Integer taskNo, int loopGeneration) { |
| | | AtomicInteger generation = taskLoopGenerations.get(taskNo); |
| | | return generation != null && generation.get() == loopGeneration; |
| | | } |
| | | |
| | | private Object getTaskLifecycleLock(Integer taskNo) { |
| | | return taskLifecycleLocks.computeIfAbsent(taskNo, key -> new Object()); |
| | | } |
| | | |
| | | /** |
| | | * 检查堆垛机是否已取走指定任务的货物 |
| | | * 通过遍历所有堆垛机设备,检查是否有堆垛机正在执行该任务且已 loaded |
| | | */ |
| | | private boolean isCrnTakenByTask(Integer taskNo) { |
| | | if (taskNo == null || taskNo <= 0) { |
| | | return false; |
| | | } |
| | | try { |
| | | com.zy.asrs.service.BasCrnpService basCrnpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasCrnpService.class); |
| | | List<com.zy.asrs.entity.BasCrnp> basCrnps = basCrnpService.list(); |
| | | for (com.zy.asrs.entity.BasCrnp basCrnp : basCrnps) { |
| | | com.zy.core.thread.CrnThread ct = (com.zy.core.thread.CrnThread) com.zy.core.cache.SlaveConnection.get( |
| | | com.zy.core.enums.SlaveType.Crn, basCrnp.getCrnNo()); |
| | | if (ct == null) { |
| | | continue; |
| | | } |
| | | com.zy.core.model.protocol.CrnProtocol protocol = ct.getStatus(); |
| | | if (protocol != null && taskNo.equals(protocol.getTaskNo()) |
| | | && protocol.getLoaded() == 1) { |
| | | return true; |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | |
| | | private static class MoveStepResult { |
| | | |
| | | private final boolean shouldContinue; |
| | | private final long nextDelayMs; |
| | | |
| | | private MoveStepResult(boolean shouldContinue, long nextDelayMs) { |
| | | this.shouldContinue = shouldContinue; |
| | | this.nextDelayMs = nextDelayMs; |
| | | } |
| | | |
| | | private static MoveStepResult continueNow() { |
| | | return new MoveStepResult(true, 0L); |
| | | } |
| | | |
| | | private static MoveStepResult continueAfter(long nextDelayMs) { |
| | | return new MoveStepResult(true, nextDelayMs); |
| | | } |
| | | |
| | | private boolean shouldContinue() { |
| | | return shouldContinue; |
| | | } |
| | | |
| | | private long getNextDelayMs() { |
| | | return nextDelayMs; |
| | | } |
| | | } |
| | | |
| | | private static class IdleStepResult { |
| | | |
| | | private final boolean finished; |
| | | private final long nextDelayMs; |
| | | |
| | | private IdleStepResult(boolean finished, long nextDelayMs) { |
| | | this.finished = finished; |
| | | this.nextDelayMs = nextDelayMs; |
| | | } |
| | | |
| | | private static IdleStepResult finish() { |
| | | return new IdleStepResult(true, 0L); |
| | | } |
| | | |
| | | private static IdleStepResult waitNext(long nextDelayMs) { |
| | | return new IdleStepResult(false, nextDelayMs); |
| | | } |
| | | |
| | | private boolean isFinished() { |
| | | return finished; |
| | | } |
| | | |
| | | private long getNextDelayMs() { |
| | | return nextDelayMs; |
| | | } |
| | | } |
| | | |
| | | private static class TaskRuntimeContext { |
| | | |
| | | private final Integer taskNo; |
| | | private final Integer deviceNo; |
| | | private final String threadImpl; |
| | | private final LinkedBlockingQueue<Integer> pendingPathQueue = new LinkedBlockingQueue<Integer>(); |
| | | private final List<Integer> stitchedPathStationIds = new ArrayList<Integer>(); |
| | |
| | | private boolean generateBarcode; |
| | | private boolean initialized; |
| | | private boolean arrivalHandled; |
| | | private Integer segmentNo; |
| | | private Integer segmentCount; |
| | | private long lastStepAt = System.currentTimeMillis(); |
| | | private long lastCommandAt = System.currentTimeMillis(); |
| | | private long lastProgressAt = System.currentTimeMillis(); |
| | | private Integer lastProgressStationId; |
| | | private int loopGeneration; |
| | | private String status = STATUS_WAITING; |
| | | |
| | | private TaskRuntimeContext(Integer taskNo, String threadImpl) { |
| | | private TaskRuntimeContext(Integer taskNo, Integer deviceNo, String threadImpl) { |
| | | this.taskNo = taskNo; |
| | | this.deviceNo = deviceNo; |
| | | this.threadImpl = threadImpl; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private void replaceLatestPath(List<Integer> path) { |
| | | latestAppendedPath.clear(); |
| | | if (path == null) { |
| | | return; |
| | | } |
| | | for (Integer stationId : path) { |
| | | if (stationId != null) { |
| | | latestAppendedPath.add(stationId); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void clearPendingPath() { |
| | | pendingPathQueue.clear(); |
| | | latestAppendedPath.clear(); |
| | | } |
| | | |
| | | private void clearPendingPathWithLog(Integer deviceNo, Integer taskNo, Integer currentStationId, |
| | | Integer targetStationId, Integer blockedStationId, String reason) { |
| | | News.info("[WCS Debug] fake task清空待执行路径,原因={},deviceNo={},taskNo={},currentStationId={},targetStationId={},blockedStationId={},pendingBeforeClear={}", |
| | | reason, deviceNo, taskNo, currentStationId, targetStationId, blockedStationId, getPendingStationIds()); |
| | | clearPendingPath(); |
| | | } |
| | | |
| | | private void addPassedStation(Integer stationId) { |
| | | if (stationId == null) { |
| | | return; |