2个文件已删除
28个文件已添加
12个文件已修改
| | |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.DeviceConfigService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.model.StationObjModel; |
| | |
| | | import com.core.common.Cools; |
| | | import com.core.common.R; |
| | | import com.zy.asrs.domain.param.StationCommandMoveParam; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.model.Task; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.thread.StationThread; |
| | | |
| | |
| | | private ConfigService configService; |
| | | @Autowired |
| | | private DeviceConfigService deviceConfigService; |
| | | @Autowired |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | |
| | | @PostMapping("/command/move") |
| | | public R commandMove(@RequestBody StationCommandMoveParam param) { |
| | |
| | | if (command == null) { |
| | | return R.error("生成输送命令失败,路径为空或不可达"); |
| | | } |
| | | MessageQueue.offer(SlaveType.Devp, devpNo, new Task(2, command)); |
| | | StationCommandDispatchResult dispatchResult = stationCommandDispatcher |
| | | .dispatch(devpNo, command, "station-controller", "manual-move"); |
| | | if (!dispatchResult.isAccepted()) { |
| | | return R.error("输送命令下发失败:" + dispatchResult.getReason()); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | |
| | | |
| | | StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9997, stationId, stationId, 0); |
| | | command.setBarcode(barcode.trim()); |
| | | MessageQueue.offer(SlaveType.Devp, devpNo, new Task(2, command)); |
| | | StationCommandDispatchResult dispatchResult = stationCommandDispatcher |
| | | .dispatch(devpNo, command, "station-controller", "manual-barcode"); |
| | | if (!dispatchResult.isAccepted()) { |
| | | return R.error("条码命令下发失败:" + dispatchResult.getReason()); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | } |
| | |
| | | import com.zy.core.model.Task; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Queue; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.ConcurrentLinkedQueue; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | |
| | | * 如果发现队列已满无法添加的话,会直接返回false。 |
| | | */ |
| | | public static boolean offer(SlaveType type, Integer id, Task task) { |
| | | Queue<Task> queue = resolveQueue(type, id); |
| | | if (queue == null) { |
| | | return false; |
| | | } |
| | | switch (type) { |
| | | case Crn: |
| | | return CRN_EXCHANGE.get(id).offer(task); |
| | | case DualCrn: |
| | | return DUAL_CRN_EXCHANGE.get(id).offer(task); |
| | | case Rgv: |
| | | return RGV_EXCHANGE.get(id).offer(task); |
| | | case Devp: |
| | | return DEVP_EXCHANGE.get(id).offer(task); |
| | | case Barcode: |
| | | return BARCODE_EXCHANGE.get(id).offer(task); |
| | | case Led: |
| | | return LED_EXCHANGE.get(id).offer(task); |
| | | case Scale: |
| | | return SCALE_EXCHANGE.get(id).offer(task); |
| | | return queue.offer(task); |
| | | default: |
| | | return false; |
| | | } |
| | |
| | | * 若队列为空,返回null。 |
| | | */ |
| | | public static Task poll(SlaveType type, Integer id) { |
| | | Queue<Task> queue = resolveQueue(type, id); |
| | | if (queue == null) { |
| | | return null; |
| | | } |
| | | switch (type) { |
| | | case Crn: |
| | | return CRN_EXCHANGE.get(id).poll(); |
| | | case DualCrn: |
| | | return DUAL_CRN_EXCHANGE.get(id).poll(); |
| | | case Rgv: |
| | | return RGV_EXCHANGE.get(id).poll(); |
| | | case Devp: |
| | | return DEVP_EXCHANGE.get(id).poll(); |
| | | case Barcode: |
| | | return BARCODE_EXCHANGE.get(id).poll(); |
| | | case Led: |
| | | return LED_EXCHANGE.get(id).poll(); |
| | | case Scale: |
| | | return SCALE_EXCHANGE.get(id).poll(); |
| | | return queue.poll(); |
| | | default: |
| | | return null; |
| | | } |
| | |
| | | * 取出元素,并不删除. |
| | | */ |
| | | public static Task peek(SlaveType type, Integer id) { |
| | | Queue<Task> queue = resolveQueue(type, id); |
| | | if (queue == null) { |
| | | return null; |
| | | } |
| | | switch (type) { |
| | | case Crn: |
| | | return CRN_EXCHANGE.get(id).peek(); |
| | | case DualCrn: |
| | | return DUAL_CRN_EXCHANGE.get(id).peek(); |
| | | case Rgv: |
| | | return RGV_EXCHANGE.get(id).peek(); |
| | | case Devp: |
| | | return DEVP_EXCHANGE.get(id).peek(); |
| | | case Barcode: |
| | | return BARCODE_EXCHANGE.get(id).peek(); |
| | | case Led: |
| | | return LED_EXCHANGE.get(id).peek(); |
| | | case Scale: |
| | | return SCALE_EXCHANGE.get(id).peek(); |
| | | return queue.peek(); |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public static void clear(SlaveType type, Integer id){ |
| | | Queue<Task> queue = resolveQueue(type, id); |
| | | if (queue == null) { |
| | | return; |
| | | } |
| | | switch (type) { |
| | | case Crn: |
| | | CRN_EXCHANGE.get(id).clear(); |
| | | break; |
| | | case DualCrn: |
| | | DUAL_CRN_EXCHANGE.get(id).clear(); |
| | | break; |
| | | case Rgv: |
| | | RGV_EXCHANGE.get(id).clear(); |
| | | break; |
| | | case Devp: |
| | | DEVP_EXCHANGE.get(id).clear(); |
| | | break; |
| | | case Barcode: |
| | | BARCODE_EXCHANGE.get(id).clear(); |
| | | break; |
| | | case Led: |
| | | LED_EXCHANGE.get(id).clear(); |
| | | break; |
| | | case Scale: |
| | | SCALE_EXCHANGE.get(id).clear(); |
| | | queue.clear(); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | public static boolean hasExchange(SlaveType type, Integer id) { |
| | | return resolveQueue(type, id) != null; |
| | | } |
| | | |
| | | public static int size(SlaveType type, Integer id) { |
| | | Queue<Task> queue = resolveQueue(type, id); |
| | | return queue == null ? 0 : queue.size(); |
| | | } |
| | | |
| | | private static Queue<Task> resolveQueue(SlaveType type, Integer id) { |
| | | if (type == null || id == null) { |
| | | return null; |
| | | } |
| | | switch (type) { |
| | | case Crn: |
| | | return CRN_EXCHANGE.get(id); |
| | | case DualCrn: |
| | | return DUAL_CRN_EXCHANGE.get(id); |
| | | case Rgv: |
| | | return RGV_EXCHANGE.get(id); |
| | | case Devp: |
| | | return DEVP_EXCHANGE.get(id); |
| | | case Barcode: |
| | | return BARCODE_EXCHANGE.get(id); |
| | | case Led: |
| | | return LED_EXCHANGE.get(id); |
| | | case Scale: |
| | | return SCALE_EXCHANGE.get(id); |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.zy.core.dispatch; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class StationCommandDispatchResult { |
| | | |
| | | private final boolean accepted; |
| | | private final String reason; |
| | | private final int queueDepth; |
| | | private final String source; |
| | | private final String scene; |
| | | |
| | | public static StationCommandDispatchResult accepted(String reason, |
| | | int queueDepth, |
| | | String source, |
| | | String scene) { |
| | | return new StationCommandDispatchResult(true, reason, queueDepth, source, scene); |
| | | } |
| | | |
| | | public static StationCommandDispatchResult rejected(String reason, |
| | | int queueDepth, |
| | | String source, |
| | | String scene) { |
| | | return new StationCommandDispatchResult(false, reason, queueDepth, source, scene); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.dispatch; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.core.common.Cools; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.model.Task; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | @Component |
| | | public class StationCommandDispatcher { |
| | | |
| | | private static final int STATION_COMMAND_DISPATCH_DEDUP_SECONDS = 10; |
| | | |
| | | @Autowired(required = false) |
| | | private RedisUtil redisUtil; |
| | | @Autowired(required = false) |
| | | private StationMoveCoordinator stationMoveCoordinator; |
| | | |
| | | public StationCommandDispatcher() { |
| | | } |
| | | |
| | | public StationCommandDispatcher(RedisUtil redisUtil, StationMoveCoordinator stationMoveCoordinator) { |
| | | this.redisUtil = redisUtil; |
| | | this.stationMoveCoordinator = stationMoveCoordinator; |
| | | } |
| | | |
| | | public StationCommandDispatchResult dispatch(Integer deviceNo, |
| | | StationCommand command, |
| | | String source, |
| | | String scene) { |
| | | String normalizedSource = Cools.isEmpty(source) ? "unknown" : source; |
| | | String normalizedScene = Cools.isEmpty(scene) ? "default" : scene; |
| | | if (deviceNo == null || command == null) { |
| | | return reject("invalid-argument", 0, normalizedSource, normalizedScene, null); |
| | | } |
| | | if (!MessageQueue.hasExchange(SlaveType.Devp, deviceNo)) { |
| | | return reject("queue-not-initialized", 0, normalizedSource, normalizedScene, command); |
| | | } |
| | | |
| | | String dedupKey = buildDedupKey(deviceNo, command); |
| | | if (!Cools.isEmpty(dedupKey) && redisUtil != null && redisUtil.get(dedupKey) != null) { |
| | | return reject("dedup-suppressed", |
| | | MessageQueue.size(SlaveType.Devp, deviceNo), |
| | | normalizedSource, |
| | | normalizedScene, |
| | | command); |
| | | } |
| | | if (!Cools.isEmpty(dedupKey) && redisUtil != null) { |
| | | redisUtil.set(dedupKey, "lock", STATION_COMMAND_DISPATCH_DEDUP_SECONDS); |
| | | } |
| | | |
| | | boolean offered = MessageQueue.offer(SlaveType.Devp, deviceNo, new Task(2, command)); |
| | | int queueDepth = MessageQueue.size(SlaveType.Devp, deviceNo); |
| | | if (!offered) { |
| | | if (!Cools.isEmpty(dedupKey) && redisUtil != null) { |
| | | redisUtil.del(dedupKey); |
| | | } |
| | | return reject("queue-offer-failed", queueDepth, normalizedSource, normalizedScene, command); |
| | | } |
| | | |
| | | News.info("输送站点命令入队成功。source={},scene={},deviceNo={},taskNo={},stationId={},targetStaNo={},commandType={},queueDepth={}", |
| | | normalizedSource, |
| | | normalizedScene, |
| | | deviceNo, |
| | | command.getTaskNo(), |
| | | command.getStationId(), |
| | | command.getTargetStaNo(), |
| | | command.getCommandType(), |
| | | queueDepth); |
| | | return StationCommandDispatchResult.accepted("accepted", queueDepth, normalizedSource, normalizedScene); |
| | | } |
| | | |
| | | private StationCommandDispatchResult reject(String reason, |
| | | int queueDepth, |
| | | String source, |
| | | String scene, |
| | | StationCommand command) { |
| | | News.warn("输送站点命令入队失败。reason={},source={},scene={},deviceNo?=N/A,taskNo={},stationId={},targetStaNo={},commandType={},queueDepth={}", |
| | | reason, |
| | | source, |
| | | scene, |
| | | command == null ? null : command.getTaskNo(), |
| | | command == null ? null : command.getStationId(), |
| | | command == null ? null : command.getTargetStaNo(), |
| | | command == null ? null : command.getCommandType(), |
| | | queueDepth); |
| | | return StationCommandDispatchResult.rejected(reason, queueDepth, source, scene); |
| | | } |
| | | |
| | | private String buildDedupKey(Integer deviceNo, StationCommand command) { |
| | | if (deviceNo == null || command == null) { |
| | | return ""; |
| | | } |
| | | return RedisKeyType.STATION_COMMAND_DISPATCH_DEDUP_.key |
| | | + deviceNo + "_" |
| | | + command.getTaskNo() + "_" |
| | | + command.getStationId() + "_" |
| | | + buildCommandSignatureHash(command); |
| | | } |
| | | |
| | | private String buildCommandSignatureHash(StationCommand command) { |
| | | if (command == null) { |
| | | return ""; |
| | | } |
| | | if (stationMoveCoordinator != null && command.getCommandType() != null |
| | | && command.getCommandType().name().startsWith("MOVE")) { |
| | | String pathSignatureHash = stationMoveCoordinator.buildPathSignatureHash(command); |
| | | if (!Cools.isEmpty(pathSignatureHash)) { |
| | | return pathSignatureHash; |
| | | } |
| | | } |
| | | return Integer.toHexString(JSON.toJSONString(command).hashCode()); |
| | | } |
| | | } |
| | |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.*; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.Task; |
| | |
| | | private DualCrnOperateProcessUtils dualCrnOperateProcessUtils; |
| | | @Autowired |
| | | private StoreInTaskGenerationService storeInTaskGenerationService; |
| | | @Autowired |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | |
| | | /** |
| | | * 带超时保护执行方法 |
| | |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, |
| | | commonService.getWorkNo(WrkIoType.FAKE_TASK_NO.id), stationId, |
| | | entity.getBarcodeStation().getStationId(), 0); |
| | | MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-enable-in"); |
| | | redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 5); |
| | | } |
| | | } |
| | |
| | | News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); |
| | | continue; |
| | | } |
| | | MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-in-task"); |
| | | redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_TASK_LIMIT.key + stationId, "lock", 5); |
| | | } |
| | | } |
| | |
| | | News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); |
| | | return; |
| | | } |
| | | MessageQueue.offer(SlaveType.Devp, context.getBasDevp().getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "fake-process", "write-info"); |
| | | } |
| | | |
| | | // 计算所有站点停留时间 |
| | |
| | | continue; |
| | | } |
| | | |
| | | MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "out-station-reset"); |
| | | redisUtil.set( |
| | | RedisKeyType.CHECK_OUT_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(), |
| | | "lock", 10); |
| | |
| | | |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (wrkMast == null) { |
| | | MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-task-over"); |
| | | redisUtil.set( |
| | | RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(), |
| | | "lock", 10); |
| | |
| | | continue; |
| | | } |
| | | |
| | | MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-crn-fetch"); |
| | | redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key |
| | | + stationObjModel.getStationId(), "lock", 10); |
| | | News.info("输送站点重置命令下发成功(crn_fetch),站点号={},命令数据={}", stationObjModel.getStationId(), |
| | |
| | | continue; |
| | | } |
| | | |
| | | MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-dual-crn-fetch"); |
| | | redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key |
| | | + stationObjModel.getStationId(), "lock", 10); |
| | | News.info("输送站点重置命令下发成功(crn_fetch),站点号={},命令数据={}", stationObjModel.getStationId(), |
| | |
| | | // 生成仿真站点数据 |
| | | StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9998, |
| | | wrkMast.getSourceStaNo(), 0, 0); |
| | | MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "crn-out-complete-write-info"); |
| | | redisUtil.set(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo(), JSON.toJSONString(stationObjModel, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24); |
| | | } |
| | | } else if (wrkMast.getWrkSts() == WrkStsType.LOC_MOVE_RUN.sts) { |
| | |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.enums.WrkIoType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.Task; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.plugin.api.MainProcessPluginApi; |
| | |
| | | private RedisUtil redisUtil; |
| | | @Autowired |
| | | private StoreInTaskGenerationService storeInTaskGenerationService; |
| | | @Autowired |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | |
| | | @Override |
| | | public void run() { |
| | |
| | | News.taskInfo(stationProtocol.getTaskNo(), "{}工作,获取输送线命令失败", stationProtocol.getTaskNo()); |
| | | return false; |
| | | } |
| | | MessageQueue.offer(SlaveType.Devp, context.getBasDevp().getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "gsl-process", "station-back"); |
| | | News.taskInfo(stationProtocol.getTaskNo(), "{}扫码异常,已退回至{}", backStation.getStationId()); |
| | | redisUtil.set(RedisKeyType.GENERATE_STATION_BACK_LIMIT.key + stationProtocol.getStationId(), "lock", 10); |
| | | return true; |
| | |
| | | && stationProtocol.isEnableIn() |
| | | ) { |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, commonService.getWorkNo(WrkIoType.ENABLE_IN.id), stationId, entity.getBarcodeStation().getStationId(), 0); |
| | | MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "gsl-process", "enable-in"); |
| | | redisUtil.set(RedisKeyType.GENERATE_ENABLE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 15); |
| | | News.info("{}站点启动入库成功,数据包:{}", stationId, JSON.toJSONString(command)); |
| | | } |
| | |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.enums.WrkIoType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.Task; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.plugin.api.MainProcessPluginApi; |
| | |
| | | private WmsOperateUtils wmsOperateUtils; |
| | | @Autowired |
| | | private StoreInTaskGenerationService storeInTaskGenerationService; |
| | | @Autowired |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | |
| | | @Override |
| | | public void run() { |
| | |
| | | && stationProtocol.isEnableIn() |
| | | ) { |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, commonService.getWorkNo(WrkIoType.ENABLE_IN.id), stationId, entity.getBarcodeStation().getStationId(), 0); |
| | | MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "normal-process", "enable-in"); |
| | | redisUtil.set(RedisKeyType.GENERATE_ENABLE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 15); |
| | | News.info("{}站点启动入库成功,数据包:{}", stationId, JSON.toJSONString(command)); |
| | | } |
| | |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.enums.WrkIoType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.Task; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.plugin.api.MainProcessPluginApi; |
| | |
| | | private DualCrnOperateProcessUtils dualCrnOperateProcessUtils; |
| | | @Autowired |
| | | private StoreInTaskGenerationService storeInTaskGenerationService; |
| | | @Autowired |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | |
| | | @Override |
| | | public void run() { |
| | |
| | | News.taskInfo(stationProtocol.getTaskNo(), "{}工作,获取输送线命令失败", stationProtocol.getTaskNo()); |
| | | return false; |
| | | } |
| | | MessageQueue.offer(SlaveType.Devp, context.getBasDevp().getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "xiaosong-process", "station-back"); |
| | | News.taskInfo(stationProtocol.getTaskNo(), "{}扫码异常,已退回至{}", backStation.getStationId()); |
| | | redisUtil.set(RedisKeyType.GENERATE_STATION_BACK_LIMIT.key + stationProtocol.getTaskNo(), "lock", 10); |
| | | return true; |
| | |
| | | && stationProtocol.isEnableIn() |
| | | ) { |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, commonService.getWorkNo(WrkIoType.ENABLE_IN.id), stationId, entity.getBarcodeStation().getStationId(), 0); |
| | | MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "xiaosong-process", "enable-in"); |
| | | redisUtil.set(RedisKeyType.GENERATE_ENABLE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 15); |
| | | News.info("{}站点启动入库成功,数据包:{}", stationId, JSON.toJSONString(command)); |
| | | } |
| | |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.core.common.Cools; |
| | | import com.core.common.DateUtils; |
| | | import com.core.common.SpringUtils; |
| | | import com.zy.asrs.domain.vo.StationCycleCapacityVo; |
| | | import com.zy.asrs.domain.vo.StationCycleLoopVo; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.BasStationOpt; |
| | | import com.zy.asrs.entity.DeviceConfig; |
| | | import com.zy.asrs.entity.DeviceDataLog; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.BasStationOptService; |
| | | import com.zy.asrs.service.StationCycleCapacityService; |
| | | import com.zy.asrs.utils.Utils; |
| | | import com.zy.common.model.NavigateNode; |
| | | import com.zy.common.utils.NavigateUtils; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.OutputQueue; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.model.CommandResponse; |
| | |
| | | import com.zy.core.network.ZyStationConnectDriver; |
| | | import com.zy.core.network.entity.ZyStationStatusEntity; |
| | | import com.zy.core.service.StationTaskLoopService; |
| | | import com.zy.core.thread.impl.v5.StationV5RunBlockReroutePlanner; |
| | | import com.zy.core.thread.impl.v5.StationV5SegmentExecutor; |
| | | import com.zy.core.thread.impl.v5.StationV5StatusReader; |
| | | import com.zy.core.thread.support.RecentStationArrivalTracker; |
| | | import com.zy.core.trace.StationTaskTraceRegistry; |
| | | import com.zy.core.utils.DeviceLogRedisKeyBuilder; |
| | | import lombok.Data; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | |
| | | import java.text.MessageFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.ArrayDeque; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.Deque; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.Executors; |
| | | |
| | |
| | | @Slf4j |
| | | public class ZyStationV5Thread implements Runnable, com.zy.core.thread.StationThread { |
| | | |
| | | private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24; |
| | | private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2; |
| | | private static final int LOOP_REPEAT_TRIGGER_COUNT = 3; |
| | | private static final int LOCAL_LOOP_NEIGHBOR_HOP = 3; |
| | | private static final int SEGMENT_EXECUTOR_POOL_SIZE = 64; |
| | | |
| | | private List<StationProtocol> statusList = new ArrayList<>(); |
| | | private DeviceConfig deviceConfig; |
| | | private RedisUtil redisUtil; |
| | | private ZyStationConnectDriver zyStationConnectDriver; |
| | | private int deviceLogCollectTime = 200; |
| | | private boolean initStatus = false; |
| | | private long deviceDataLogTime = System.currentTimeMillis(); |
| | | private ExecutorService executor = Executors.newFixedThreadPool(9999); |
| | | private final ExecutorService executor = Executors.newFixedThreadPool(SEGMENT_EXECUTOR_POOL_SIZE); |
| | | private StationV5SegmentExecutor segmentExecutor; |
| | | private final RecentStationArrivalTracker recentArrivalTracker; |
| | | private final StationV5StatusReader statusReader; |
| | | private final StationV5RunBlockReroutePlanner runBlockReroutePlanner; |
| | | |
| | | public ZyStationV5Thread(DeviceConfig deviceConfig, RedisUtil redisUtil) { |
| | | this.deviceConfig = deviceConfig; |
| | | this.redisUtil = redisUtil; |
| | | this.recentArrivalTracker = new RecentStationArrivalTracker(redisUtil); |
| | | this.segmentExecutor = new StationV5SegmentExecutor(deviceConfig, redisUtil, this::sendCommand); |
| | | this.statusReader = new StationV5StatusReader(deviceConfig, redisUtil, recentArrivalTracker); |
| | | this.runBlockReroutePlanner = new StationV5RunBlockReroutePlanner(redisUtil); |
| | | } |
| | | |
| | | @Override |
| | | @SuppressWarnings("InfiniteLoopStatement") |
| | | public void run() { |
| | | this.connect(); |
| | | deviceLogCollectTime = Utils.getDeviceLogCollectTime(); |
| | | |
| | | Thread readThread = new Thread(() -> { |
| | | while (true) { |
| | | try { |
| | | if (initStatus) { |
| | | deviceLogCollectTime = Utils.getDeviceLogCollectTime(); |
| | | } |
| | | readStatus(); |
| | | statusReader.readStatus(zyStationConnectDriver); |
| | | Thread.sleep(100); |
| | | } catch (Exception e) { |
| | | log.error("StationV5Thread Fail", e); |
| | |
| | | Thread processThread = new Thread(() -> { |
| | | while (true) { |
| | | try { |
| | | int step = 1; |
| | | Task task = MessageQueue.poll(SlaveType.Devp, deviceConfig.getDeviceNo()); |
| | | if (task != null) { |
| | | step = task.getStep(); |
| | | } |
| | | if (step == 2) { |
| | | StationCommand cmd = (StationCommand) task.getData(); |
| | | executor.submit(() -> segmentExecutor.execute(cmd)); |
| | | } |
| | | pollAndDispatchQueuedCommand(); |
| | | Thread.sleep(100); |
| | | } catch (Exception e) { |
| | | log.error("StationV5Process Fail", e); |
| | |
| | | } |
| | | }, "DevpProcess-" + deviceConfig.getDeviceNo()); |
| | | processThread.start(); |
| | | } |
| | | |
| | | private void readStatus() { |
| | | if (zyStationConnectDriver == null) { |
| | | return; |
| | | } |
| | | |
| | | if (statusList.isEmpty()) { |
| | | BasDevpService basDevpService = null; |
| | | try { |
| | | basDevpService = SpringUtils.getBean(BasDevpService.class); |
| | | } catch (Exception ignore) { |
| | | } |
| | | if (basDevpService == null) { |
| | | return; |
| | | } |
| | | |
| | | BasDevp basDevp = basDevpService |
| | | .getOne(new QueryWrapper<BasDevp>().eq("devp_no", deviceConfig.getDeviceNo())); |
| | | if (basDevp == null) { |
| | | return; |
| | | } |
| | | |
| | | List<ZyStationStatusEntity> list = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class); |
| | | for (ZyStationStatusEntity entity : list) { |
| | | StationProtocol stationProtocol = new StationProtocol(); |
| | | stationProtocol.setStationId(entity.getStationId()); |
| | | statusList.add(stationProtocol); |
| | | } |
| | | initStatus = true; |
| | | } |
| | | |
| | | List<ZyStationStatusEntity> zyStationStatusEntities = zyStationConnectDriver.getStatus(); |
| | | for (ZyStationStatusEntity statusEntity : zyStationStatusEntities) { |
| | | for (StationProtocol stationProtocol : statusList) { |
| | | if (stationProtocol.getStationId().equals(statusEntity.getStationId())) { |
| | | stationProtocol.setTaskNo(statusEntity.getTaskNo()); |
| | | stationProtocol.setTargetStaNo(statusEntity.getTargetStaNo()); |
| | | stationProtocol.setAutoing(statusEntity.isAutoing()); |
| | | stationProtocol.setLoading(statusEntity.isLoading()); |
| | | stationProtocol.setInEnable(statusEntity.isInEnable()); |
| | | stationProtocol.setOutEnable(statusEntity.isOutEnable()); |
| | | stationProtocol.setEmptyMk(statusEntity.isEmptyMk()); |
| | | stationProtocol.setFullPlt(statusEntity.isFullPlt()); |
| | | stationProtocol.setPalletHeight(statusEntity.getPalletHeight()); |
| | | stationProtocol.setError(statusEntity.getError()); |
| | | stationProtocol.setErrorMsg(statusEntity.getErrorMsg()); |
| | | stationProtocol.setBarcode(statusEntity.getBarcode()); |
| | | stationProtocol.setRunBlock(statusEntity.isRunBlock()); |
| | | stationProtocol.setEnableIn(statusEntity.isEnableIn()); |
| | | stationProtocol.setWeight(statusEntity.getWeight()); |
| | | stationProtocol.setTaskWriteIdx(statusEntity.getTaskWriteIdx()); |
| | | stationProtocol.setTaskBufferItems(statusEntity.getTaskBufferItems()); |
| | | recentArrivalTracker.observe(statusEntity.getStationId(), statusEntity.getTaskNo(), statusEntity.isLoading()); |
| | | } |
| | | |
| | | if (!Cools.isEmpty(stationProtocol.getSystemWarning())) { |
| | | if (stationProtocol.isAutoing() && !stationProtocol.isLoading()) { |
| | | stationProtocol.setSystemWarning(""); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功", |
| | | DateUtils.convert(new Date()), deviceConfig.getDeviceNo())); |
| | | |
| | | if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) { |
| | | DeviceDataLog deviceDataLog = new DeviceDataLog(); |
| | | deviceDataLog.setOriginData(JSON.toJSONString(zyStationStatusEntities)); |
| | | deviceDataLog.setWcsData(JSON.toJSONString(statusList)); |
| | | deviceDataLog.setType(String.valueOf(SlaveType.Devp)); |
| | | deviceDataLog.setDeviceNo(deviceConfig.getDeviceNo()); |
| | | deviceDataLog.setCreateTime(new Date()); |
| | | |
| | | redisUtil.set(DeviceLogRedisKeyBuilder.build(deviceDataLog), deviceDataLog, 60 * 60 * 24); |
| | | deviceDataLogTime = System.currentTimeMillis(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public List<StationProtocol> getStatus() { |
| | | return statusList; |
| | | return statusReader.getStatusList(); |
| | | } |
| | | |
| | | @Override |
| | | public Map<Integer, StationProtocol> getStatusMap() { |
| | | Map<Integer, StationProtocol> map = new HashMap<>(); |
| | | for (StationProtocol stationProtocol : statusList) { |
| | | for (StationProtocol stationProtocol : statusReader.getStatusList()) { |
| | | map.put(stationProtocol.getStationId(), stationProtocol); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | private void pollAndDispatchQueuedCommand() { |
| | | Task task = MessageQueue.poll(SlaveType.Devp, deviceConfig.getDeviceNo()); |
| | | if (task == null || task.getStep() == null || task.getStep() != 2) { |
| | | return; |
| | | } |
| | | submitSegmentCommand((StationCommand) task.getData()); |
| | | } |
| | | |
| | | private void submitSegmentCommand(StationCommand command) { |
| | | if (command == null || executor == null || segmentExecutor == null) { |
| | | return; |
| | | } |
| | | executor.submit(() -> segmentExecutor.execute(command)); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return getCommand(StationCommandType.MOVE, taskNo, stationId, targetStationId, palletSize, pathLenFactor); |
| | | } |
| | | |
| | | RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, stationId); |
| | | StationTaskLoopService taskLoopService = loadStationTaskLoopService(); |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = taskLoopService == null |
| | | ? new StationTaskLoopService.LoopEvaluation(taskNo, stationId, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false) |
| | |
| | | loopEvaluation.getLoopIdentity().getScopeType(), |
| | | loopEvaluation.getLoopIdentity().getLocalStationCount(), |
| | | loopEvaluation.getLoopIdentity().getSourceLoopStationCount()); |
| | | rerouteState.setTaskNo(taskNo); |
| | | rerouteState.setBlockStationId(stationId); |
| | | rerouteState.setLastTargetStationId(targetStationId); |
| | | rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1); |
| | | rerouteState.setLastPlanTime(System.currentTimeMillis()); |
| | | |
| | | List<List<NavigateNode>> candidatePathList = calcCandidatePathNavigateNodes(taskNo, stationId, targetStationId, pathLenFactor); |
| | | if (candidatePathList.isEmpty()) { |
| | | saveRunBlockRerouteState(rerouteState); |
| | | List<StationCommand> candidateCommandList = new ArrayList<>(); |
| | | for (List<NavigateNode> candidatePath : candidatePathList) { |
| | | StationCommand rerouteCommand = buildMoveCommand(taskNo, stationId, targetStationId, palletSize, candidatePath); |
| | | if (rerouteCommand == null || rerouteCommand.getNavigatePath() == null || rerouteCommand.getNavigatePath().isEmpty()) { |
| | | continue; |
| | | } |
| | | candidateCommandList.add(rerouteCommand); |
| | | } |
| | | |
| | | StationV5RunBlockReroutePlanner.PlanResult planResult = runBlockReroutePlanner.plan( |
| | | taskNo, |
| | | stationId, |
| | | loopEvaluation, |
| | | candidateCommandList |
| | | ); |
| | | if (candidateCommandList.isEmpty()) { |
| | | log.warn("输送线堵塞重规划失败,候选路径为空,taskNo={}, planCount={}, stationId={}, targetStationId={}", |
| | | taskNo, rerouteState.getPlanCount(), stationId, targetStationId); |
| | | taskNo, planResult.getPlanCount(), stationId, targetStationId); |
| | | return null; |
| | | } |
| | | |
| | | StationCommand rerouteCommand = selectAvailableRerouteCommand( |
| | | rerouteState, |
| | | loopEvaluation, |
| | | candidatePathList, |
| | | taskNo, |
| | | stationId, |
| | | targetStationId, |
| | | palletSize |
| | | ); |
| | | if (rerouteCommand == null) { |
| | | log.info("输送线堵塞重规划候选路线已全部试过,重置路线历史后重新开始,taskNo={}, planCount={}, stationId={}, targetStationId={}", |
| | | taskNo, rerouteState.getPlanCount(), stationId, targetStationId); |
| | | rerouteState.resetIssuedRoutes(); |
| | | rerouteCommand = selectAvailableRerouteCommand( |
| | | rerouteState, |
| | | loopEvaluation, |
| | | candidatePathList, |
| | | taskNo, |
| | | stationId, |
| | | targetStationId, |
| | | palletSize |
| | | ); |
| | | } |
| | | |
| | | StationCommand rerouteCommand = planResult.getCommand(); |
| | | if (rerouteCommand != null) { |
| | | saveRunBlockRerouteState(rerouteState); |
| | | if (taskLoopService != null) { |
| | | taskLoopService.recordLoopIssue(loopEvaluation, "RUN_BLOCK_REROUTE"); |
| | | } |
| | | log.info("输送线堵塞重规划选中候选路线,taskNo={}, planCount={}, stationId={}, targetStationId={}, route={}", |
| | | taskNo, rerouteState.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteCommand.getNavigatePath())); |
| | | taskNo, planResult.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteCommand.getNavigatePath())); |
| | | return rerouteCommand; |
| | | } |
| | | |
| | | saveRunBlockRerouteState(rerouteState); |
| | | log.warn("输送线堵塞重规划未找到可下发路线,taskNo={}, planCount={}, stationId={}, targetStationId={}, triedRoutes={}", |
| | | taskNo, |
| | | rerouteState.getPlanCount(), |
| | | planResult.getPlanCount(), |
| | | stationId, |
| | | targetStationId, |
| | | JSON.toJSONString(rerouteState.getIssuedRoutePathList())); |
| | | JSON.toJSONString(planResult.getIssuedRoutePathList())); |
| | | return null; |
| | | } |
| | | |
| | |
| | | stationId, item.getSlotIdx(), item.getTaskNo()); |
| | | continue; |
| | | }else { |
| | | item.setTaskNo(0); |
| | | item.setTargetStaNo(0); |
| | | success = true; |
| | | log.warn("输送站缓存区残留路径清理成功。stationId={}, slotIdx={}, taskNo={}", |
| | | stationId, item.getSlotIdx(), item.getTaskNo()); |
| | |
| | | return stationCommand; |
| | | } |
| | | |
| | | private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState, |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation, |
| | | List<List<NavigateNode>> candidatePathList, |
| | | Integer taskNo, |
| | | Integer stationId, |
| | | Integer targetStationId, |
| | | Integer palletSize) { |
| | | if (rerouteState == null || candidatePathList == null || candidatePathList.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | Set<String> issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet(); |
| | | List<RerouteCandidateCommand> candidateCommandList = new ArrayList<>(); |
| | | for (List<NavigateNode> candidatePath : candidatePathList) { |
| | | StationCommand rerouteCommand = buildMoveCommand(taskNo, stationId, targetStationId, palletSize, candidatePath); |
| | | if (rerouteCommand == null || rerouteCommand.getNavigatePath() == null || rerouteCommand.getNavigatePath().isEmpty()) { |
| | | continue; |
| | | } |
| | | String routeSignature = buildPathSignature(rerouteCommand.getNavigatePath()); |
| | | if (Cools.isEmpty(routeSignature)) { |
| | | continue; |
| | | } |
| | | RerouteCandidateCommand candidateCommand = new RerouteCandidateCommand(); |
| | | candidateCommand.setCommand(rerouteCommand); |
| | | candidateCommand.setRouteSignature(routeSignature); |
| | | candidateCommand.setPathLength(rerouteCommand.getNavigatePath().size()); |
| | | candidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0)); |
| | | candidateCommand.setLoopFingerprint(loopEvaluation.getLoopIdentity().getLoopFingerprint()); |
| | | candidateCommand.setLoopIssuedCount(loopEvaluation.getExpectedLoopIssueCount()); |
| | | candidateCommand.setLoopTriggered(loopEvaluation.isLargeLoopTriggered()); |
| | | candidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit( |
| | | rerouteCommand.getNavigatePath(), |
| | | loopEvaluation.getLoopIdentity().getStationIdSet() |
| | | )); |
| | | candidateCommandList.add(candidateCommand); |
| | | } |
| | | if (candidateCommandList.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | List<RerouteCandidateCommand> orderedCandidateCommandList = reorderCandidateCommandsForLoopRelease(candidateCommandList); |
| | | for (RerouteCandidateCommand candidateCommand : orderedCandidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getCommand() == null) { |
| | | continue; |
| | | } |
| | | if (issuedRouteSignatureSet.contains(candidateCommand.getRouteSignature())) { |
| | | continue; |
| | | } |
| | | |
| | | StationCommand rerouteCommand = candidateCommand.getCommand(); |
| | | issuedRouteSignatureSet.add(candidateCommand.getRouteSignature()); |
| | | rerouteState.getIssuedRoutePathList().add(new ArrayList<>(rerouteCommand.getNavigatePath())); |
| | | rerouteState.setLastSelectedRoute(new ArrayList<>(rerouteCommand.getNavigatePath())); |
| | | rerouteState.getRouteIssueCountMap().put( |
| | | candidateCommand.getRouteSignature(), |
| | | rerouteState.getRouteIssueCountMap().getOrDefault(candidateCommand.getRouteSignature(), 0) + 1 |
| | | ); |
| | | return rerouteCommand; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private List<RerouteCandidateCommand> reorderCandidateCommandsForLoopRelease(List<RerouteCandidateCommand> candidateCommandList) { |
| | | if (candidateCommandList == null || candidateCommandList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | int shortestPathLength = Integer.MAX_VALUE; |
| | | int shortestPathLoopHitCount = Integer.MAX_VALUE; |
| | | boolean shortestPathOverused = false; |
| | | boolean currentLoopOverused = false; |
| | | boolean hasLongerCandidate = false; |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) { |
| | | continue; |
| | | } |
| | | shortestPathLength = Math.min(shortestPathLength, candidateCommand.getPathLength()); |
| | | } |
| | | if (shortestPathLength == Integer.MAX_VALUE) { |
| | | return candidateCommandList; |
| | | } |
| | | |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) { |
| | | continue; |
| | | } |
| | | if (candidateCommand.getPathLength() == shortestPathLength) { |
| | | shortestPathLoopHitCount = Math.min(shortestPathLoopHitCount, safeInt(candidateCommand.getCurrentLoopHitCount())); |
| | | } |
| | | if (candidateCommand.getPathLength() > shortestPathLength) { |
| | | hasLongerCandidate = true; |
| | | } |
| | | if (candidateCommand.getPathLength() == shortestPathLength |
| | | && candidateCommand.getIssuedCount() != null |
| | | && candidateCommand.getIssuedCount() >= SHORT_PATH_REPEAT_AVOID_THRESHOLD) { |
| | | shortestPathOverused = true; |
| | | } |
| | | if (!Cools.isEmpty(candidateCommand.getLoopFingerprint()) |
| | | && Boolean.TRUE.equals(candidateCommand.getLoopTriggered())) { |
| | | currentLoopOverused = true; |
| | | } |
| | | } |
| | | if (!shortestPathOverused && !currentLoopOverused) { |
| | | return candidateCommandList; |
| | | } |
| | | if (shortestPathLoopHitCount == Integer.MAX_VALUE) { |
| | | shortestPathLoopHitCount = 0; |
| | | } |
| | | |
| | | boolean hasLoopExitCandidate = false; |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null) { |
| | | continue; |
| | | } |
| | | if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) { |
| | | hasLoopExitCandidate = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!hasLongerCandidate && !hasLoopExitCandidate) { |
| | | return candidateCommandList; |
| | | } |
| | | |
| | | List<RerouteCandidateCommand> reorderedList = new ArrayList<>(); |
| | | if (currentLoopOverused && hasLoopExitCandidate) { |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null) { |
| | | continue; |
| | | } |
| | | if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) { |
| | | appendCandidateIfAbsent(reorderedList, candidateCommand); |
| | | } |
| | | } |
| | | } |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand != null |
| | | && candidateCommand.getPathLength() != null |
| | | && candidateCommand.getPathLength() > shortestPathLength) { |
| | | appendCandidateIfAbsent(reorderedList, candidateCommand); |
| | | } |
| | | } |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getPathLength() == null) { |
| | | continue; |
| | | } |
| | | appendCandidateIfAbsent(reorderedList, candidateCommand); |
| | | } |
| | | return reorderedList; |
| | | } |
| | | |
| | | private void appendCandidateIfAbsent(List<RerouteCandidateCommand> reorderedList, |
| | | RerouteCandidateCommand candidateCommand) { |
| | | if (reorderedList == null || candidateCommand == null) { |
| | | return; |
| | | } |
| | | if (!reorderedList.contains(candidateCommand)) { |
| | | reorderedList.add(candidateCommand); |
| | | } |
| | | } |
| | | |
| | | private RunBlockRerouteState loadRunBlockRerouteState(Integer taskNo, Integer blockStationId) { |
| | | if (redisUtil == null || taskNo == null || taskNo <= 0 || blockStationId == null || blockStationId <= 0) { |
| | | return new RunBlockRerouteState(); |
| | | } |
| | | Object stateObj = redisUtil.get(buildRunBlockRerouteStateKey(taskNo, blockStationId)); |
| | | if (stateObj == null) { |
| | | return new RunBlockRerouteState(); |
| | | } |
| | | try { |
| | | RunBlockRerouteState state = JSON.parseObject(String.valueOf(stateObj), RunBlockRerouteState.class); |
| | | return state == null ? new RunBlockRerouteState() : state.normalize(); |
| | | } catch (Exception ignore) { |
| | | return new RunBlockRerouteState(); |
| | | } |
| | | } |
| | | |
| | | private void saveRunBlockRerouteState(RunBlockRerouteState rerouteState) { |
| | | if (redisUtil == null |
| | | || rerouteState == null |
| | | || rerouteState.getTaskNo() == null |
| | | || rerouteState.getTaskNo() <= 0 |
| | | || rerouteState.getBlockStationId() == null |
| | | || rerouteState.getBlockStationId() <= 0) { |
| | | return; |
| | | } |
| | | rerouteState.normalize(); |
| | | redisUtil.set( |
| | | buildRunBlockRerouteStateKey(rerouteState.getTaskNo(), rerouteState.getBlockStationId()), |
| | | JSON.toJSONString(rerouteState), |
| | | RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS |
| | | ); |
| | | } |
| | | |
| | | private TaskLoopRerouteState loadTaskLoopRerouteState(Integer taskNo) { |
| | | if (redisUtil == null || taskNo == null || taskNo <= 0) { |
| | | return new TaskLoopRerouteState(); |
| | | } |
| | | Object stateObj = redisUtil.get(RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskNo); |
| | | if (stateObj == null) { |
| | | return new TaskLoopRerouteState(); |
| | | } |
| | | try { |
| | | TaskLoopRerouteState state = JSON.parseObject(String.valueOf(stateObj), TaskLoopRerouteState.class); |
| | | return state == null ? new TaskLoopRerouteState() : state.normalize(); |
| | | } catch (Exception ignore) { |
| | | return new TaskLoopRerouteState(); |
| | | } |
| | | } |
| | | |
| | | private void saveTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState) { |
| | | if (redisUtil == null |
| | | || taskLoopRerouteState == null |
| | | || taskLoopRerouteState.getTaskNo() == null |
| | | || taskLoopRerouteState.getTaskNo() <= 0) { |
| | | return; |
| | | } |
| | | taskLoopRerouteState.normalize(); |
| | | redisUtil.set( |
| | | RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskLoopRerouteState.getTaskNo(), |
| | | JSON.toJSONString(taskLoopRerouteState), |
| | | RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS |
| | | ); |
| | | } |
| | | |
| | | private void touchTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState, |
| | | LoopIdentity currentLoopIdentity) { |
| | | if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { |
| | | return; |
| | | } |
| | | taskLoopRerouteState.getLoopIssueCountMap().put( |
| | | currentLoopIdentity.getLoopFingerprint(), |
| | | taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0) + 1 |
| | | ); |
| | | taskLoopRerouteState.setLastLoopFingerprint(currentLoopIdentity.getLoopFingerprint()); |
| | | taskLoopRerouteState.setLastIssueTime(System.currentTimeMillis()); |
| | | } |
| | | |
| | | private int resolveCurrentLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState, |
| | | LoopIdentity currentLoopIdentity) { |
| | | if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { |
| | | return 0; |
| | | } |
| | | return taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0); |
| | | } |
| | | |
| | | private int resolveExpectedLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState, |
| | | LoopIdentity currentLoopIdentity) { |
| | | if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { |
| | | return 0; |
| | | } |
| | | return resolveCurrentLoopIssuedCount(taskLoopRerouteState, currentLoopIdentity) + 1; |
| | | } |
| | | |
| | | private boolean isLoopRepeatTriggered(Integer loopIssuedCount) { |
| | | return loopIssuedCount != null && loopIssuedCount >= LOOP_REPEAT_TRIGGER_COUNT; |
| | | } |
| | | |
| | | private void syncTaskTraceLoopAlert(Integer taskNo, |
| | | Integer blockedStationId, |
| | | LoopIdentity currentLoopIdentity, |
| | | int loopIssuedCount) { |
| | | StationTaskTraceRegistry traceRegistry; |
| | | try { |
| | | traceRegistry = SpringUtils.getBean(StationTaskTraceRegistry.class); |
| | | } catch (Exception ignore) { |
| | | return; |
| | | } |
| | | if (traceRegistry == null || taskNo == null || taskNo <= 0) { |
| | | return; |
| | | } |
| | | |
| | | boolean active = currentLoopIdentity != null |
| | | && !Cools.isEmpty(currentLoopIdentity.getLoopFingerprint()) |
| | | && isLoopRepeatTriggered(loopIssuedCount); |
| | | Map<String, Object> details = new HashMap<>(); |
| | | details.put("blockedStationId", blockedStationId); |
| | | details.put("loopScopeType", currentLoopIdentity == null ? "" : currentLoopIdentity.getScopeType()); |
| | | details.put("loopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getLocalStationCount()); |
| | | details.put("sourceLoopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getSourceLoopStationCount()); |
| | | details.put("loopRepeatCount", loopIssuedCount); |
| | | String loopAlertType = resolveLoopAlertType(currentLoopIdentity); |
| | | String loopAlertText = buildLoopAlertText(loopAlertType, currentLoopIdentity, loopIssuedCount); |
| | | traceRegistry.updateLoopHint(taskNo, active, loopAlertType, loopAlertText, loopIssuedCount, details); |
| | | } |
| | | |
| | | private String resolveLoopAlertType(LoopIdentity currentLoopIdentity) { |
| | | if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { |
| | | return ""; |
| | | } |
| | | return "wholeLoop".equals(currentLoopIdentity.getScopeType()) ? "LARGE_LOOP" : "SMALL_LOOP"; |
| | | } |
| | | |
| | | private String buildLoopAlertText(String loopAlertType, |
| | | LoopIdentity currentLoopIdentity, |
| | | int loopIssuedCount) { |
| | | if (Cools.isEmpty(loopAlertType) || currentLoopIdentity == null || !isLoopRepeatTriggered(loopIssuedCount)) { |
| | | return ""; |
| | | } |
| | | String typeLabel = "LARGE_LOOP".equals(loopAlertType) ? "大环线" : "小环线"; |
| | | return typeLabel + "绕圈预警,累计重规划" + loopIssuedCount + "次,当前识别范围" |
| | | + currentLoopIdentity.getLocalStationCount() + "站"; |
| | | } |
| | | |
| | | private int countCurrentLoopStationHit(List<Integer> path, Set<Integer> currentLoopStationIdSet) { |
| | | if (path == null || path.isEmpty() || currentLoopStationIdSet == null || currentLoopStationIdSet.isEmpty()) { |
| | | return 0; |
| | | } |
| | | int hitCount = 0; |
| | | for (Integer stationId : path) { |
| | | if (stationId != null && currentLoopStationIdSet.contains(stationId)) { |
| | | hitCount++; |
| | | } |
| | | } |
| | | return hitCount; |
| | | } |
| | | |
| | | private String buildPathSignature(List<Integer> path) { |
| | | if (path == null || path.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder builder = new StringBuilder(); |
| | | for (Integer stationNo : path) { |
| | | if (stationNo == null) { |
| | | continue; |
| | | } |
| | | if (builder.length() > 0) { |
| | | builder.append("->"); |
| | | } |
| | | builder.append(stationNo); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private StationTaskLoopService loadStationTaskLoopService() { |
| | | try { |
| | | return SpringUtils.getBean(StationTaskLoopService.class); |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) { |
| | | return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId; |
| | | } |
| | | |
| | | private LoopIdentity resolveStationLoopIdentity(Integer stationId) { |
| | | if (stationId == null || stationId <= 0) { |
| | | return LoopIdentity.empty(); |
| | | } |
| | | try { |
| | | StationCycleCapacityService stationCycleCapacityService = SpringUtils.getBean(StationCycleCapacityService.class); |
| | | if (stationCycleCapacityService == null) { |
| | | return LoopIdentity.empty(); |
| | | } |
| | | StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); |
| | | if (capacityVo == null || capacityVo.getLoopList() == null || capacityVo.getLoopList().isEmpty()) { |
| | | return LoopIdentity.empty(); |
| | | } |
| | | for (StationCycleLoopVo loopVo : capacityVo.getLoopList()) { |
| | | List<Integer> loopStationIdList = normalizeLoopStationIdList(loopVo == null ? null : loopVo.getStationIdList()); |
| | | if (loopStationIdList.isEmpty() || !loopStationIdList.contains(stationId)) { |
| | | continue; |
| | | } |
| | | Set<Integer> loopStationIdSet = new HashSet<>(loopStationIdList); |
| | | Map<Integer, Set<Integer>> stationGraph = loadUndirectedStationGraph(); |
| | | List<Integer> localCycleStationIdList = resolveLocalCycleStationIdList(stationId, loopStationIdSet, stationGraph); |
| | | if (localCycleStationIdList.size() >= 3) { |
| | | return buildLoopIdentity(localCycleStationIdList, loopStationIdList.size(), "localCycle"); |
| | | } |
| | | |
| | | List<Integer> localNeighborhoodStationIdList = resolveLocalNeighborhoodStationIdList(stationId, loopStationIdSet, stationGraph); |
| | | if (localNeighborhoodStationIdList.size() >= 3 && localNeighborhoodStationIdList.size() < loopStationIdList.size()) { |
| | | return buildLoopIdentity(localNeighborhoodStationIdList, loopStationIdList.size(), "localNeighborhood"); |
| | | } |
| | | return buildLoopIdentity(loopStationIdList, loopStationIdList.size(), "wholeLoop"); |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return LoopIdentity.empty(); |
| | | } |
| | | |
| | | private LoopIdentity buildLoopIdentity(List<Integer> stationIdList, |
| | | int sourceLoopStationCount, |
| | | String scopeType) { |
| | | List<Integer> normalizedStationIdList = normalizeLoopStationIdList(stationIdList); |
| | | if (normalizedStationIdList.isEmpty()) { |
| | | return LoopIdentity.empty(); |
| | | } |
| | | return new LoopIdentity( |
| | | buildLoopFingerprint(normalizedStationIdList), |
| | | new HashSet<>(normalizedStationIdList), |
| | | sourceLoopStationCount, |
| | | normalizedStationIdList.size(), |
| | | scopeType |
| | | ); |
| | | } |
| | | |
| | | private List<Integer> resolveLocalCycleStationIdList(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | if (stationId == null |
| | | || stationId <= 0 |
| | | || loopStationIdSet == null |
| | | || loopStationIdSet.isEmpty() |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | Set<Integer> localNeighborhoodStationIdSet = collectLoopNeighborhoodStationIdSet( |
| | | stationId, |
| | | loopStationIdSet, |
| | | stationGraph, |
| | | LOCAL_LOOP_NEIGHBOR_HOP |
| | | ); |
| | | if (localNeighborhoodStationIdSet.size() < 3) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | Set<Integer> directNeighborStationIdSet = filterLoopNeighborStationIdSet( |
| | | stationGraph.getOrDefault(stationId, Collections.emptySet()), |
| | | localNeighborhoodStationIdSet, |
| | | stationId |
| | | ); |
| | | if (directNeighborStationIdSet.size() < 2) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | List<Integer> bestCycleStationIdList = new ArrayList<>(); |
| | | List<Integer> neighborStationIdList = new ArrayList<>(directNeighborStationIdSet); |
| | | for (int i = 0; i < neighborStationIdList.size(); i++) { |
| | | Integer leftNeighborStationId = neighborStationIdList.get(i); |
| | | if (leftNeighborStationId == null) { |
| | | continue; |
| | | } |
| | | for (int j = i + 1; j < neighborStationIdList.size(); j++) { |
| | | Integer rightNeighborStationId = neighborStationIdList.get(j); |
| | | if (rightNeighborStationId == null) { |
| | | continue; |
| | | } |
| | | List<Integer> pathBetweenNeighbors = findShortestScopePath( |
| | | leftNeighborStationId, |
| | | rightNeighborStationId, |
| | | stationId, |
| | | localNeighborhoodStationIdSet, |
| | | stationGraph |
| | | ); |
| | | if (pathBetweenNeighbors.isEmpty()) { |
| | | continue; |
| | | } |
| | | List<Integer> cycleStationIdList = new ArrayList<>(); |
| | | cycleStationIdList.add(stationId); |
| | | cycleStationIdList.addAll(pathBetweenNeighbors); |
| | | cycleStationIdList = normalizeLoopStationIdList(cycleStationIdList); |
| | | if (cycleStationIdList.size() < 3) { |
| | | continue; |
| | | } |
| | | if (bestCycleStationIdList.isEmpty() || cycleStationIdList.size() < bestCycleStationIdList.size()) { |
| | | bestCycleStationIdList = cycleStationIdList; |
| | | } |
| | | } |
| | | } |
| | | return bestCycleStationIdList; |
| | | } |
| | | |
| | | private List<Integer> resolveLocalNeighborhoodStationIdList(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | return normalizeLoopStationIdList(new ArrayList<>(collectLoopNeighborhoodStationIdSet( |
| | | stationId, |
| | | loopStationIdSet, |
| | | stationGraph, |
| | | LOCAL_LOOP_NEIGHBOR_HOP |
| | | ))); |
| | | } |
| | | |
| | | private Set<Integer> collectLoopNeighborhoodStationIdSet(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph, |
| | | int maxHop) { |
| | | Set<Integer> neighborhoodStationIdSet = new LinkedHashSet<>(); |
| | | if (stationId == null |
| | | || stationId <= 0 |
| | | || loopStationIdSet == null |
| | | || loopStationIdSet.isEmpty() |
| | | || !loopStationIdSet.contains(stationId) |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty() |
| | | || maxHop < 0) { |
| | | return neighborhoodStationIdSet; |
| | | } |
| | | |
| | | Deque<StationHopNode> queue = new ArrayDeque<>(); |
| | | queue.offer(new StationHopNode(stationId, 0)); |
| | | neighborhoodStationIdSet.add(stationId); |
| | | while (!queue.isEmpty()) { |
| | | StationHopNode current = queue.poll(); |
| | | if (current == null || current.getHop() >= maxHop) { |
| | | continue; |
| | | } |
| | | Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet( |
| | | stationGraph.getOrDefault(current.getStationId(), Collections.emptySet()), |
| | | loopStationIdSet, |
| | | null |
| | | ); |
| | | for (Integer neighborStationId : neighborStationIdSet) { |
| | | if (neighborStationId == null || !neighborhoodStationIdSet.add(neighborStationId)) { |
| | | continue; |
| | | } |
| | | queue.offer(new StationHopNode(neighborStationId, current.getHop() + 1)); |
| | | } |
| | | } |
| | | return neighborhoodStationIdSet; |
| | | } |
| | | |
| | | private Set<Integer> filterLoopNeighborStationIdSet(Set<Integer> candidateNeighborStationIdSet, |
| | | Set<Integer> allowedStationIdSet, |
| | | Integer excludedStationId) { |
| | | Set<Integer> result = new LinkedHashSet<>(); |
| | | if (candidateNeighborStationIdSet == null || candidateNeighborStationIdSet.isEmpty() |
| | | || allowedStationIdSet == null || allowedStationIdSet.isEmpty()) { |
| | | return result; |
| | | } |
| | | for (Integer stationId : candidateNeighborStationIdSet) { |
| | | if (stationId == null |
| | | || !allowedStationIdSet.contains(stationId) |
| | | || (excludedStationId != null && excludedStationId.equals(stationId))) { |
| | | continue; |
| | | } |
| | | result.add(stationId); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<Integer> findShortestScopePath(Integer startStationId, |
| | | Integer endStationId, |
| | | Integer excludedStationId, |
| | | Set<Integer> allowedStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | if (startStationId == null |
| | | || endStationId == null |
| | | || Objects.equals(startStationId, excludedStationId) |
| | | || Objects.equals(endStationId, excludedStationId) |
| | | || allowedStationIdSet == null |
| | | || !allowedStationIdSet.contains(startStationId) |
| | | || !allowedStationIdSet.contains(endStationId) |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | Deque<Integer> queue = new ArrayDeque<>(); |
| | | Map<Integer, Integer> parentMap = new HashMap<>(); |
| | | Set<Integer> visitedStationIdSet = new HashSet<>(); |
| | | queue.offer(startStationId); |
| | | visitedStationIdSet.add(startStationId); |
| | | while (!queue.isEmpty()) { |
| | | Integer currentStationId = queue.poll(); |
| | | if (currentStationId == null) { |
| | | continue; |
| | | } |
| | | if (Objects.equals(currentStationId, endStationId)) { |
| | | break; |
| | | } |
| | | Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet( |
| | | stationGraph.getOrDefault(currentStationId, Collections.emptySet()), |
| | | allowedStationIdSet, |
| | | excludedStationId |
| | | ); |
| | | for (Integer neighborStationId : neighborStationIdSet) { |
| | | if (neighborStationId == null || !visitedStationIdSet.add(neighborStationId)) { |
| | | continue; |
| | | } |
| | | parentMap.put(neighborStationId, currentStationId); |
| | | queue.offer(neighborStationId); |
| | | } |
| | | } |
| | | if (!visitedStationIdSet.contains(endStationId)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | List<Integer> pathStationIdList = new ArrayList<>(); |
| | | Integer cursorStationId = endStationId; |
| | | while (cursorStationId != null) { |
| | | pathStationIdList.add(cursorStationId); |
| | | if (Objects.equals(cursorStationId, startStationId)) { |
| | | break; |
| | | } |
| | | cursorStationId = parentMap.get(cursorStationId); |
| | | } |
| | | if (pathStationIdList.isEmpty() |
| | | || !Objects.equals(pathStationIdList.get(pathStationIdList.size() - 1), startStationId)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | Collections.reverse(pathStationIdList); |
| | | return pathStationIdList; |
| | | } |
| | | |
| | | private Map<Integer, Set<Integer>> loadUndirectedStationGraph() { |
| | | NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class); |
| | | if (navigateUtils == null) { |
| | | return new HashMap<>(); |
| | | } |
| | | Map<Integer, Set<Integer>> stationGraph = navigateUtils.loadUndirectedStationGraphSnapshot(); |
| | | return stationGraph == null ? new HashMap<>() : stationGraph; |
| | | } |
| | | |
| | | private List<Integer> normalizeLoopStationIdList(List<Integer> stationIdList) { |
| | | if (stationIdList == null || stationIdList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | List<Integer> normalizedList = new ArrayList<>(); |
| | | Set<Integer> seenStationIdSet = new HashSet<>(); |
| | | for (Integer stationId : stationIdList) { |
| | | if (stationId == null || stationId <= 0 || !seenStationIdSet.add(stationId)) { |
| | | continue; |
| | | } |
| | | normalizedList.add(stationId); |
| | | } |
| | | Collections.sort(normalizedList); |
| | | return normalizedList; |
| | | } |
| | | |
| | | private String buildLoopFingerprint(List<Integer> stationIdList) { |
| | | if (stationIdList == null || stationIdList.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder builder = new StringBuilder(); |
| | | for (Integer stationId : stationIdList) { |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | if (builder.length() > 0) { |
| | | builder.append("|"); |
| | | } |
| | | builder.append(stationId); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private int safeInt(Integer value) { |
| | | return value == null ? 0 : value; |
| | | } |
| | | |
| | | @Data |
| | | private static class RunBlockRerouteState { |
| | | private Integer taskNo; |
| | | private Integer blockStationId; |
| | | private Integer planCount = 0; |
| | | private Integer lastTargetStationId; |
| | | private Long lastPlanTime; |
| | | private List<List<Integer>> issuedRoutePathList = new ArrayList<>(); |
| | | private List<Integer> lastSelectedRoute = new ArrayList<>(); |
| | | private Set<String> issuedRouteSignatureSet = new LinkedHashSet<>(); |
| | | private Map<String, Integer> routeIssueCountMap = new HashMap<>(); |
| | | |
| | | private RunBlockRerouteState normalize() { |
| | | if (planCount == null || planCount < 0) { |
| | | planCount = 0; |
| | | } |
| | | if (issuedRoutePathList == null) { |
| | | issuedRoutePathList = new ArrayList<>(); |
| | | } |
| | | if (lastSelectedRoute == null) { |
| | | lastSelectedRoute = new ArrayList<>(); |
| | | } |
| | | if (issuedRouteSignatureSet == null) { |
| | | issuedRouteSignatureSet = new LinkedHashSet<>(); |
| | | } |
| | | if (routeIssueCountMap == null) { |
| | | routeIssueCountMap = new HashMap<>(); |
| | | } |
| | | for (List<Integer> routePath : issuedRoutePathList) { |
| | | if (routePath == null || routePath.isEmpty()) { |
| | | continue; |
| | | } |
| | | String pathSignature = buildPathSignatureText(routePath); |
| | | if (!Cools.isEmpty(pathSignature)) { |
| | | issuedRouteSignatureSet.add(pathSignature); |
| | | routeIssueCountMap.putIfAbsent(pathSignature, 1); |
| | | } |
| | | } |
| | | return this; |
| | | } |
| | | |
| | | private void resetIssuedRoutes() { |
| | | this.issuedRoutePathList = new ArrayList<>(); |
| | | this.lastSelectedRoute = new ArrayList<>(); |
| | | this.issuedRouteSignatureSet = new LinkedHashSet<>(); |
| | | } |
| | | |
| | | private static String buildPathSignatureText(List<Integer> routePath) { |
| | | if (routePath == null || routePath.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder builder = new StringBuilder(); |
| | | for (Integer stationId : routePath) { |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | if (builder.length() > 0) { |
| | | builder.append("->"); |
| | | } |
| | | builder.append(stationId); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | } |
| | | |
| | | @Data |
| | | private static class RerouteCandidateCommand { |
| | | private StationCommand command; |
| | | private String routeSignature; |
| | | private Integer pathLength; |
| | | private Integer issuedCount; |
| | | private String loopFingerprint; |
| | | private Integer loopIssuedCount; |
| | | private Boolean loopTriggered; |
| | | private Integer currentLoopHitCount; |
| | | } |
| | | |
| | | @Data |
| | | private static class TaskLoopRerouteState { |
| | | private Integer taskNo; |
| | | private String lastLoopFingerprint; |
| | | private Long lastIssueTime; |
| | | private Map<String, Integer> loopIssueCountMap = new HashMap<>(); |
| | | |
| | | private TaskLoopRerouteState normalize() { |
| | | if (loopIssueCountMap == null) { |
| | | loopIssueCountMap = new HashMap<>(); |
| | | } |
| | | return this; |
| | | } |
| | | } |
| | | |
| | | @Data |
| | | private static class LoopIdentity { |
| | | private String loopFingerprint; |
| | | private Set<Integer> stationIdSet = new HashSet<>(); |
| | | private int sourceLoopStationCount; |
| | | private int localStationCount; |
| | | private String scopeType; |
| | | |
| | | private LoopIdentity(String loopFingerprint, |
| | | Set<Integer> stationIdSet, |
| | | int sourceLoopStationCount, |
| | | int localStationCount, |
| | | String scopeType) { |
| | | this.loopFingerprint = loopFingerprint; |
| | | this.stationIdSet = stationIdSet == null ? new HashSet<>() : stationIdSet; |
| | | this.sourceLoopStationCount = sourceLoopStationCount; |
| | | this.localStationCount = localStationCount; |
| | | this.scopeType = scopeType == null ? "" : scopeType; |
| | | } |
| | | |
| | | private static LoopIdentity empty() { |
| | | return new LoopIdentity("", new HashSet<>(), 0, 0, "none"); |
| | | } |
| | | } |
| | | |
| | | @Data |
| | | private static class StationHopNode { |
| | | private final Integer stationId; |
| | | private final int hop; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.thread.impl.v5; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.core.common.Cools; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.service.StationTaskLoopService; |
| | | import lombok.Data; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | public class StationV5RunBlockReroutePlanner { |
| | | |
| | | private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24; |
| | | private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2; |
| | | |
| | | private final RedisUtil redisUtil; |
| | | |
| | | public StationV5RunBlockReroutePlanner(RedisUtil redisUtil) { |
| | | this.redisUtil = redisUtil; |
| | | } |
| | | |
| | | public PlanResult plan(Integer taskNo, |
| | | Integer blockStationId, |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation, |
| | | List<StationCommand> candidateCommands) { |
| | | RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, blockStationId); |
| | | rerouteState.setTaskNo(taskNo); |
| | | rerouteState.setBlockStationId(blockStationId); |
| | | rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1); |
| | | |
| | | StationCommand rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands); |
| | | if (rerouteCommand == null && candidateCommands != null && !candidateCommands.isEmpty()) { |
| | | rerouteState.resetIssuedRoutes(); |
| | | rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands); |
| | | } |
| | | |
| | | saveRunBlockRerouteState(rerouteState); |
| | | return new PlanResult( |
| | | rerouteCommand, |
| | | rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount(), |
| | | copyRoutePathList(rerouteState.getIssuedRoutePathList()) |
| | | ); |
| | | } |
| | | |
| | | private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState, |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation, |
| | | List<StationCommand> candidateCommands) { |
| | | if (rerouteState == null || candidateCommands == null || candidateCommands.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | Set<String> issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet(); |
| | | StationTaskLoopService.LoopIdentitySnapshot loopIdentity = loopEvaluation == null |
| | | ? StationTaskLoopService.LoopIdentitySnapshot.empty() |
| | | : loopEvaluation.getLoopIdentity(); |
| | | if (loopIdentity == null) { |
| | | loopIdentity = StationTaskLoopService.LoopIdentitySnapshot.empty(); |
| | | } |
| | | |
| | | List<RerouteCandidateCommand> candidateCommandList = new ArrayList<>(); |
| | | for (StationCommand candidateCommand : candidateCommands) { |
| | | if (candidateCommand == null || candidateCommand.getNavigatePath() == null || candidateCommand.getNavigatePath().isEmpty()) { |
| | | continue; |
| | | } |
| | | String routeSignature = buildPathSignature(candidateCommand.getNavigatePath()); |
| | | if (Cools.isEmpty(routeSignature)) { |
| | | continue; |
| | | } |
| | | RerouteCandidateCommand rerouteCandidateCommand = new RerouteCandidateCommand(); |
| | | rerouteCandidateCommand.setCommand(candidateCommand); |
| | | rerouteCandidateCommand.setRouteSignature(routeSignature); |
| | | rerouteCandidateCommand.setPathLength(candidateCommand.getNavigatePath().size()); |
| | | rerouteCandidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0)); |
| | | rerouteCandidateCommand.setLoopFingerprint(loopIdentity.getLoopFingerprint()); |
| | | rerouteCandidateCommand.setLoopTriggered(loopEvaluation != null && loopEvaluation.isLargeLoopTriggered()); |
| | | rerouteCandidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit( |
| | | candidateCommand.getNavigatePath(), |
| | | loopIdentity.getStationIdSet() |
| | | )); |
| | | candidateCommandList.add(rerouteCandidateCommand); |
| | | } |
| | | if (candidateCommandList.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | List<RerouteCandidateCommand> orderedCandidateCommandList = reorderCandidateCommandsForLoopRelease(candidateCommandList); |
| | | for (RerouteCandidateCommand candidateCommand : orderedCandidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getCommand() == null) { |
| | | continue; |
| | | } |
| | | if (issuedRouteSignatureSet.contains(candidateCommand.getRouteSignature())) { |
| | | continue; |
| | | } |
| | | |
| | | StationCommand rerouteCommand = candidateCommand.getCommand(); |
| | | issuedRouteSignatureSet.add(candidateCommand.getRouteSignature()); |
| | | rerouteState.getIssuedRoutePathList().add(new ArrayList<>(rerouteCommand.getNavigatePath())); |
| | | rerouteState.setLastSelectedRoute(new ArrayList<>(rerouteCommand.getNavigatePath())); |
| | | rerouteState.getRouteIssueCountMap().put( |
| | | candidateCommand.getRouteSignature(), |
| | | rerouteState.getRouteIssueCountMap().getOrDefault(candidateCommand.getRouteSignature(), 0) + 1 |
| | | ); |
| | | return rerouteCommand; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private List<RerouteCandidateCommand> reorderCandidateCommandsForLoopRelease(List<RerouteCandidateCommand> candidateCommandList) { |
| | | if (candidateCommandList == null || candidateCommandList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | int shortestPathLength = Integer.MAX_VALUE; |
| | | int shortestPathLoopHitCount = Integer.MAX_VALUE; |
| | | boolean shortestPathOverused = false; |
| | | boolean currentLoopOverused = false; |
| | | boolean hasLongerCandidate = false; |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) { |
| | | continue; |
| | | } |
| | | shortestPathLength = Math.min(shortestPathLength, candidateCommand.getPathLength()); |
| | | } |
| | | if (shortestPathLength == Integer.MAX_VALUE) { |
| | | return candidateCommandList; |
| | | } |
| | | |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) { |
| | | continue; |
| | | } |
| | | if (candidateCommand.getPathLength() == shortestPathLength) { |
| | | shortestPathLoopHitCount = Math.min(shortestPathLoopHitCount, safeInt(candidateCommand.getCurrentLoopHitCount())); |
| | | } |
| | | if (candidateCommand.getPathLength() > shortestPathLength) { |
| | | hasLongerCandidate = true; |
| | | } |
| | | if (candidateCommand.getPathLength() == shortestPathLength |
| | | && candidateCommand.getIssuedCount() != null |
| | | && candidateCommand.getIssuedCount() >= SHORT_PATH_REPEAT_AVOID_THRESHOLD) { |
| | | shortestPathOverused = true; |
| | | } |
| | | if (!Cools.isEmpty(candidateCommand.getLoopFingerprint()) |
| | | && Boolean.TRUE.equals(candidateCommand.getLoopTriggered())) { |
| | | currentLoopOverused = true; |
| | | } |
| | | } |
| | | if (!shortestPathOverused && !currentLoopOverused) { |
| | | return candidateCommandList; |
| | | } |
| | | if (shortestPathLoopHitCount == Integer.MAX_VALUE) { |
| | | shortestPathLoopHitCount = 0; |
| | | } |
| | | |
| | | boolean hasLoopExitCandidate = false; |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null) { |
| | | continue; |
| | | } |
| | | if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) { |
| | | hasLoopExitCandidate = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!hasLongerCandidate && !hasLoopExitCandidate) { |
| | | return candidateCommandList; |
| | | } |
| | | |
| | | List<RerouteCandidateCommand> reorderedList = new ArrayList<>(); |
| | | if (currentLoopOverused && hasLoopExitCandidate) { |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null) { |
| | | continue; |
| | | } |
| | | if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) { |
| | | appendCandidateIfAbsent(reorderedList, candidateCommand); |
| | | } |
| | | } |
| | | } |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand != null |
| | | && candidateCommand.getPathLength() != null |
| | | && candidateCommand.getPathLength() > shortestPathLength) { |
| | | appendCandidateIfAbsent(reorderedList, candidateCommand); |
| | | } |
| | | } |
| | | for (RerouteCandidateCommand candidateCommand : candidateCommandList) { |
| | | if (candidateCommand == null || candidateCommand.getPathLength() == null) { |
| | | continue; |
| | | } |
| | | appendCandidateIfAbsent(reorderedList, candidateCommand); |
| | | } |
| | | return reorderedList; |
| | | } |
| | | |
| | | private void appendCandidateIfAbsent(List<RerouteCandidateCommand> reorderedList, |
| | | RerouteCandidateCommand candidateCommand) { |
| | | if (reorderedList == null || candidateCommand == null) { |
| | | return; |
| | | } |
| | | if (!reorderedList.contains(candidateCommand)) { |
| | | reorderedList.add(candidateCommand); |
| | | } |
| | | } |
| | | |
| | | private RunBlockRerouteState loadRunBlockRerouteState(Integer taskNo, Integer blockStationId) { |
| | | if (redisUtil == null || taskNo == null || taskNo <= 0 || blockStationId == null || blockStationId <= 0) { |
| | | return new RunBlockRerouteState(); |
| | | } |
| | | Object stateObj = redisUtil.get(buildRunBlockRerouteStateKey(taskNo, blockStationId)); |
| | | if (stateObj == null) { |
| | | return new RunBlockRerouteState(); |
| | | } |
| | | try { |
| | | RunBlockRerouteState state = JSON.parseObject(String.valueOf(stateObj), RunBlockRerouteState.class); |
| | | return state == null ? new RunBlockRerouteState() : state.normalize(); |
| | | } catch (Exception ignore) { |
| | | return new RunBlockRerouteState(); |
| | | } |
| | | } |
| | | |
| | | private void saveRunBlockRerouteState(RunBlockRerouteState rerouteState) { |
| | | if (redisUtil == null |
| | | || rerouteState == null |
| | | || rerouteState.getTaskNo() == null |
| | | || rerouteState.getTaskNo() <= 0 |
| | | || rerouteState.getBlockStationId() == null |
| | | || rerouteState.getBlockStationId() <= 0) { |
| | | return; |
| | | } |
| | | rerouteState.normalize(); |
| | | redisUtil.set( |
| | | buildRunBlockRerouteStateKey(rerouteState.getTaskNo(), rerouteState.getBlockStationId()), |
| | | JSON.toJSONString(rerouteState), |
| | | RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS |
| | | ); |
| | | } |
| | | |
| | | private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) { |
| | | return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId; |
| | | } |
| | | |
| | | private int countCurrentLoopStationHit(List<Integer> path, Set<Integer> currentLoopStationIdSet) { |
| | | if (path == null || path.isEmpty() || currentLoopStationIdSet == null || currentLoopStationIdSet.isEmpty()) { |
| | | return 0; |
| | | } |
| | | int hitCount = 0; |
| | | for (Integer stationId : path) { |
| | | if (stationId != null && currentLoopStationIdSet.contains(stationId)) { |
| | | hitCount++; |
| | | } |
| | | } |
| | | return hitCount; |
| | | } |
| | | |
| | | private String buildPathSignature(List<Integer> path) { |
| | | if (path == null || path.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder builder = new StringBuilder(); |
| | | for (Integer stationNo : path) { |
| | | if (stationNo == null) { |
| | | continue; |
| | | } |
| | | if (builder.length() > 0) { |
| | | builder.append("->"); |
| | | } |
| | | builder.append(stationNo); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private int safeInt(Integer value) { |
| | | return value == null ? 0 : value; |
| | | } |
| | | |
| | | private List<List<Integer>> copyRoutePathList(List<List<Integer>> source) { |
| | | List<List<Integer>> copy = new ArrayList<>(); |
| | | if (source == null || source.isEmpty()) { |
| | | return copy; |
| | | } |
| | | for (List<Integer> route : source) { |
| | | copy.add(route == null ? new ArrayList<>() : new ArrayList<>(route)); |
| | | } |
| | | return copy; |
| | | } |
| | | |
| | | @Data |
| | | public static class PlanResult { |
| | | private final StationCommand command; |
| | | private final int planCount; |
| | | private final List<List<Integer>> issuedRoutePathList; |
| | | } |
| | | |
| | | @Data |
| | | private static class RunBlockRerouteState { |
| | | private Integer taskNo; |
| | | private Integer blockStationId; |
| | | private Integer planCount = 0; |
| | | private List<List<Integer>> issuedRoutePathList = new ArrayList<>(); |
| | | private List<Integer> lastSelectedRoute = new ArrayList<>(); |
| | | private Set<String> issuedRouteSignatureSet = new LinkedHashSet<>(); |
| | | private Map<String, Integer> routeIssueCountMap = new HashMap<>(); |
| | | |
| | | private RunBlockRerouteState normalize() { |
| | | if (planCount == null || planCount < 0) { |
| | | planCount = 0; |
| | | } |
| | | if (issuedRoutePathList == null) { |
| | | issuedRoutePathList = new ArrayList<>(); |
| | | } |
| | | if (lastSelectedRoute == null) { |
| | | lastSelectedRoute = new ArrayList<>(); |
| | | } |
| | | if (issuedRouteSignatureSet == null) { |
| | | issuedRouteSignatureSet = new LinkedHashSet<>(); |
| | | } |
| | | if (routeIssueCountMap == null) { |
| | | routeIssueCountMap = new HashMap<>(); |
| | | } |
| | | for (List<Integer> routePath : issuedRoutePathList) { |
| | | if (routePath == null || routePath.isEmpty()) { |
| | | continue; |
| | | } |
| | | String pathSignature = buildPathSignatureText(routePath); |
| | | if (!Cools.isEmpty(pathSignature)) { |
| | | issuedRouteSignatureSet.add(pathSignature); |
| | | routeIssueCountMap.putIfAbsent(pathSignature, 1); |
| | | } |
| | | } |
| | | return this; |
| | | } |
| | | |
| | | private void resetIssuedRoutes() { |
| | | this.issuedRoutePathList = new ArrayList<>(); |
| | | this.lastSelectedRoute = new ArrayList<>(); |
| | | this.issuedRouteSignatureSet = new LinkedHashSet<>(); |
| | | } |
| | | |
| | | private static String buildPathSignatureText(List<Integer> routePath) { |
| | | if (routePath == null || routePath.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder builder = new StringBuilder(); |
| | | for (Integer stationId : routePath) { |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | if (builder.length() > 0) { |
| | | builder.append("->"); |
| | | } |
| | | builder.append(stationId); |
| | | } |
| | | return builder.toString(); |
| | | } |
| | | } |
| | | |
| | | @Data |
| | | private static class RerouteCandidateCommand { |
| | | private StationCommand command; |
| | | private String routeSignature; |
| | | private Integer pathLength; |
| | | private Integer issuedCount; |
| | | private String loopFingerprint; |
| | | private Boolean loopTriggered; |
| | | private Integer currentLoopHitCount; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.thread.impl.v5; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.core.common.Cools; |
| | | import com.core.common.DateUtils; |
| | | import com.core.common.SpringUtils; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.DeviceConfig; |
| | | import com.zy.asrs.entity.DeviceDataLog; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.utils.Utils; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.cache.OutputQueue; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.network.ZyStationConnectDriver; |
| | | import com.zy.core.network.entity.ZyStationStatusEntity; |
| | | import com.zy.core.thread.support.RecentStationArrivalTracker; |
| | | import com.zy.core.utils.DeviceLogRedisKeyBuilder; |
| | | |
| | | import java.text.MessageFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | public class StationV5StatusReader { |
| | | |
| | | private final DeviceConfig deviceConfig; |
| | | private final RedisUtil redisUtil; |
| | | private final RecentStationArrivalTracker recentArrivalTracker; |
| | | private final List<StationProtocol> statusList = new ArrayList<>(); |
| | | private boolean initialized = false; |
| | | private long deviceDataLogTime = System.currentTimeMillis(); |
| | | |
| | | public StationV5StatusReader(DeviceConfig deviceConfig, |
| | | RedisUtil redisUtil, |
| | | RecentStationArrivalTracker recentArrivalTracker) { |
| | | this.deviceConfig = deviceConfig; |
| | | this.redisUtil = redisUtil; |
| | | this.recentArrivalTracker = recentArrivalTracker; |
| | | } |
| | | |
| | | public void readStatus(ZyStationConnectDriver zyStationConnectDriver) { |
| | | if (zyStationConnectDriver == null) { |
| | | return; |
| | | } |
| | | |
| | | if (statusList.isEmpty()) { |
| | | BasDevpService basDevpService = null; |
| | | try { |
| | | basDevpService = SpringUtils.getBean(BasDevpService.class); |
| | | } catch (Exception ignore) { |
| | | } |
| | | if (basDevpService == null) { |
| | | return; |
| | | } |
| | | |
| | | BasDevp basDevp = basDevpService |
| | | .getOne(new QueryWrapper<BasDevp>().eq("devp_no", deviceConfig.getDeviceNo())); |
| | | if (basDevp == null) { |
| | | return; |
| | | } |
| | | |
| | | List<ZyStationStatusEntity> list = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class); |
| | | for (ZyStationStatusEntity entity : list) { |
| | | StationProtocol stationProtocol = new StationProtocol(); |
| | | stationProtocol.setStationId(entity.getStationId()); |
| | | statusList.add(stationProtocol); |
| | | } |
| | | initialized = true; |
| | | } |
| | | |
| | | int deviceLogCollectTime = initialized ? Utils.getDeviceLogCollectTime() : 200; |
| | | List<ZyStationStatusEntity> zyStationStatusEntities = zyStationConnectDriver.getStatus(); |
| | | for (ZyStationStatusEntity statusEntity : zyStationStatusEntities) { |
| | | for (StationProtocol stationProtocol : statusList) { |
| | | if (stationProtocol.getStationId().equals(statusEntity.getStationId())) { |
| | | stationProtocol.setTaskNo(statusEntity.getTaskNo()); |
| | | stationProtocol.setTargetStaNo(statusEntity.getTargetStaNo()); |
| | | stationProtocol.setAutoing(statusEntity.isAutoing()); |
| | | stationProtocol.setLoading(statusEntity.isLoading()); |
| | | stationProtocol.setInEnable(statusEntity.isInEnable()); |
| | | stationProtocol.setOutEnable(statusEntity.isOutEnable()); |
| | | stationProtocol.setEmptyMk(statusEntity.isEmptyMk()); |
| | | stationProtocol.setFullPlt(statusEntity.isFullPlt()); |
| | | stationProtocol.setPalletHeight(statusEntity.getPalletHeight()); |
| | | stationProtocol.setError(statusEntity.getError()); |
| | | stationProtocol.setErrorMsg(statusEntity.getErrorMsg()); |
| | | stationProtocol.setBarcode(statusEntity.getBarcode()); |
| | | stationProtocol.setRunBlock(statusEntity.isRunBlock()); |
| | | stationProtocol.setEnableIn(statusEntity.isEnableIn()); |
| | | stationProtocol.setWeight(statusEntity.getWeight()); |
| | | stationProtocol.setTaskWriteIdx(statusEntity.getTaskWriteIdx()); |
| | | stationProtocol.setTaskBufferItems(statusEntity.getTaskBufferItems()); |
| | | recentArrivalTracker.observe(statusEntity.getStationId(), statusEntity.getTaskNo(), statusEntity.isLoading()); |
| | | } |
| | | |
| | | if (!Cools.isEmpty(stationProtocol.getSystemWarning())) { |
| | | if (stationProtocol.isAutoing() && !stationProtocol.isLoading()) { |
| | | stationProtocol.setSystemWarning(""); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功", |
| | | DateUtils.convert(new Date()), deviceConfig.getDeviceNo())); |
| | | |
| | | if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) { |
| | | DeviceDataLog deviceDataLog = new DeviceDataLog(); |
| | | deviceDataLog.setOriginData(JSON.toJSONString(zyStationStatusEntities)); |
| | | deviceDataLog.setWcsData(JSON.toJSONString(statusList)); |
| | | deviceDataLog.setType(String.valueOf(SlaveType.Devp)); |
| | | deviceDataLog.setDeviceNo(deviceConfig.getDeviceNo()); |
| | | deviceDataLog.setCreateTime(new Date()); |
| | | |
| | | redisUtil.set(DeviceLogRedisKeyBuilder.build(deviceDataLog), deviceDataLog, 60 * 60 * 24); |
| | | deviceDataLogTime = System.currentTimeMillis(); |
| | | } |
| | | } |
| | | |
| | | public List<StationProtocol> getStatusList() { |
| | | return statusList; |
| | | } |
| | | } |
| | |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.*; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.Task; |
| | |
| | | private StationOperateProcessUtils stationOperateProcessUtils; |
| | | @Autowired |
| | | private WrkAnalysisService wrkAnalysisService; |
| | | @Autowired |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | |
| | | private static final String CRN_OUT_REQUIRE_STATION_OUT_ENABLE_CONFIG = "crnOutRequireStationOutEnable"; |
| | | |
| | |
| | | } |
| | | //生成仿真站点数据 |
| | | StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9998, wrkMast.getSourceStaNo(), 0, 0); |
| | | MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command)); |
| | | stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "dual-crn-operate-process", "fake-out-complete-write-info"); |
| | | } |
| | | } |
| | | |
| | |
| | | package com.zy.core.utils; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.alibaba.fastjson.serializer.SerializerFeature; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.core.common.Cools; |
| | | import com.core.exception.CoolException; |
| | | import com.zy.asrs.domain.enums.NotifyMsgType; |
| | | import com.zy.asrs.domain.path.StationPathResolvedPolicy; |
| | | import com.zy.asrs.domain.vo.StationCycleCapacityVo; |
| | | import com.zy.asrs.domain.vo.StationCycleLoopVo; |
| | | import com.zy.asrs.entity.*; |
| | | import com.zy.asrs.service.*; |
| | | import com.zy.asrs.utils.NotifyUtils; |
| | | import com.zy.common.entity.FindCrnNoResult; |
| | | import com.zy.common.model.NavigateNode; |
| | | import com.zy.common.model.StartupDto; |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.NavigateUtils; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.move.StationMoveDispatchMode; |
| | | import com.zy.core.move.StationMoveSession; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.enums.*; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.Task; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.service.StationTaskLoopService; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.core.enums.WrkIoType; |
| | | import com.zy.core.enums.WrkStsType; |
| | | import com.zy.core.utils.station.StationDispatchLoadSupport; |
| | | import com.zy.core.utils.station.StationOutboundDispatchProcessor; |
| | | import com.zy.core.utils.station.StationRegularDispatchProcessor; |
| | | import com.zy.core.utils.station.StationRerouteProcessor; |
| | | import com.zy.core.utils.station.model.RerouteCommandPlan; |
| | | import com.zy.core.utils.station.model.RerouteContext; |
| | | import com.zy.core.utils.station.model.RerouteDecision; |
| | | import com.zy.core.utils.station.model.RerouteExecutionResult; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.*; |
| | | import java.util.List; |
| | | |
| | | @Component |
| | | public class StationOperateProcessUtils { |
| | | private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120; |
| | | private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2; |
| | | private static final int STATION_COMMAND_DISPATCH_DEDUP_SECONDS = 10; |
| | | private static final int STATION_IDLE_RECOVER_SECONDS = 10; |
| | | private static final int STATION_IDLE_RECOVER_LIMIT_SECONDS = 30; |
| | | private static final int STATION_IDLE_TRACK_EXPIRE_SECONDS = 60 * 60; |
| | | private static final long STATION_MOVE_RESET_WAIT_MS = 1000L; |
| | | private static final String IDLE_RECOVER_CLEARED_MEMO = "idleRecoverRerouteCleared"; |
| | | |
| | | @Autowired |
| | | private BasDevpService basDevpService; |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private CommonService commonService; |
| | | private StationRegularDispatchProcessor stationRegularDispatchProcessor; |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | private StationDispatchLoadSupport stationDispatchLoadSupport; |
| | | @Autowired |
| | | private LocMastService locMastService; |
| | | private StationOutboundDispatchProcessor stationOutboundDispatchProcessor; |
| | | @Autowired |
| | | private WmsOperateUtils wmsOperateUtils; |
| | | @Autowired |
| | | private NotifyUtils notifyUtils; |
| | | @Autowired |
| | | private NavigateUtils navigateUtils; |
| | | @Autowired |
| | | private BasStationService basStationService; |
| | | @Autowired |
| | | private StationCycleCapacityService stationCycleCapacityService; |
| | | @Autowired |
| | | private StationPathPolicyService stationPathPolicyService; |
| | | @Autowired |
| | | private BasStationOptService basStationOptService; |
| | | @Autowired |
| | | private StationTaskLoopService stationTaskLoopService; |
| | | @Autowired |
| | | private WrkAnalysisService wrkAnalysisService; |
| | | @Autowired |
| | | private StationMoveCoordinator stationMoveCoordinator; |
| | | private StationRerouteProcessor stationRerouteProcessor; |
| | | |
| | | //执行输送站点入库任务 |
| | | public synchronized void stationInExecute() { |
| | | try { |
| | | DispatchLimitConfig baseLimitConfig = getDispatchLimitConfig(null, null); |
| | | int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()}; |
| | | LoadGuardState loadGuardState = buildLoadGuardState(baseLimitConfig); |
| | | |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap(); |
| | | |
| | | List<StationObjModel> list = basDevp.getBarcodeStationList$(); |
| | | for (StationObjModel entity : list) { |
| | | Integer stationId = entity.getStationId(); |
| | | if (!stationMap.containsKey(stationId)) { |
| | | continue; |
| | | } |
| | | |
| | | StationProtocol stationProtocol = stationMap.get(stationId); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | //满足自动、有物、有工作号 |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() > 0 |
| | | ) { |
| | | //检测任务是否生成 |
| | | WrkMast wrkMast = wrkMastService.getOne(new QueryWrapper<WrkMast>().eq("barcode", stationProtocol.getBarcode())); |
| | | if (wrkMast == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.NEW_INBOUND.sts)) { |
| | | continue; |
| | | } |
| | | |
| | | String locNo = wrkMast.getLocNo(); |
| | | FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo); |
| | | if (findCrnNoResult == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,未匹配到堆垛机", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId); |
| | | if (targetStationId == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}站点,搜索入库站点失败", stationId); |
| | | continue; |
| | | } |
| | | |
| | | DispatchLimitConfig limitConfig = getDispatchLimitConfig(stationProtocol.getStationId(), targetStationId); |
| | | LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), targetStationId, loadGuardState); |
| | | |
| | | if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) { |
| | | return; |
| | | } |
| | | |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,获取输送线命令失败", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | wrkMast.setWrkSts(WrkStsType.INBOUND_STATION_RUN.sts); |
| | | wrkMast.setSourceStaNo(stationProtocol.getStationId()); |
| | | wrkMast.setStaNo(targetStationId); |
| | | wrkMast.setSystemMsg(""); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setModiTime(now); |
| | | if (wrkMastService.updateById(wrkMast)) { |
| | | wrkAnalysisService.markInboundStationStart(wrkMast, now); |
| | | boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "stationInExecute"); |
| | | if (offered && stationMoveCoordinator != null) { |
| | | // 初始入库命令也纳入 session 跟踪,后续停留恢复/绕圈/堵塞重算才能基于同一条路线状态判断。 |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "stationInExecute", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | News.info("输送站点入库命令下发成功,站点号={},工作号={},命令数据={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command)); |
| | | redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5); |
| | | loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); |
| | | saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | stationRegularDispatchProcessor.stationInExecute(); |
| | | } |
| | | |
| | | //执行堆垛机输送站点出库任务 |
| | | public synchronized void crnStationOutExecute() { |
| | | try { |
| | | DispatchLimitConfig baseLimitConfig = getDispatchLimitConfig(null, null); |
| | | int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()}; |
| | | LoadGuardState loadGuardState = buildLoadGuardState(baseLimitConfig); |
| | | |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts) |
| | | .isNotNull("crn_no") |
| | | ); |
| | | List<Integer> outOrderList = getAllOutOrderList(); |
| | | |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Object infoObj = redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | if (infoObj == null) { |
| | | News.info("出库任务{}数据缓存不存在", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class); |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId()); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId()); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | //满足自动、有物、工作号0 |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() == 0 |
| | | ) { |
| | | // 先算当前任务在批次出库中的路径倾向系数,再带着这个系数去决策目标站, |
| | | // 这样同一批次不同序号任务在排序点、绕圈点和堵塞重算时会得到一致的目标裁决。 |
| | | Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); |
| | | OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision( |
| | | stationProtocol.getStationId(), |
| | | wrkMast, |
| | | outOrderList, |
| | | pathLenFactor |
| | | ); |
| | | Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId(); |
| | | if (moveStaNo == null) { |
| | | continue; |
| | | } |
| | | |
| | | DispatchLimitConfig limitConfig = getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo); |
| | | LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), moveStaNo, loadGuardState, wrkMast, pathLenFactor); |
| | | |
| | | if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) { |
| | | return; |
| | | } |
| | | |
| | | StationCommand command = buildOutboundMoveCommand( |
| | | stationThread, |
| | | wrkMast, |
| | | stationProtocol.getStationId(), |
| | | moveStaNo, |
| | | pathLenFactor |
| | | ); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); |
| | | continue; |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); |
| | | wrkMast.setSystemMsg(""); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setModiTime(now); |
| | | if (wrkMastService.updateById(wrkMast)) { |
| | | wrkAnalysisService.markOutboundStationStart(wrkMast, now); |
| | | boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute"); |
| | | if (offered && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "crnStationOutExecute", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | News.info("输送站点出库命令下发成功,站点号={},工作号={},命令数据={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command)); |
| | | redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5); |
| | | redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | currentStationTaskCountRef[0]++; |
| | | loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); |
| | | saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | stationOutboundDispatchProcessor.crnStationOutExecute(); |
| | | } |
| | | |
| | | //执行双工位堆垛机输送站点出库任务 |
| | | public synchronized void dualCrnStationOutExecute() { |
| | | try { |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts) |
| | | .isNotNull("dual_crn_no") |
| | | ); |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Object infoObj = redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | if (infoObj == null) { |
| | | News.info("出库任务{}数据缓存不存在", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class); |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId()); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId()); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | //满足自动、有物、工作号0 |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() == 0 |
| | | ) { |
| | | Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); |
| | | StationCommand command = buildOutboundMoveCommand( |
| | | stationThread, |
| | | wrkMast, |
| | | stationProtocol.getStationId(), |
| | | wrkMast.getStaNo(), |
| | | pathLenFactor |
| | | ); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); |
| | | continue; |
| | | } |
| | | |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); |
| | | wrkMast.setSystemMsg(""); |
| | | wrkMast.setIoTime(new Date()); |
| | | if (wrkMastService.updateById(wrkMast)) { |
| | | boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute"); |
| | | if (offered && stationMoveCoordinator != null) { |
| | | // 双工位堆垛机转入输送线后同样要登记 session,否则后续重算只能看到 PLC 命令,看不到路线语义。 |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "dualCrnStationOutExecute", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(), String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN, null); |
| | | News.info("输送站点出库命令下发成功,站点号={},工作号={},命令数据={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command)); |
| | | redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5); |
| | | redisUtil.del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | stationOutboundDispatchProcessor.dualCrnStationOutExecute(); |
| | | } |
| | | |
| | | //检测输送站点出库任务执行完成 |
| | | public synchronized void stationOutExecuteFinish() { |
| | | try { |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN.sts)); |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Integer wrkNo = wrkMast.getWrkNo(); |
| | | Integer targetStaNo = wrkMast.getStaNo(); |
| | | if (wrkNo == null || targetStaNo == null) { |
| | | continue; |
| | | } |
| | | |
| | | boolean complete = false; |
| | | Integer targetDeviceNo = null; |
| | | StationThread stationThread = null; |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo)); |
| | | if (basStation != null) { |
| | | targetDeviceNo = basStation.getDeviceNo(); |
| | | stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread != null) { |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = statusMap.get(basStation.getStationId()); |
| | | if (stationProtocol != null && wrkNo.equals(stationProtocol.getTaskNo())) { |
| | | complete = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (complete) { |
| | | attemptClearTaskPath(stationThread, wrkNo); |
| | | completeStationRunTask(wrkMast, targetDeviceNo); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | private void attemptClearTaskPath(StationThread stationThread, Integer taskNo) { |
| | | if (stationThread == null || taskNo == null || taskNo <= 0) { |
| | | return; |
| | | } |
| | | try { |
| | | boolean cleared = stationThread.clearPath(taskNo); |
| | | if (cleared) { |
| | | News.info("输送站点任务运行完成后清理残留路径,工作号={}", taskNo); |
| | | } |
| | | } catch (Exception e) { |
| | | News.error("输送站点任务运行完成后清理残留路径异常,工作号={}", taskNo, e); |
| | | } |
| | | } |
| | | |
| | | private void completeStationRunTask(WrkMast wrkMast, Integer deviceNo) { |
| | | if (wrkMast == null || wrkMast.getWrkNo() == null) { |
| | | return; |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.finishSession(wrkMast.getWrkNo()); |
| | | } |
| | | Date now = new Date(); |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setModiTime(now); |
| | | wrkMastService.updateById(wrkMast); |
| | | wrkAnalysisService.markOutboundStationComplete(wrkMast, now); |
| | | if (deviceNo != null) { |
| | | notifyUtils.notify(String.valueOf(SlaveType.Devp), deviceNo, String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null); |
| | | } |
| | | redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60); |
| | | stationRegularDispatchProcessor.stationOutExecuteFinish(); |
| | | } |
| | | |
| | | // 检测任务转完成 |
| | | public synchronized void checkTaskToComplete() { |
| | | try { |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts)); |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Integer wrkNo = wrkMast.getWrkNo(); |
| | | Integer targetStaNo = wrkMast.getStaNo(); |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkNo); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | boolean complete = false; |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo)); |
| | | if (basStation == null) { |
| | | continue; |
| | | } |
| | | |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = statusMap.get(basStation.getStationId()); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (!stationProtocol.getTaskNo().equals(wrkNo)) { |
| | | complete = true; |
| | | } |
| | | |
| | | if (complete) { |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.finishSession(wrkNo); |
| | | } |
| | | wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts); |
| | | wrkMast.setIoTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | stationRegularDispatchProcessor.checkTaskToComplete(); |
| | | } |
| | | |
| | | //检测输送站点是否运行堵塞 |
| | | public synchronized void checkStationRunBlock() { |
| | | try { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | List<Integer> runBlockReassignLocStationList = new ArrayList<>(); |
| | | for (StationObjModel stationObjModel : basDevp.getRunBlockReassignLocStationList$()) { |
| | | runBlockReassignLocStationList.add(stationObjModel.getStationId()); |
| | | } |
| | | List<Integer> outOrderStationIds = basDevp.getOutOrderIntList(); |
| | | |
| | | List<StationProtocol> list = stationThread.getStatus(); |
| | | for (StationProtocol stationProtocol : list) { |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() > 0 |
| | | && stationProtocol.isRunBlock() |
| | | ) { |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (wrkMast == null) { |
| | | News.info("输送站点号={} 运行阻塞,但无法找到对应任务,工作号={}", stationProtocol.getStationId(), stationProtocol.getTaskNo()); |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo()); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15); |
| | | |
| | | if (shouldUseRunBlockDirectReassign(wrkMast, stationProtocol.getStationId(), runBlockReassignLocStationList)) { |
| | | executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast); |
| | | continue; |
| | | } |
| | | |
| | | Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); |
| | | // 运行堵塞不单独决定业务目标站,仍然复用出库排序/绕圈的目标裁决, |
| | | // 这里只是要求用 run-block 专用算路,并在重发前清掉旧 session/segment 状态。 |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderStationIds, |
| | | pathLenFactor, |
| | | "checkStationRunBlock_reroute" |
| | | ).withRunBlockCommand() |
| | | .withSuppressDispatchGuard() |
| | | .withCancelSessionBeforeDispatch() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | executeSharedReroute(context); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | stationRerouteProcessor.checkStationRunBlock(); |
| | | } |
| | | |
| | | //检测输送站点任务停留超时后重新计算路径 |
| | | public synchronized void checkStationIdleRecover() { |
| | | try { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | List<StationProtocol> list = stationThread.getStatus(); |
| | | for (StationProtocol stationProtocol : list) { |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() > 0 |
| | | && !stationProtocol.isRunBlock() |
| | | ) { |
| | | checkStationIdleRecover(basDevp, stationThread, stationProtocol, basDevp.getOutOrderIntList()); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | stationRerouteProcessor.checkStationIdleRecover(); |
| | | } |
| | | |
| | | //获取输送线任务数量 |
| | | public synchronized int getCurrentStationTaskCount() { |
| | | return countCurrentStationTask(); |
| | | return stationDispatchLoadSupport.countCurrentStationTask(); |
| | | } |
| | | |
| | | public synchronized int getCurrentOutboundTaskCountByTargetStation(Integer stationId) { |
| | |
| | | |
| | | // 检测出库排序 |
| | | public synchronized void checkStationOutOrder() { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | List<StationObjModel> orderList = basDevp.getOutOrderList$(); |
| | | List<Integer> outOrderStationIds = basDevp.getOutOrderIntList(); |
| | | for (StationObjModel stationObjModel : orderList) { |
| | | StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId()); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (!stationProtocol.isAutoing()) { |
| | | continue; |
| | | } |
| | | |
| | | if (!stationProtocol.isLoading()) { |
| | | continue; |
| | | } |
| | | |
| | | if (stationProtocol.getTaskNo() <= 0) { |
| | | continue; |
| | | } |
| | | |
| | | // 排序点本身已经堵塞时,不在 out-order 里做二次决策,统一交给 run-block 重规划处理。 |
| | | if (stationProtocol.isRunBlock()) { |
| | | continue; |
| | | } |
| | | |
| | | if (!stationProtocol.getStationId().equals(stationProtocol.getTargetStaNo())) { |
| | | continue; |
| | | } |
| | | |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (wrkMast == null) { |
| | | continue; |
| | | } |
| | | if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)) { |
| | | continue; |
| | | } |
| | | if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) { |
| | | continue; |
| | | } |
| | | // 只有活动中的现有路线才会压制 out-order;BLOCKED 路线要允许排序点重新启动。 |
| | | if (shouldSkipOutOrderDispatchForExistingRoute(wrkMast.getWrkNo(), stationProtocol.getStationId())) { |
| | | continue; |
| | | } |
| | | |
| | | Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.OUT_ORDER, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderStationIds, |
| | | pathLenFactor, |
| | | "checkStationOutOrder" |
| | | ).withDispatchDeviceNo(stationObjModel.getDeviceNo()) |
| | | .withSuppressDispatchGuard() |
| | | .withOutOrderDispatchLock() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | executeSharedReroute(context); |
| | | } |
| | | } |
| | | stationRerouteProcessor.checkStationOutOrder(); |
| | | } |
| | | |
| | | // 监控绕圈站点 |
| | | public synchronized void watchCircleStation() { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | List<Integer> outOrderList = basDevp.getOutOrderIntList(); |
| | | |
| | | for (StationProtocol stationProtocol : stationThread.getStatus()) { |
| | | if (!stationProtocol.isAutoing()) { |
| | | continue; |
| | | } |
| | | |
| | | if (!stationProtocol.isLoading()) { |
| | | continue; |
| | | } |
| | | |
| | | if (stationProtocol.getTaskNo() <= 0) { |
| | | continue; |
| | | } |
| | | |
| | | // 绕圈触发优先读 session 的下一决策站,legacy WATCH_CIRCLE key 只做兼容回退。 |
| | | if (!isWatchingCircleArrival(stationProtocol.getTaskNo(), stationProtocol.getStationId())) { |
| | | continue; |
| | | } |
| | | |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (wrkMast == null) { |
| | | continue; |
| | | } |
| | | if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)) { |
| | | continue; |
| | | } |
| | | if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) { |
| | | continue; |
| | | } |
| | | Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.WATCH_CIRCLE, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderList, |
| | | pathLenFactor, |
| | | "watchCircleStation" |
| | | ).withSuppressDispatchGuard() |
| | | .withOutOrderDispatchLock() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | executeSharedReroute(context); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private StationCommand buildOutboundMoveCommand(StationThread stationThread, |
| | | WrkMast wrkMast, |
| | | Integer stationId, |
| | | Integer targetStationId, |
| | | Double pathLenFactor) { |
| | | if (stationThread == null || wrkMast == null) { |
| | | return null; |
| | | } |
| | | return stationThread.getCommand( |
| | | StationCommandType.MOVE, |
| | | wrkMast.getWrkNo(), |
| | | stationId, |
| | | targetStationId, |
| | | 0, |
| | | normalizePathLenFactor(pathLenFactor) |
| | | ); |
| | | stationRerouteProcessor.watchCircleStation(); |
| | | } |
| | | |
| | | RerouteCommandPlan buildRerouteCommandPlan(RerouteContext context, |
| | | RerouteDecision decision) { |
| | | if (context == null) { |
| | | return RerouteCommandPlan.skip("missing-context"); |
| | | } |
| | | if (decision == null) { |
| | | return RerouteCommandPlan.skip("missing-decision"); |
| | | } |
| | | if (decision.skip()) { |
| | | return RerouteCommandPlan.skip(decision.skipReason()); |
| | | } |
| | | if (context.stationThread() == null || context.stationProtocol() == null || context.wrkMast() == null) { |
| | | return RerouteCommandPlan.skip("missing-runtime-dependency"); |
| | | } |
| | | Integer currentStationId = context.stationProtocol().getStationId(); |
| | | Integer targetStationId = decision.targetStationId(); |
| | | if (currentStationId == null || targetStationId == null) { |
| | | return RerouteCommandPlan.skip("missing-target-station"); |
| | | } |
| | | if (Objects.equals(currentStationId, targetStationId)) { |
| | | return RerouteCommandPlan.skip("same-station"); |
| | | } |
| | | |
| | | StationCommand command = context.useRunBlockCommand() |
| | | ? context.stationThread().getRunBlockRerouteCommand( |
| | | context.wrkMast().getWrkNo(), |
| | | currentStationId, |
| | | targetStationId, |
| | | 0, |
| | | context.pathLenFactor() |
| | | ) |
| | | : buildOutboundMoveCommand( |
| | | context.stationThread(), |
| | | context.wrkMast(), |
| | | currentStationId, |
| | | targetStationId, |
| | | context.pathLenFactor() |
| | | ); |
| | | if (command == null) { |
| | | if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) { |
| | | News.taskInfo(context.wrkMast().getWrkNo(), |
| | | "输送站点堵塞重规划未找到可下发路线,当前站点={},目标站点={}", |
| | | currentStationId, |
| | | targetStationId); |
| | | } else if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { |
| | | News.taskInfo(context.wrkMast().getWrkNo(), |
| | | "站点任务停留超时后重算路径失败,当前站点={},目标站点={}", |
| | | currentStationId, |
| | | targetStationId); |
| | | } else { |
| | | News.taskInfo(context.wrkMast().getWrkNo(), "获取输送线命令失败"); |
| | | } |
| | | return RerouteCommandPlan.skip("missing-command"); |
| | | } |
| | | return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene()); |
| | | return stationRerouteProcessor.buildRerouteCommandPlan(context, decision); |
| | | } |
| | | |
| | | RerouteExecutionResult executeReroutePlan(RerouteContext context, |
| | | RerouteCommandPlan plan) { |
| | | if (context == null) { |
| | | return RerouteExecutionResult.skip("missing-context"); |
| | | } |
| | | if (plan == null) { |
| | | return RerouteExecutionResult.skip("missing-plan"); |
| | | } |
| | | if (plan.skip()) { |
| | | return RerouteExecutionResult.skip(plan.skipReason()); |
| | | } |
| | | StationProtocol stationProtocol = context.stationProtocol(); |
| | | if (stationProtocol == null) { |
| | | return RerouteExecutionResult.skip("missing-station-protocol"); |
| | | } |
| | | Integer taskNo = stationProtocol.getTaskNo(); |
| | | Integer stationId = stationProtocol.getStationId(); |
| | | if (taskNo == null || taskNo <= 0 || stationId == null) { |
| | | return RerouteExecutionResult.skip("invalid-station-task"); |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | return stationMoveCoordinator.withTaskDispatchLock(taskNo, |
| | | () -> executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId)); |
| | | } |
| | | return executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId); |
| | | } |
| | | |
| | | private RerouteExecutionResult executeReroutePlanWithTaskLock(RerouteContext context, |
| | | RerouteCommandPlan plan, |
| | | StationProtocol stationProtocol, |
| | | Integer taskNo, |
| | | Integer stationId) { |
| | | boolean runBlockReroute = context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE; |
| | | if (context.checkRecentDispatch() |
| | | && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) { |
| | | return RerouteExecutionResult.skip("recent-dispatch"); |
| | | } |
| | | int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo); |
| | | if (currentTaskBufferCommandCount > 0 && !runBlockReroute) { |
| | | if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { |
| | | News.info("输送站点任务停留超时,但缓存区仍存在当前任务命令,已跳过重算。站点号={},工作号={},当前任务命令数={}", |
| | | stationId, |
| | | taskNo, |
| | | currentTaskBufferCommandCount); |
| | | } |
| | | return RerouteExecutionResult.skip("buffer-has-current-task"); |
| | | } |
| | | if (currentTaskBufferCommandCount > 0 && runBlockReroute) { |
| | | // 堵塞重规划要替换的正是这些旧分段命令,不能再把残留 buffer 当成新的拦截条件。 |
| | | News.info("输送站点运行堵塞重规划检测到旧分段命令残留,已先清理本地状态后继续重发。站点号={},工作号={},当前任务命令数={}", |
| | | stationId, |
| | | taskNo, |
| | | currentTaskBufferCommandCount); |
| | | } |
| | | if (!runBlockReroute |
| | | && context.checkSuppressDispatch() |
| | | && stationMoveCoordinator != null |
| | | && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) { |
| | | return RerouteExecutionResult.skip("dispatch-suppressed"); |
| | | } |
| | | // 进入堵塞重规划后,旧路线已经被显式取消,本轮命令不再参与 active-session suppress 判定。 |
| | | if (context.requireOutOrderDispatchLock() |
| | | && !tryAcquireOutOrderDispatchLock(taskNo, stationId)) { |
| | | return RerouteExecutionResult.skip("out-order-lock"); |
| | | } |
| | | |
| | | if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) { |
| | | // 切路前先把旧 session 置为 CANCEL_PENDING,让已经排队中的旧分段线程在最终发送前停下。 |
| | | stationMoveCoordinator.markCancelPending(taskNo, "reroute_pending"); |
| | | } |
| | | |
| | | if (runBlockReroute) { |
| | | // 站点进入堵塞后,设备侧可能已经把之前预下发的分段命令清掉了。 |
| | | // 先作废本地 session/segment 状态,再按新路线重发,避免被旧状态反向卡住。 |
| | | if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.cancelSession(taskNo); |
| | | } |
| | | if (context.resetSegmentCommandsBeforeDispatch()) { |
| | | resetSegmentMoveCommandsBeforeReroute(taskNo); |
| | | } |
| | | } |
| | | |
| | | if (!runBlockReroute |
| | | && context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.cancelSession(taskNo); |
| | | } |
| | | if (!isBlank(context.executionLockKey())) { |
| | | Object lock = redisUtil.get(context.executionLockKey()); |
| | | if (lock != null) { |
| | | return RerouteExecutionResult.skip("scene-lock"); |
| | | } |
| | | redisUtil.set(context.executionLockKey(), "lock", context.executionLockSeconds()); |
| | | } |
| | | if (!runBlockReroute && context.resetSegmentCommandsBeforeDispatch()) { |
| | | resetSegmentMoveCommandsBeforeReroute(taskNo); |
| | | } |
| | | |
| | | int clearedCommandCount = 0; |
| | | if (context.clearIdleIssuedCommands()) { |
| | | clearedCommandCount = clearIssuedMoveCommandsDuringIdleStay(context.idleTrack(), taskNo, stationId); |
| | | } |
| | | |
| | | boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene()); |
| | | if (!offered) { |
| | | return RerouteExecutionResult.skip("dispatch-dedup"); |
| | | } |
| | | |
| | | applyRerouteDispatchEffects(context, plan, clearedCommandCount); |
| | | return RerouteExecutionResult.dispatched(plan.command(), clearedCommandCount); |
| | | return stationRerouteProcessor.executeReroutePlan(context, plan); |
| | | } |
| | | |
| | | RerouteDecision resolveSharedRerouteDecision(RerouteContext context) { |
| | | if (context == null || context.wrkMast() == null || context.stationProtocol() == null) { |
| | | return RerouteDecision.skip("missing-runtime-dependency"); |
| | | } |
| | | Integer currentStationId = context.stationProtocol().getStationId(); |
| | | if (currentStationId == null) { |
| | | return RerouteDecision.skip("missing-current-station"); |
| | | } |
| | | |
| | | if (context.sceneType() == RerouteSceneType.IDLE_RECOVER |
| | | && !Objects.equals(context.wrkMast().getWrkSts(), WrkStsType.STATION_RUN.sts)) { |
| | | Integer targetStationId = context.wrkMast().getStaNo(); |
| | | return targetStationId == null || Objects.equals(targetStationId, currentStationId) |
| | | ? RerouteDecision.skip("same-station") |
| | | : RerouteDecision.proceed(targetStationId); |
| | | } |
| | | |
| | | OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision( |
| | | currentStationId, |
| | | context.wrkMast(), |
| | | context.outOrderStationIds(), |
| | | context.pathLenFactor() |
| | | ); |
| | | Integer targetStationId = dispatchDecision == null ? null : dispatchDecision.getTargetStationId(); |
| | | if (targetStationId == null || Objects.equals(targetStationId, currentStationId)) { |
| | | return RerouteDecision.skip("same-station"); |
| | | } |
| | | return RerouteDecision.proceed(targetStationId, dispatchDecision); |
| | | } |
| | | |
| | | private RerouteExecutionResult executeSharedReroute(RerouteContext context) { |
| | | RerouteDecision decision = resolveSharedRerouteDecision(context); |
| | | if (decision.skip()) { |
| | | return RerouteExecutionResult.skip(decision.skipReason()); |
| | | } |
| | | RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision); |
| | | return executeReroutePlan(context, plan); |
| | | } |
| | | |
| | | private void applyRerouteDispatchEffects(RerouteContext context, |
| | | RerouteCommandPlan plan, |
| | | int clearedCommandCount) { |
| | | if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) { |
| | | return; |
| | | } |
| | | WrkMast wrkMast = context.wrkMast(); |
| | | StationProtocol stationProtocol = context.stationProtocol(); |
| | | OutOrderDispatchDecision dispatchDecision = plan.decision() == null ? null : plan.decision().dispatchDecision(); |
| | | |
| | | syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), context.outOrderStationIds(), dispatchDecision, plan.command()); |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | plan.dispatchScene(), |
| | | plan.command(), |
| | | dispatchDecision != null && dispatchDecision.isCircle() |
| | | ); |
| | | } |
| | | if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { |
| | | saveStationTaskIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis())); |
| | | News.info("输送站点任务停留{}秒未运行,已重新计算路径并重启运行,站点号={},目标站={},工作号={},清理旧分段命令数={},命令数据={}", |
| | | STATION_IDLE_RECOVER_SECONDS, |
| | | stationProtocol.getStationId(), |
| | | plan.command().getTargetStaNo(), |
| | | wrkMast.getWrkNo(), |
| | | clearedCommandCount, |
| | | JSON.toJSONString(plan.command())); |
| | | return; |
| | | } |
| | | if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) { |
| | | News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}", |
| | | stationProtocol.getStationId(), |
| | | wrkMast.getWrkNo(), |
| | | JSON.toJSONString(plan.command())); |
| | | return; |
| | | } |
| | | if (context.sceneType() == RerouteSceneType.OUT_ORDER) { |
| | | News.info(dispatchDecision != null && dispatchDecision.isCircle() ? "{}任务进行绕圈" : "{}任务直接去目标点", wrkMast.getWrkNo()); |
| | | } |
| | | } |
| | | |
| | | private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast, |
| | | Integer sourceStationId, |
| | | Integer targetStationId, |
| | | Double pathLenFactor) { |
| | | Double normalizedFactor = normalizePathLenFactor(pathLenFactor); |
| | | Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo(); |
| | | if (currentTaskNo == null) { |
| | | return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor); |
| | | } |
| | | return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor); |
| | | } |
| | | |
| | | /** |
| | | * 计算当前出库任务的路径倾向系数。 |
| | | * |
| | | * <p>这个系数不是业务目标站本身,而是“在多条可行路线之间更偏向哪一条”的辅助输入, |
| | | * 目的是让同一批次、不同序号的任务在共享环线里尽量形成稳定、可重复的路径分布。 |
| | | * |
| | | * <p>返回值范围固定在 {@code [0, 1]}: |
| | | * 1. 非批次出库任务,直接返回 {@code 0.0},表示不引入额外路径偏置。 |
| | | * 2. 当前批次只有 1 个有效活动任务,返回 {@code 0.0},因为没有“前后顺序”可比较。 |
| | | * 3. 否则按“当前任务前面还有多少个有效前序任务”占“有效活动任务总数”的比例来算。 |
| | | * |
| | | * <p>这里的“有效任务”只统计: |
| | | * 1. ioType=OUT 的出库任务; |
| | | * 2. 仍处于活动状态,未完成/未结算; |
| | | * 3. 有 batchSeq; |
| | | * 4. mk != taskCancel。 |
| | | * |
| | | * <p>结果含义可以直观理解为: |
| | | * 任务批次序号越靠后,前面已经存在的有效任务越多,得到的系数越大; |
| | | * 后续算路时就更容易和前序任务形成稳定的路径分流,而不是所有任务都走同一条默认短路。 |
| | | * |
| | | * <p>注意: |
| | | * 这个方法不直接决定目标站,不负责排序放行,只提供“路径偏好”输入。 |
| | | * 真正的目标站仍由 {@link #resolveOutboundDispatchDecision(Integer, WrkMast, List, Double)} 决定。 |
| | | */ |
| | | private Double resolveOutboundPathLenFactor(WrkMast wrkMast) { |
| | | if (!isBatchOutboundTaskWithSeq(wrkMast)) { |
| | | return 0.0d; |
| | | } |
| | | List<WrkMast> activeBatchTaskList = loadActiveBatchTaskList(wrkMast.getBatch()); |
| | | if (activeBatchTaskList.size() <= 1) { |
| | | return 0.0d; |
| | | } |
| | | |
| | | int activeTaskCount = 0; |
| | | int predecessorCount = 0; |
| | | for (WrkMast item : activeBatchTaskList) { |
| | | if (!isFactorCandidateTask(item)) { |
| | | continue; |
| | | } |
| | | activeTaskCount++; |
| | | if (item.getBatchSeq() < wrkMast.getBatchSeq()) { |
| | | predecessorCount++; |
| | | } |
| | | } |
| | | if (activeTaskCount <= 1 || predecessorCount <= 0) { |
| | | return 0.0d; |
| | | } |
| | | return normalizePathLenFactor((double) predecessorCount / (double) (activeTaskCount - 1)); |
| | | } |
| | | |
| | | /** |
| | | * 判断当前任务是否具备“按批次出库规则参与排序/路径偏好计算”的基础条件。 |
| | | * |
| | | * <p>这里只做最基础的资格过滤,不关心当前是否真的需要排序点介入。 |
| | | * 只要不是批次出库任务,后面的路径偏好系数与排序目标决策都应该直接退化为默认行为。 |
| | | */ |
| | | private boolean isBatchOutboundTaskWithSeq(WrkMast wrkMast) { |
| | | return wrkMast != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) |
| | | && !Cools.isEmpty(wrkMast.getBatch()) |
| | | && wrkMast.getBatchSeq() != null |
| | | && wrkMast.getWrkNo() != null; |
| | | } |
| | | |
| | | /** |
| | | * 加载同一批次下仍处于活动中的出库任务。 |
| | | * |
| | | * <p>这里用于两类计算: |
| | | * 1. 计算路径偏好系数时,统计当前任务前面还有多少个有效前序任务。 |
| | | * 2. 当前排序点重新决策时,找出这一批“首个未完成任务”的实际批次序号。 |
| | | * |
| | | * <p>已经完成/结算的任务不再参与当前批次的排序与偏好计算。 |
| | | */ |
| | | private List<WrkMast> loadActiveBatchTaskList(String batch) { |
| | | if (Cools.isEmpty(batch)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("io_type", WrkIoType.OUT.id) |
| | | .eq("batch", batch) |
| | | .notIn("wrk_sts", |
| | | WrkStsType.STATION_RUN_COMPLETE.sts, |
| | | WrkStsType.COMPLETE_OUTBOUND.sts, |
| | | WrkStsType.SETTLE_OUTBOUND.sts)); |
| | | } |
| | | |
| | | /** |
| | | * 判断某条批次任务是否应该计入路径偏好系数的分母/分子统计。 |
| | | * |
| | | * <p>这里排除没有 batchSeq 的任务以及被显式标记为 taskCancel 的任务, |
| | | * 避免无效任务把同批次的路径偏好计算拉偏。 |
| | | */ |
| | | private boolean isFactorCandidateTask(WrkMast wrkMast) { |
| | | return wrkMast != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) |
| | | && wrkMast.getBatchSeq() != null |
| | | && !"taskCancel".equals(wrkMast.getMk()); |
| | | } |
| | | |
| | | public List<Integer> getAllOutOrderList() { |
| | | List<Integer> list = new ArrayList<>(); |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | List<Integer> orderList = basDevp.getOutOrderIntList(); |
| | | list.addAll(orderList); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | /** |
| | | * 统一计算当前任务“此刻应该朝哪个目标站继续运行”。 |
| | | * |
| | | * <p>这是出库排序、绕圈、堵塞重规划共用的目标裁决入口。 |
| | | * 不管触发来源是 OUT_ORDER、WATCH_CIRCLE 还是 RUN_BLOCK_REROUTE, |
| | | * 只要业务语义还是“当前这票出库任务下一步该往哪里走”,都从这里得出目标站。 |
| | | * |
| | | * <p>它做三层判断: |
| | | * 1. 如果当前任务根本不适用出库排序,直接返回任务业务目标站 {@code wrkMast.staNo}。 |
| | | * 2. 如果适用出库排序,先算出“当前批次规则下,此刻允许前往的 dispatchStationId”。 |
| | | * 3. 如果当前站点正好就是排序决策点,再进一步判断是: |
| | | * 直接去目标点,还是先进入绕圈目标点,或者因为严格窗口限制而暂不放行。 |
| | | * |
| | | * <p>返回的 {@link OutOrderDispatchDecision} 不只是一个目标站, |
| | | * 还携带了这次决策是否属于绕圈、是否来自当前排序点重新裁决等语义信息, |
| | | * 供后续日志、session 记录和 watch-circle 判定使用。 |
| | | * |
| | | * <p>参数含义: |
| | | * 1. {@code currentStationId}:任务当前所在站点。用于判断当前是不是排序决策点、 |
| | | * 当前是不是已经到达 watch-circle 的下一决策站。 |
| | | * 2. {@code wrkMast}:当前任务主状态,至少要提供业务目标站、来源站、批次、序号等信息。 |
| | | * 3. {@code outOrderStationIds}:当前设备配置的所有出库排序站点列表。 |
| | | * 4. {@code pathLenFactor}:由 {@link #resolveOutboundPathLenFactor(WrkMast)} 得到的路径偏好系数, |
| | | * 用来让同一批次任务在选择 dispatch target 时保持稳定的路径倾向。 |
| | | * |
| | | * <p>返回值语义: |
| | | * 1. 返回 {@code null}:当前无法得到合法目标站,调用方应跳过本次派发。 |
| | | * 2. 返回 {@code targetStationId=wrkMast.staNo, circle=false}: |
| | | * 当前不需要出库排序干预,或已允许直接去业务目标站。 |
| | | * 3. 返回 {@code targetStationId!=wrkMast.staNo}: |
| | | * 当前应该先去一个中间 dispatch 目标站,后续再由排序点/绕圈点继续决策。 |
| | | * 4. 返回 {@code circle=true}: |
| | | * 当前属于绕圈决策结果,后续 watch-circle 逻辑会据此接管。 |
| | | * |
| | | * <p>注意: |
| | | * 这个方法只决定“目标站”,不直接生成输送命令。 |
| | | * 真正的路径由普通算路或 run-block 专用算路在后续步骤生成。 |
| | | */ |
| | | private OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor) { |
| | | if (wrkMast == null || wrkMast.getStaNo() == null) { |
| | | return null; |
| | | } |
| | | if (!shouldApplyOutOrder(wrkMast, outOrderStationIds)) { |
| | | return new OutOrderDispatchDecision(wrkMast.getStaNo(), false); |
| | | } |
| | | Integer dispatchStationId = resolveDispatchOutOrderTarget( |
| | | wrkMast, |
| | | wrkMast.getSourceStaNo(), |
| | | wrkMast.getStaNo(), |
| | | outOrderStationIds, |
| | | pathLenFactor |
| | | ); |
| | | if (dispatchStationId == null) { |
| | | return null; |
| | | } |
| | | if (isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { |
| | | return resolveCurrentOutOrderDispatchDecision(currentStationId, wrkMast, outOrderStationIds, pathLenFactor); |
| | | } |
| | | if (!Objects.equals(dispatchStationId, wrkMast.getStaNo()) |
| | | && isCurrentOutOrderStation(currentStationId, outOrderStationIds) |
| | | && isWatchingCircleArrival(wrkMast.getWrkNo(), currentStationId)) { |
| | | return new OutOrderDispatchDecision(dispatchStationId, true, null, false); |
| | | } |
| | | return new OutOrderDispatchDecision(dispatchStationId, false); |
| | | } |
| | | |
| | | /** |
| | | * 在“当前站点就是本次排序决策点”时,计算这里到底该直接放行还是进入绕圈。 |
| | | * |
| | | * <p>这是出库排序里最核心的局部决策方法。进入这里之前,已经满足: |
| | | * 1. 当前任务适用 out-order; |
| | | * 2. 当前站点就是这票任务此刻对应的 dispatch 排序点。 |
| | | * |
| | | * <p>内部决策顺序: |
| | | * 1. 先取出同批次仍未完成的任务,找出这批当前“应当被优先放行”的序号位置。 |
| | | * 2. 再结合当前任务在初始路径上的排序窗口位置,判断自己此刻能否直接去业务目标站。 |
| | | * 3. 如果理论上该自己放行,还要额外检查目标方向上是否存在可进入的 release slot。 |
| | | * 4. 如果不能直达,或者直达方向当前全部阻塞,就转成 circle 决策,寻找下一排序检测点。 |
| | | * |
| | | * <p>返回值: |
| | | * 1. {@code circle=false} 表示当前排序点已经允许直接去业务目标站。 |
| | | * 2. {@code circle=true} 表示当前只能先去下一绕圈目标站,后续由 watch-circle/排序点继续接力决策。 |
| | | * 3. {@code null} 表示当前既不能直达,也没找到合法的下一绕圈点,调用方应跳过本次派发。 |
| | | */ |
| | | private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor) { |
| | | if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { |
| | | return null; |
| | | } |
| | | |
| | | List<WrkMast> batchWrkList = wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("io_type", WrkIoType.OUT.id) |
| | | .notIn("wrk_sts", |
| | | WrkStsType.STATION_RUN_COMPLETE.sts, |
| | | WrkStsType.COMPLETE_OUTBOUND.sts, |
| | | WrkStsType.SETTLE_OUTBOUND.sts) |
| | | .eq("batch", wrkMast.getBatch()) |
| | | .orderByAsc("batch_seq") |
| | | .orderByAsc("wrk_no")); |
| | | if (batchWrkList.isEmpty()) { |
| | | return new OutOrderDispatchDecision(wrkMast.getStaNo(), false); |
| | | } |
| | | |
| | | WrkMast firstWrkMast = batchWrkList.get(0); |
| | | Integer currentBatchSeq = firstWrkMast.getBatchSeq(); |
| | | if (currentBatchSeq == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "批次:{} 首个未完成任务缺少批次序号,当前任务暂不放行", wrkMast.getBatch()); |
| | | return null; |
| | | } |
| | | |
| | | List<NavigateNode> initPath; |
| | | try { |
| | | initPath = calcOutboundNavigatePath(wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), pathLenFactor); |
| | | } catch (Exception e) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "批次:{} 计算排序路径失败,当前站点={}", wrkMast.getBatch(), currentStationId); |
| | | return null; |
| | | } |
| | | |
| | | Integer seq = getOutStationBatchSeq(initPath, currentStationId, wrkMast.getBatch()); |
| | | boolean toTarget; |
| | | if (seq == null) { |
| | | toTarget = currentBatchSeq.equals(wrkMast.getBatchSeq()); |
| | | } else { |
| | | toTarget = Integer.valueOf(seq + 1).equals(wrkMast.getBatchSeq()); |
| | | } |
| | | if (toTarget) { |
| | | if (hasReachableOutReleaseSlot(wrkMast, currentStationId, wrkMast.getStaNo(), pathLenFactor)) { |
| | | return new OutOrderDispatchDecision(wrkMast.getStaNo(), false); |
| | | } |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( |
| | | wrkMast.getWrkNo(), |
| | | currentStationId, |
| | | outOrderStationIds |
| | | ); |
| | | Integer circleTarget = resolveNextCircleOrderTarget( |
| | | wrkMast, |
| | | currentStationId, |
| | | outOrderStationIds, |
| | | loopEvaluation.getExpectedLoopIssueCount(), |
| | | pathLenFactor |
| | | ); |
| | | if (circleTarget == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "目标站当前不可进,且未找到可执行的下一排序检测点,当前站点={}", currentStationId); |
| | | return null; |
| | | } |
| | | return new OutOrderDispatchDecision(circleTarget, true, loopEvaluation, true); |
| | | } |
| | | |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( |
| | | wrkMast.getWrkNo(), |
| | | currentStationId, |
| | | outOrderStationIds |
| | | ); |
| | | Integer circleTarget = resolveNextCircleOrderTarget( |
| | | wrkMast, |
| | | currentStationId, |
| | | outOrderStationIds, |
| | | loopEvaluation.getExpectedLoopIssueCount(), |
| | | pathLenFactor |
| | | ); |
| | | if (circleTarget == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "未找到可执行的下一排序检测点,当前站点={}", currentStationId); |
| | | return null; |
| | | } |
| | | return new OutOrderDispatchDecision(circleTarget, true, loopEvaluation, true); |
| | | } |
| | | |
| | | /** |
| | | * 判断这票任务在当前设备上是否应该启用出库排序逻辑。 |
| | | * |
| | | * <p>只要缺少任何一个排序前提,例如不是出库任务、没有批次、没有序号、 |
| | | * 当前设备也没有配置排序点,就应该直接退回“普通目标站决策”。 |
| | | */ |
| | | private boolean shouldApplyOutOrder(WrkMast wrkMast, List<Integer> outOrderStationIds) { |
| | | return wrkMast != null |
| | | && wrkMast.getStaNo() != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) |
| | | && !Cools.isEmpty(wrkMast.getBatch()) |
| | | && wrkMast.getBatchSeq() != null |
| | | && outOrderStationIds != null |
| | | && !outOrderStationIds.isEmpty(); |
| | | } |
| | | |
| | | /** |
| | | * 判断当前所在站点是否就是“这票任务此刻应该触发排序决策的 dispatch 站点”。 |
| | | * |
| | | * <p>注意它不是简单判断“当前站点是否属于排序点列表”, |
| | | * 而是先根据完整路径反推出当前任务对应的 dispatch 排序点, |
| | | * 再判断当前位置是否正好等于这个 dispatch 点。 |
| | | */ |
| | | private boolean isCurrentOutOrderDispatchStation(Integer currentStationId, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor) { |
| | | if (!shouldApplyOutOrder(wrkMast, outOrderStationIds) || currentStationId == null) { |
| | | return false; |
| | | } |
| | | Integer dispatchStationId = resolveDispatchOutOrderTarget( |
| | | wrkMast, |
| | | wrkMast.getSourceStaNo(), |
| | | wrkMast.getStaNo(), |
| | | outOrderStationIds, |
| | | pathLenFactor |
| | | ); |
| | | return dispatchStationId != null |
| | | && !Objects.equals(dispatchStationId, wrkMast.getStaNo()) |
| | | && Objects.equals(currentStationId, dispatchStationId); |
| | | } |
| | | |
| | | /** |
| | | * 判断当前位置是否属于设备配置里的任意一个排序点。 |
| | | * |
| | | * <p>这个判断比 {@link #isCurrentOutOrderDispatchStation(Integer, WrkMast, List, Double)} 更宽, |
| | | * 只回答“当前位置是不是排序点”,不回答“是不是这票任务当前应该命中的排序点”。 |
| | | */ |
| | | private boolean isCurrentOutOrderStation(Integer currentStationId, |
| | | List<Integer> outOrderStationIds) { |
| | | return currentStationId != null |
| | | && outOrderStationIds != null |
| | | && outOrderStationIds.contains(currentStationId); |
| | | } |
| | | |
| | | private void syncOutOrderWatchState(WrkMast wrkMast, |
| | | Integer currentStationId, |
| | | List<Integer> outOrderStationIds, |
| | | OutOrderDispatchDecision dispatchDecision, |
| | | StationCommand command) { |
| | | if (dispatchDecision == null || command == null || !shouldApplyOutOrder(wrkMast, outOrderStationIds)) { |
| | | return; |
| | | } |
| | | if (dispatchDecision.isCircle()) { |
| | | saveWatchCircleCommand(wrkMast.getWrkNo(), command); |
| | | if (dispatchDecision.shouldCountLoopIssue() |
| | | && stationTaskLoopService != null |
| | | && dispatchDecision.getLoopEvaluation() != null) { |
| | | stationTaskLoopService.recordLoopIssue(dispatchDecision.getLoopEvaluation(), "OUT_ORDER_CIRCLE"); |
| | | } |
| | | } else { |
| | | clearWatchCircleCommand(wrkMast.getWrkNo()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 为当前排序点决策预先做一次环线评估。 |
| | | * |
| | | * <p>当本次决策最终进入绕圈时,评估结果会被带进 {@link OutOrderDispatchDecision}, |
| | | * 后续用于记录 loop issue 统计,而不是在真正下发后再重复评估一次。 |
| | | */ |
| | | private StationTaskLoopService.LoopEvaluation evaluateOutOrderLoop(Integer taskNo, |
| | | Integer currentStationId, |
| | | List<Integer> outOrderStationIds) { |
| | | if (stationTaskLoopService == null) { |
| | | return new StationTaskLoopService.LoopEvaluation( |
| | | taskNo, |
| | | currentStationId, |
| | | StationTaskLoopService.LoopIdentitySnapshot.empty(), |
| | | 0, |
| | | 0, |
| | | false |
| | | ); |
| | | } |
| | | return stationTaskLoopService.evaluateLoop( |
| | | taskNo, |
| | | currentStationId, |
| | | true, |
| | | outOrderStationIds, |
| | | "outOrderCircle" |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 从“源站到业务目标站”的完整路径里,反推出当前任务应当先命中的 dispatch 排序点。 |
| | | * |
| | | * <p>做法是: |
| | | * 1. 先计算从 sourceStationId 到 finalTargetStationId 的完整导航路径; |
| | | * 2. 再从路径尾部向前扫描; |
| | | * 3. 找到离最终目标最近的那个排序点,作为当前 dispatch 目标。 |
| | | * |
| | | * <p>如果路径上根本没有排序点,或者缺少源站/排序点配置, |
| | | * 就直接把业务目标站本身当成 dispatch 目标。 |
| | | */ |
| | | private Integer resolveDispatchOutOrderTarget(WrkMast wrkMast, |
| | | Integer sourceStationId, |
| | | Integer finalTargetStationId, |
| | | List<Integer> outOrderList, |
| | | Double pathLenFactor) { |
| | | if (finalTargetStationId == null) { |
| | | return null; |
| | | } |
| | | if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) { |
| | | return finalTargetStationId; |
| | | } |
| | | |
| | | try { |
| | | List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, sourceStationId, finalTargetStationId, pathLenFactor); |
| | | for (int i = nodes.size() - 1; i >= 0; i--) { |
| | | Integer stationId = getStationIdFromNode(nodes.get(i)); |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | if (Objects.equals(stationId, finalTargetStationId)) { |
| | | continue; |
| | | } |
| | | if (outOrderList.contains(stationId)) { |
| | | return stationId; |
| | | } |
| | | } |
| | | } catch (Exception ignore) {} |
| | | return finalTargetStationId; |
| | | } |
| | | |
| | | /** |
| | | * 判断从当前排序点继续前往最终业务目标站时,路径上是否至少存在一个可进入的后续站点。 |
| | | * |
| | | * <p>这里不是要求整条路径完全畅通,而是判断“当前是否有释放口可走”。 |
| | | * 只要后续路径上存在一个未阻塞站点,就认为当前仍可尝试直达; |
| | | * 只有整段后续路径看起来都被阻塞时,才会强制转入绕圈。 |
| | | */ |
| | | private boolean hasReachableOutReleaseSlot(WrkMast wrkMast, |
| | | Integer currentStationId, |
| | | Integer finalTargetStationId, |
| | | Double pathLenFactor) { |
| | | if (currentStationId == null || finalTargetStationId == null) { |
| | | return true; |
| | | } |
| | | |
| | | try { |
| | | List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, currentStationId, finalTargetStationId, pathLenFactor); |
| | | if (nodes == null || nodes.isEmpty()) { |
| | | return true; |
| | | } |
| | | |
| | | for (NavigateNode node : nodes) { |
| | | Integer stationId = getStationIdFromNode(node); |
| | | if (stationId == null || Objects.equals(stationId, currentStationId)) { |
| | | continue; |
| | | } |
| | | |
| | | if (!isPathStationBlocked(stationId)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } catch (Exception ignore) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | private boolean isPathStationBlocked(Integer stationId) { |
| | | if (stationId == null) { |
| | | return true; |
| | | } |
| | | |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", stationId)); |
| | | if (basStation == null) { |
| | | return true; |
| | | } |
| | | |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | return true; |
| | | } |
| | | |
| | | StationProtocol stationProtocol = stationThread.getStatusMap().get(stationId); |
| | | if (stationProtocol == null) { |
| | | return true; |
| | | } |
| | | |
| | | return !stationProtocol.isAutoing() |
| | | || stationProtocol.isLoading() |
| | | || (stationProtocol.getTaskNo() != null && stationProtocol.getTaskNo() > 0); |
| | | } |
| | | |
| | | /** |
| | | * 为“排序点需要绕圈”的场景,从出库排序站点列表里挑出下一跳绕圈目标。 |
| | | * <p> |
| | | * 这里的输入不是整张地图,而是已经按业务顺序排好的出库站点序列。方法会从当前站点在该序列中的后继开始, |
| | | * 依次尝试每个候选站点,并计算“当前站点 -> 候选站点”的可行路径: |
| | | * <p> |
| | | * 1. 只要能算出路径,就把候选站点记录为一个可选绕圈目标; |
| | | * 2. 记录两类排序信息: |
| | | * - pathLength:到该候选点的路径长度,越短说明越适合先拿来做临时缓冲; |
| | | * - offset:该站点在排序序列中距离当前站点有多远,用来在路径长度相同的时候保留既有顺序感; |
| | | * 3. 最后把候选列表交给 {@link #resolveGradualCircleTargetByPathLength(Integer, List, Double)}, |
| | | * 再根据“当前已经绕圈多少次”与“路径长度偏好系数”从不同长度层级里挑最终目标。 |
| | | * <p> |
| | | * 这个方法本身不判断“是否应该绕圈”,只负责在已经决定绕圈后,从排序站点链路里找一个下一跳缓冲点。 |
| | | * |
| | | * @param wrkMast 当前任务,主要用于算路 |
| | | * @param currentStationId 当前站点,即本次准备从哪里发出绕圈命令 |
| | | * @param orderedOutStationList 已按业务顺序排好的出库站点列表 |
| | | * @param expectedLoopIssueCount 预计已发生的绕圈/堵塞轮次,用于决定是否逐步放大绕圈半径 |
| | | * @param pathLenFactor 当前任务对应的路径偏好系数,影响 calcOutboundNavigatePath 的选路结果 |
| | | * @return 下一跳绕圈目标站;如果没有任何可到达候选则返回 null |
| | | */ |
| | | private Integer resolveNextCircleOrderTarget(WrkMast wrkMast, |
| | | Integer currentStationId, |
| | | List<Integer> orderedOutStationList, |
| | | Integer expectedLoopIssueCount, |
| | | Double pathLenFactor) { |
| | | if (currentStationId == null || orderedOutStationList == null || orderedOutStationList.size() <= 1) { |
| | | return null; |
| | | } |
| | | |
| | | int startIndex = orderedOutStationList.indexOf(currentStationId); |
| | | int total = orderedOutStationList.size(); |
| | | List<CircleTargetCandidate> candidateList = new ArrayList<>(); |
| | | for (int offset = 1; offset < total; offset++) { |
| | | int candidateIndex = (startIndex + offset + total) % total; |
| | | Integer candidateStationId = orderedOutStationList.get(candidateIndex); |
| | | if (candidateStationId == null || currentStationId.equals(candidateStationId)) { |
| | | continue; |
| | | } |
| | | try { |
| | | List<NavigateNode> path = calcOutboundNavigatePath(wrkMast, currentStationId, candidateStationId, pathLenFactor); |
| | | if (path != null && !path.isEmpty()) { |
| | | candidateList.add(new CircleTargetCandidate(candidateStationId, path.size(), offset)); |
| | | } |
| | | } catch (Exception ignore) {} |
| | | } |
| | | if (candidateList.isEmpty()) { |
| | | return null; |
| | | } |
| | | candidateList.sort(new Comparator<CircleTargetCandidate>() { |
| | | @Override |
| | | public int compare(CircleTargetCandidate left, CircleTargetCandidate right) { |
| | | if (left == right) { |
| | | return 0; |
| | | } |
| | | if (left == null) { |
| | | return 1; |
| | | } |
| | | if (right == null) { |
| | | return -1; |
| | | } |
| | | int pathCompare = Integer.compare(left.getPathLength(), right.getPathLength()); |
| | | if (pathCompare != 0) { |
| | | return pathCompare; |
| | | } |
| | | return Integer.compare(left.getOffset(), right.getOffset()); |
| | | } |
| | | }); |
| | | return resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor); |
| | | } |
| | | |
| | | /** |
| | | * 在“已按路径长度升序排好”的绕圈候选列表中,按层级渐进地挑选目标站。 |
| | | * <p> |
| | | * candidateList 里可能存在多个候选点拥有相同的 pathLength。对绕圈决策来说, |
| | | * 同一长度层里的候选点都属于“同一绕圈半径”,真正需要控制的是: |
| | | * <p> |
| | | * 1. 初次/前几次堵塞时,优先选择最短可达层,尽量用最小绕行距离恢复流转; |
| | | * 2. 如果任务已经连续多次在同一区域绕圈,说明短半径候选大概率已经试过或恢复效果差, |
| | | * 就需要逐步放大到更远一层; |
| | | * 3. pathLenFactor 代表当前任务对“短路径/长路径”的偏好,允许在相同堵塞轮次下适度往更远层偏移。 |
| | | * <p> |
| | | * 因此这里先把 candidateList 压缩成“按 pathLength 去重后的 tierList”,每个 tier 只保留该长度层的首个候选。 |
| | | * 然后同时计算两个层级索引: |
| | | * <p> |
| | | * - defaultTierIndex:基于 expectedLoopIssueCount 的默认放大层级; |
| | | * - factorTierIndex:基于 pathLenFactor 的偏好层级; |
| | | * <p> |
| | | * 最终取两者较大值,含义是“至少满足当前堵塞轮次需要的放大半径,同时允许路径偏好把目标推向更远层级”。 |
| | | * |
| | | * @param expectedLoopIssueCount 预计已发生的绕圈/堵塞轮次 |
| | | * @param candidateList 已按 pathLength、offset 排序的候选列表 |
| | | * @param pathLenFactor 当前任务的路径偏好系数 |
| | | * @return 最终选中的绕圈目标站;若没有候选则返回 null |
| | | */ |
| | | private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount, |
| | | List<CircleTargetCandidate> candidateList, |
| | | Double pathLenFactor) { |
| | | if (candidateList == null || candidateList.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | List<CircleTargetCandidate> tierList = new ArrayList<>(); |
| | | Integer lastPathLength = null; |
| | | for (CircleTargetCandidate candidate : candidateList) { |
| | | if (candidate == null) { |
| | | continue; |
| | | } |
| | | if (lastPathLength == null || !Objects.equals(lastPathLength, candidate.getPathLength())) { |
| | | tierList.add(candidate); |
| | | lastPathLength = candidate.getPathLength(); |
| | | } |
| | | } |
| | | if (tierList.isEmpty()) { |
| | | return candidateList.get(0).getStationId(); |
| | | } |
| | | int defaultTierIndex = expectedLoopIssueCount == null || expectedLoopIssueCount <= 2 |
| | | ? 0 |
| | | : Math.min(expectedLoopIssueCount - 2, tierList.size() - 1); |
| | | int factorTierIndex = (int) Math.round(normalizePathLenFactor(pathLenFactor) * (tierList.size() - 1)); |
| | | int tierIndex = Math.max(defaultTierIndex, factorTierIndex); |
| | | return tierList.get(tierIndex).getStationId(); |
| | | } |
| | | |
| | | private boolean tryAcquireOutOrderDispatchLock(Integer wrkNo, Integer stationId) { |
| | | if (wrkNo == null || wrkNo <= 0 || stationId == null) { |
| | | return true; |
| | | } |
| | | String key = RedisKeyType.STATION_OUT_ORDER_DISPATCH_LIMIT_.key + wrkNo + "_" + stationId; |
| | | Object lock = redisUtil.get(key); |
| | | if (lock != null) { |
| | | return false; |
| | | } |
| | | redisUtil.set(key, "lock", OUT_ORDER_DISPATCH_LIMIT_SECONDS); |
| | | return true; |
| | | } |
| | | |
| | | private boolean shouldSkipOutOrderDispatchForExistingRoute(Integer wrkNo, Integer stationId) { |
| | | if (stationMoveCoordinator == null || wrkNo == null || wrkNo <= 0 || stationId == null) { |
| | | return false; |
| | | } |
| | | StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); |
| | | if (session == null) { |
| | | return false; |
| | | } |
| | | if (!session.isActive() || !session.containsStation(stationId)) { |
| | | return false; |
| | | } |
| | | // 绕圈路线在当前站点尚未走完时,排序点不应再次插手。 |
| | | if (StationMoveDispatchMode.CIRCLE == session.getDispatchMode()) { |
| | | return true; |
| | | } |
| | | // 直接路线只在“当前站点已经被别的活动路线占住且目标不同”时才拦截。 |
| | | return !Objects.equals(stationId, session.getCurrentRouteTargetStationId()); |
| | | } |
| | | |
| | | private boolean isWatchingCircleArrival(Integer wrkNo, Integer stationId) { |
| | | if (stationMoveCoordinator != null) { |
| | | StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); |
| | | if (session != null && session.isActive() && stationId != null) { |
| | | // nextDecisionStationId 表示这条路线真正等待重新决策的站点,到站才触发 watch-circle。 |
| | | if (stationId.equals(session.getNextDecisionStationId())) { |
| | | return true; |
| | | } |
| | | // 还在 session 路径中间站运行时不应误触发。 |
| | | if (session.containsStation(stationId)) { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | StationCommand command = getWatchCircleCommand(wrkNo); |
| | | return command != null && stationId != null && stationId.equals(command.getTargetStaNo()); |
| | | } |
| | | |
| | | private boolean isWatchingCircleTransit(Integer wrkNo, Integer stationId) { |
| | | if (stationMoveCoordinator != null) { |
| | | StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); |
| | | if (session != null && session.isActive() && stationId != null) { |
| | | if (stationId.equals(session.getNextDecisionStationId())) { |
| | | return false; |
| | | } |
| | | if (session.containsStation(stationId)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | StationCommand command = getWatchCircleCommand(wrkNo); |
| | | if (command == null || stationId == null || Objects.equals(stationId, command.getTargetStaNo())) { |
| | | return false; |
| | | } |
| | | List<Integer> navigatePath = command.getNavigatePath(); |
| | | return navigatePath != null && navigatePath.contains(stationId); |
| | | } |
| | | |
| | | private StationCommand getWatchCircleCommand(Integer wrkNo) { |
| | | if (wrkNo == null || wrkNo <= 0) { |
| | | return null; |
| | | } |
| | | Object circleObj = redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo); |
| | | if (circleObj == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return JSON.parseObject(circleObj.toString(), StationCommand.class); |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private void saveWatchCircleCommand(Integer wrkNo, StationCommand command) { |
| | | if (wrkNo == null || wrkNo <= 0 || command == null) { |
| | | return; |
| | | } |
| | | redisUtil.set(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo, |
| | | JSON.toJSONString(command, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24); |
| | | } |
| | | |
| | | private void clearWatchCircleCommand(Integer wrkNo) { |
| | | if (wrkNo == null || wrkNo <= 0) { |
| | | return; |
| | | } |
| | | redisUtil.del(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo); |
| | | } |
| | | |
| | | private void checkStationIdleRecover(BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | List<Integer> outOrderList) { |
| | | if (stationProtocol == null || stationProtocol.getTaskNo() == null || stationProtocol.getTaskNo() <= 0) { |
| | | return; |
| | | } |
| | | if (!Objects.equals(stationProtocol.getStationId(), stationProtocol.getTargetStaNo())) { |
| | | return; |
| | | } |
| | | |
| | | StationTaskIdleTrack idleTrack = touchStationTaskIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId()); |
| | | if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) { |
| | | return; |
| | | } |
| | | if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) { |
| | | return; |
| | | } |
| | | |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (!canRecoverIdleStationTask(wrkMast, stationProtocol.getStationId())) { |
| | | return; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo()); |
| | | if (lock != null) { |
| | | return; |
| | | } |
| | | Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.IDLE_RECOVER, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderList, |
| | | pathLenFactor, |
| | | "checkStationIdleRecover" |
| | | ).withCancelSessionBeforeDispatch() |
| | | .withExecutionLock(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), STATION_IDLE_RECOVER_LIMIT_SECONDS) |
| | | .withResetSegmentCommandsBeforeDispatch() |
| | | .clearIdleIssuedCommands(idleTrack); |
| | | executeSharedReroute(context); |
| | | return stationRerouteProcessor.resolveSharedRerouteDecision(context); |
| | | } |
| | | |
| | | boolean shouldUseRunBlockDirectReassign(WrkMast wrkMast, |
| | | Integer stationId, |
| | | List<Integer> runBlockReassignLocStationList) { |
| | | return wrkMast != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id) |
| | | && stationId != null |
| | | && runBlockReassignLocStationList != null |
| | | && runBlockReassignLocStationList.contains(stationId); |
| | | } |
| | | |
| | | private void executeRunBlockDirectReassign(BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | WrkMast wrkMast) { |
| | | if (basDevp == null || stationThread == null || stationProtocol == null || wrkMast == null) { |
| | | return; |
| | | } |
| | | int currentTaskBufferCommandCount = countCurrentTaskBufferCommands( |
| | | stationProtocol.getTaskBufferItems(), |
| | | stationProtocol.getTaskNo() |
| | | ); |
| | | if (currentTaskBufferCommandCount > 0) { |
| | | News.info("输送站点运行堵塞重分配已跳过,缓存区仍存在当前任务命令。站点号={},工作号={},当前任务命令数={}", |
| | | stationProtocol.getStationId(), |
| | | stationProtocol.getTaskNo(), |
| | | currentTaskBufferCommandCount); |
| | | return; |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.cancelSession(wrkMast.getWrkNo()); |
| | | } |
| | | String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId()); |
| | | if (Cools.isEmpty(response)) { |
| | | News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,接口未响应!!!response:{}", response); |
| | | return; |
| | | } |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (!jsonObject.getInteger("code").equals(200)) { |
| | | News.error("请求WMS接口失败!!!response:{}", response); |
| | | return; |
| | | } |
| | | |
| | | StartupDto dto = jsonObject.getObject("data", StartupDto.class); |
| | | String sourceLocNo = wrkMast.getLocNo(); |
| | | String locNo = dto.getLocNo(); |
| | | |
| | | LocMast sourceLocMast = locMastService.queryByLoc(sourceLocNo); |
| | | if (sourceLocMast == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 源库位信息不存在", sourceLocNo); |
| | | return; |
| | | } |
| | | if (!sourceLocMast.getLocSts().equals("S")) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 源库位状态不处于入库预约", sourceLocNo); |
| | | return; |
| | | } |
| | | |
| | | LocMast locMast = locMastService.queryByLoc(locNo); |
| | | if (locMast == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 目标库位信息不存在", locNo); |
| | | return; |
| | | } |
| | | if (!locMast.getLocSts().equals("O")) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 目标库位状态不处于空库位", locNo); |
| | | return; |
| | | } |
| | | |
| | | FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo); |
| | | if (findCrnNoResult == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,未匹配到堆垛机", wrkMast.getWrkNo()); |
| | | return; |
| | | } |
| | | Integer crnNo = findCrnNoResult.getCrnNo(); |
| | | |
| | | Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId()); |
| | | if (targetStationId == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}站点,搜索入库站点失败", stationProtocol.getStationId()); |
| | | return; |
| | | } |
| | | |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), targetStationId, 0); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,获取输送线命令失败", wrkMast.getWrkNo()); |
| | | return; |
| | | } |
| | | |
| | | sourceLocMast.setLocSts("O"); |
| | | sourceLocMast.setModiTime(new Date()); |
| | | locMastService.updateById(sourceLocMast); |
| | | |
| | | locMast.setLocSts("S"); |
| | | locMast.setModiTime(new Date()); |
| | | locMastService.updateById(locMast); |
| | | |
| | | wrkMast.setLocNo(locNo); |
| | | wrkMast.setStaNo(targetStationId); |
| | | |
| | | if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) { |
| | | wrkMast.setCrnNo(crnNo); |
| | | } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) { |
| | | wrkMast.setDualCrnNo(crnNo); |
| | | } else { |
| | | throw new CoolException("未知设备类型"); |
| | | } |
| | | |
| | | if (!wrkMastService.updateById(wrkMast)) { |
| | | return; |
| | | } |
| | | boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct"); |
| | | if (!offered) { |
| | | return; |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "checkStationRunBlock_direct", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | } |
| | | |
| | | private boolean canRecoverIdleStationTask(WrkMast wrkMast, Integer currentStationId) { |
| | | if (wrkMast == null || currentStationId == null || wrkMast.getStaNo() == null) { |
| | | return false; |
| | | } |
| | | if (Objects.equals(currentStationId, wrkMast.getStaNo())) { |
| | | return false; |
| | | } |
| | | return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts) |
| | | || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts); |
| | | return stationRerouteProcessor.shouldUseRunBlockDirectReassign(wrkMast, stationId, runBlockReassignLocStationList); |
| | | } |
| | | |
| | | private boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) { |
| | | if (taskNo == null || taskNo <= 0 || stationId == null) { |
| | | return false; |
| | | } |
| | | long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L; |
| | | StationMoveSession session = stationMoveCoordinator == null ? null : stationMoveCoordinator.loadSession(taskNo); |
| | | if (session != null && session.isActive() && session.getLastIssuedAt() != null) { |
| | | // 分段执行过程中,刚下发下一段命令时,session 的 currentStationId/dispatchStationId |
| | | // 可能还没来得及和当前观察站点完全对齐;只要当前站点仍在这条活动路线里, |
| | | // 就说明这次 recent dispatch 仍然和它相关,idle recover 不应在 10 秒窗口内再次介入。 |
| | | if (Objects.equals(stationId, session.getCurrentStationId()) |
| | | || Objects.equals(stationId, session.getDispatchStationId()) |
| | | || session.containsStation(stationId)) { |
| | | long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt(); |
| | | if (elapsedMs < thresholdMs) { |
| | | saveStationTaskIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis())); |
| | | News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距上次下发={}ms,routeVersion={}", |
| | | stationId, taskNo, elapsedMs, session.getRouteVersion()); |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | if (!hasRecentIssuedMoveCommand(taskNo, stationId, thresholdMs)) { |
| | | return false; |
| | | } |
| | | saveStationTaskIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis())); |
| | | News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距最近命令下发<{}ms,routeVersion={}", |
| | | stationId, taskNo, thresholdMs, session == null ? null : session.getRouteVersion()); |
| | | return true; |
| | | } |
| | | |
| | | private boolean hasRecentIssuedMoveCommand(Integer taskNo, Integer stationId, long thresholdMs) { |
| | | if (taskNo == null || taskNo <= 0 || stationId == null || thresholdMs <= 0L || basStationOptService == null) { |
| | | return false; |
| | | } |
| | | Date thresholdTime = new Date(System.currentTimeMillis() - thresholdMs); |
| | | List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>() |
| | | .select("id") |
| | | .eq("task_no", taskNo) |
| | | .eq("station_id", stationId) |
| | | .eq("mode", String.valueOf(StationCommandType.MOVE)) |
| | | .eq("send", 1) |
| | | .ge("send_time", thresholdTime) |
| | | .orderByDesc("send_time") |
| | | .last("limit 1")); |
| | | return optList != null && !optList.isEmpty(); |
| | | } |
| | | |
| | | private void resetSegmentMoveCommandsBeforeReroute(Integer taskNo) { |
| | | if (redisUtil == null || taskNo == null || taskNo <= 0) { |
| | | return; |
| | | } |
| | | String key = RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo; |
| | | redisUtil.set(key, "cancel", 3); |
| | | try { |
| | | Thread.sleep(STATION_MOVE_RESET_WAIT_MS); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | } catch (Exception ignore) { |
| | | } |
| | | redisUtil.del(key); |
| | | } |
| | | |
| | | private int countCurrentTaskBufferCommands(List<StationTaskBufferItem> taskBufferItems, Integer currentTaskNo) { |
| | | if (taskBufferItems == null || taskBufferItems.isEmpty() || currentTaskNo == null || currentTaskNo <= 0) { |
| | | return 0; |
| | | } |
| | | int count = 0; |
| | | for (StationTaskBufferItem item : taskBufferItems) { |
| | | if (item == null || item.getTaskNo() == null) { |
| | | continue; |
| | | } |
| | | if (currentTaskNo.equals(item.getTaskNo())) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) { |
| | | if (deviceNo == null || command == null) { |
| | | return false; |
| | | } |
| | | String dedupKey = buildStationCommandDispatchDedupKey(deviceNo, command); |
| | | if (redisUtil != null) { |
| | | Object lock = redisUtil.get(dedupKey); |
| | | if (lock != null) { |
| | | News.info("输送站点命令短时重复派发,已跳过。scene={},deviceNo={},taskNo={},stationId={},targetStaNo={},commandType={}", |
| | | scene, |
| | | deviceNo, |
| | | command.getTaskNo(), |
| | | command.getStationId(), |
| | | command.getTargetStaNo(), |
| | | command.getCommandType()); |
| | | return false; |
| | | } |
| | | redisUtil.set(dedupKey, "lock", STATION_COMMAND_DISPATCH_DEDUP_SECONDS); |
| | | } |
| | | boolean offered = MessageQueue.offer(SlaveType.Devp, deviceNo, new Task(2, command)); |
| | | if (!offered && redisUtil != null) { |
| | | redisUtil.del(dedupKey); |
| | | } |
| | | return offered; |
| | | } |
| | | |
| | | private String buildStationCommandDispatchDedupKey(Integer deviceNo, StationCommand command) { |
| | | return RedisKeyType.STATION_COMMAND_DISPATCH_DEDUP_.key |
| | | + deviceNo + "_" |
| | | + command.getTaskNo() + "_" |
| | | + command.getStationId() + "_" |
| | | + (stationMoveCoordinator == null ? Integer.toHexString(buildFallbackPathSignature(command).hashCode()) |
| | | : stationMoveCoordinator.buildPathSignatureHash(command)); |
| | | } |
| | | |
| | | private String buildFallbackPathSignature(StationCommand command) { |
| | | if (command == null) { |
| | | return ""; |
| | | } |
| | | return String.valueOf(command.getCommandType()) |
| | | + "_" + command.getStationId() |
| | | + "_" + command.getTargetStaNo() |
| | | + "_" + command.getNavigatePath() |
| | | + "_" + command.getLiftTransferPath() |
| | | + "_" + command.getOriginalNavigatePath(); |
| | | } |
| | | |
| | | private int clearIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack, |
| | | Integer taskNo, |
| | | Integer stationId) { |
| | | if (basStationOptService == null) { |
| | | return 0; |
| | | } |
| | | List<BasStationOpt> optList; |
| | | try { |
| | | optList = listIssuedMoveCommandsDuringIdleStay(idleTrack, taskNo); |
| | | } catch (Exception e) { |
| | | return 0; |
| | | } |
| | | if (optList == null || optList.isEmpty()) { |
| | | return 0; |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | String cleanupMemo = buildIdleRecoverClearedMemo(stationId); |
| | | int clearedCount = 0; |
| | | for (BasStationOpt opt : optList) { |
| | | if (opt == null || opt.getId() == null) { |
| | | continue; |
| | | } |
| | | opt.setSend(0); |
| | | opt.setUpdateTime(now); |
| | | opt.setMemo(appendCleanupMemo(opt.getMemo(), cleanupMemo)); |
| | | clearedCount++; |
| | | } |
| | | if (clearedCount > 0) { |
| | | basStationOptService.updateBatchById(optList); |
| | | } |
| | | return clearedCount; |
| | | } |
| | | |
| | | private List<BasStationOpt> listIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack, |
| | | Integer taskNo) { |
| | | if (idleTrack == null || taskNo == null || taskNo <= 0 || idleTrack.firstSeenTime == null || basStationOptService == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>() |
| | | .select("id", "task_no", "send_time", "target_station_id", "memo", "send") |
| | | .eq("task_no", taskNo) |
| | | .eq("mode", String.valueOf(StationCommandType.MOVE)) |
| | | .eq("send", 1) |
| | | .ge("send_time", new Date(idleTrack.firstSeenTime)) |
| | | .orderByAsc("send_time")); |
| | | if (optList == null || optList.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return optList; |
| | | } |
| | | |
| | | private String buildIdleRecoverClearedMemo(Integer stationId) { |
| | | if (stationId == null) { |
| | | return IDLE_RECOVER_CLEARED_MEMO; |
| | | } |
| | | return IDLE_RECOVER_CLEARED_MEMO + "(stationId=" + stationId + ")"; |
| | | } |
| | | |
| | | private String appendCleanupMemo(String memo, String cleanupMemo) { |
| | | if (Cools.isEmpty(cleanupMemo)) { |
| | | return memo; |
| | | } |
| | | if (Cools.isEmpty(memo)) { |
| | | return cleanupMemo; |
| | | } |
| | | if (memo.contains(cleanupMemo)) { |
| | | return memo; |
| | | } |
| | | return memo + " | " + cleanupMemo; |
| | | } |
| | | |
| | | private StationTaskIdleTrack touchStationTaskIdleTrack(Integer taskNo, Integer stationId) { |
| | | if (taskNo == null || taskNo <= 0 || stationId == null) { |
| | | return null; |
| | | } |
| | | long now = System.currentTimeMillis(); |
| | | StationTaskIdleTrack idleTrack = getStationTaskIdleTrack(taskNo); |
| | | if (idleTrack == null || !Objects.equals(idleTrack.stationId, stationId)) { |
| | | idleTrack = new StationTaskIdleTrack(taskNo, stationId, now); |
| | | saveStationTaskIdleTrack(idleTrack); |
| | | } |
| | | return idleTrack; |
| | | } |
| | | |
| | | private StationTaskIdleTrack getStationTaskIdleTrack(Integer taskNo) { |
| | | if (taskNo == null || taskNo <= 0) { |
| | | return null; |
| | | } |
| | | Object obj = redisUtil.get(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + taskNo); |
| | | if (obj == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return JSON.parseObject(obj.toString(), StationTaskIdleTrack.class); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private void saveStationTaskIdleTrack(StationTaskIdleTrack idleTrack) { |
| | | if (idleTrack == null || idleTrack.taskNo == null || idleTrack.taskNo <= 0) { |
| | | return; |
| | | } |
| | | redisUtil.set( |
| | | RedisKeyType.STATION_TASK_IDLE_TRACK_.key + idleTrack.taskNo, |
| | | JSON.toJSONString(idleTrack, SerializerFeature.DisableCircularReferenceDetect), |
| | | STATION_IDLE_TRACK_EXPIRE_SECONDS |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 沿“源站 -> 目标站”的理论路径,从当前站点往下游回看,找出同批次任务在后续站点上的已知序号。 |
| | | * <p> |
| | | * 严格窗口控制要回答的问题不是“当前任务自己的 batchSeq 是多少”,而是: |
| | | * “在当前站点后面,沿这条出库链路已经排着的同批次任务,最靠近目标端的序号是多少?” |
| | | * 只有拿到这个序号,排序点才能判断当前任务是否应该紧跟其后放行。 |
| | | * <p> |
| | | * 具体做法: |
| | | * <p> |
| | | * 1. 按 pathList 从尾到头回扫,截出 searchStationId 之后的全部下游站点; |
| | | * 2. 读取这些站点当前正在执行的任务号; |
| | | * 3. 如果某站点上的任务属于 searchBatch,就记录它的 batchSeq; |
| | | * 4. 返回该批次在下游已知的序号。 |
| | | * <p> |
| | | * 这里返回的是“路径下游现场已出现的批次序号”,不是批次理论最小/最大值。 |
| | | * 如果下游没有同批次任务,返回 null,调用方需要退回到“当前批次首个未完成任务是否就是自己”的判定。 |
| | | * |
| | | * @param pathList 从任务源站到目标站的理论出库路径 |
| | | * @param searchStationId 当前正在做排序决策的站点 |
| | | * @param searchBatch 当前任务所属批次 |
| | | * @return 下游同批次任务的已知序号;若路径下游尚未出现该批次则返回 null |
| | | */ |
| | | public Integer getOutStationBatchSeq(List<NavigateNode> pathList, Integer searchStationId, String searchBatch) { |
| | | if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) { |
| | | return null; |
| | | } |
| | | // 只关心当前站点之后的下游站点,当前站点之前的节点不会影响“谁应该排在我前面”。 |
| | | List<Integer> checkList = new ArrayList<>(); |
| | | for (int i = pathList.size() - 1; i >= 0; i--) { |
| | | NavigateNode node = pathList.get(i); |
| | | JSONObject v = JSONObject.parseObject(node.getNodeValue()); |
| | | if (v != null) { |
| | | Integer stationId = v.getInteger("stationId"); |
| | | if (searchStationId.equals(stationId)) { |
| | | break; |
| | | } else { |
| | | checkList.add(stationId); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 下游站点可能同时挂着多个不同批次任务;这里只抽取与当前批次相关的现场序号快照。 |
| | | HashMap<String, Integer> batchMap = new HashMap<>(); |
| | | for (Integer station : checkList) { |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", station)); |
| | | if (basStation == null) { |
| | | continue; |
| | | } |
| | | |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | StationProtocol checkStationProtocol = statusMap.get(station); |
| | | if (checkStationProtocol == null) { |
| | | continue; |
| | | } |
| | | if (checkStationProtocol.getTaskNo() > 0) { |
| | | WrkMast checkWrkMast = wrkMastService.selectByWorkNo(checkStationProtocol.getTaskNo()); |
| | | if (checkWrkMast == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (!Cools.isEmpty(checkWrkMast.getBatch())) { |
| | | batchMap.put(checkWrkMast.getBatch(), checkWrkMast.getBatchSeq()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | Integer seq = batchMap.get(searchBatch); |
| | | return seq; |
| | | } |
| | | |
| | | private int countCurrentStationTask() { |
| | | int currentStationTaskCount = 0; |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | for (StationProtocol stationProtocol : stationThread.getStatus()) { |
| | | if (stationProtocol.getTaskNo() > 0) { |
| | | currentStationTaskCount++; |
| | | } |
| | | } |
| | | } |
| | | return currentStationTaskCount; |
| | | } |
| | | |
| | | private boolean isDispatchBlocked(DispatchLimitConfig config, |
| | | int currentStationTaskCount, |
| | | LoadGuardState loadGuardState, |
| | | boolean needReserveLoopLoad) { |
| | | if (config.loopModeEnable) { |
| | | double currentLoad = loadGuardState.currentLoad(); |
| | | if (currentLoad >= config.circleMaxLoadLimit) { |
| | | News.warn("当前承载量达到上限,已停止站点任务下发。当前承载量={},上限={}", formatPercent(currentLoad), formatPercent(config.circleMaxLoadLimit)); |
| | | return true; |
| | | } |
| | | |
| | | if (needReserveLoopLoad) { |
| | | double reserveLoad = loadGuardState.loadAfterReserve(); |
| | | if (reserveLoad >= config.circleMaxLoadLimit) { |
| | | News.warn("预占后承载量达到上限,已停止站点任务下发。预占后承载量={},上限={}", formatPercent(reserveLoad), formatPercent(config.circleMaxLoadLimit)); |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private LoadGuardState buildLoadGuardState(DispatchLimitConfig config) { |
| | | LoadGuardState state = new LoadGuardState(); |
| | | if (!config.loopModeEnable) { |
| | | return state; |
| | | } |
| | | |
| | | StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); |
| | | if (capacityVo == null) { |
| | | return state; |
| | | } |
| | | |
| | | state.totalStationCount = toNonNegative(capacityVo.getTotalStationCount()); |
| | | Integer occupiedStationCount = capacityVo.getOccupiedStationCount(); |
| | | state.projectedTaskStationCount = toNonNegative(occupiedStationCount != null ? occupiedStationCount : capacityVo.getTaskStationCount()); |
| | | |
| | | List<StationCycleLoopVo> loopList = capacityVo.getLoopList(); |
| | | if (loopList != null) { |
| | | for (StationCycleLoopVo loopVo : loopList) { |
| | | if (loopVo == null || loopVo.getStationIdList() == null) { |
| | | continue; |
| | | } |
| | | Integer loopNo = loopVo.getLoopNo(); |
| | | for (Integer stationId : loopVo.getStationIdList()) { |
| | | if (stationId != null) { |
| | | if (loopNo != null) { |
| | | state.stationLoopNoMap.put(stationId, loopNo); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return state; |
| | | } |
| | | |
| | | private LoopHitResult findPathLoopHit(DispatchLimitConfig config, |
| | | Integer sourceStationId, |
| | | Integer targetStationId, |
| | | LoadGuardState loadGuardState) { |
| | | return findPathLoopHit(config, sourceStationId, targetStationId, loadGuardState, null, null); |
| | | } |
| | | |
| | | private LoopHitResult findPathLoopHit(DispatchLimitConfig config, |
| | | Integer sourceStationId, |
| | | Integer targetStationId, |
| | | LoadGuardState loadGuardState, |
| | | WrkMast wrkMast, |
| | | Double pathLenFactor) { |
| | | if (!config.loopModeEnable) { |
| | | return LoopHitResult.NO_HIT; |
| | | } |
| | | if (sourceStationId == null || targetStationId == null) { |
| | | return LoopHitResult.NO_HIT; |
| | | } |
| | | if (loadGuardState.stationLoopNoMap.isEmpty()) { |
| | | return LoopHitResult.NO_HIT; |
| | | } |
| | | |
| | | try { |
| | | List<NavigateNode> nodes = wrkMast == null |
| | | ? navigateUtils.calcByStationId(sourceStationId, targetStationId) |
| | | : calcOutboundNavigatePath(wrkMast, sourceStationId, targetStationId, pathLenFactor); |
| | | if (nodes == null || nodes.isEmpty()) { |
| | | return LoopHitResult.NO_HIT; |
| | | } |
| | | |
| | | for (NavigateNode node : nodes) { |
| | | Integer stationId = getStationIdFromNode(node); |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | Integer loopNo = loadGuardState.stationLoopNoMap.get(stationId); |
| | | if (loopNo != null) { |
| | | return new LoopHitResult(true, loopNo, stationId); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | return LoopHitResult.NO_HIT; |
| | | } |
| | | |
| | | return LoopHitResult.NO_HIT; |
| | | } |
| | | |
| | | private Integer getStationIdFromNode(NavigateNode node) { |
| | | if (node == null || isBlank(node.getNodeValue())) { |
| | | return null; |
| | | } |
| | | try { |
| | | JSONObject v = JSONObject.parseObject(node.getNodeValue()); |
| | | if (v == null) { |
| | | return null; |
| | | } |
| | | return v.getInteger("stationId"); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private int toNonNegative(Integer value) { |
| | | if (value == null || value < 0) { |
| | | return 0; |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private Double normalizePathLenFactor(Double pathLenFactor) { |
| | | if (pathLenFactor == null || pathLenFactor < 0.0d) { |
| | | return 0.0d; |
| | | } |
| | | if (pathLenFactor > 1.0d) { |
| | | return 1.0d; |
| | | } |
| | | return pathLenFactor; |
| | | } |
| | | |
| | | enum RerouteSceneType { |
| | | RUN_BLOCK_REROUTE, |
| | | IDLE_RECOVER, |
| | | OUT_ORDER, |
| | | WATCH_CIRCLE |
| | | } |
| | | |
| | | static final class RerouteDecision { |
| | | private final boolean skip; |
| | | private final String skipReason; |
| | | private final Integer targetStationId; |
| | | private final OutOrderDispatchDecision dispatchDecision; |
| | | |
| | | private RerouteDecision(boolean skip, |
| | | String skipReason, |
| | | Integer targetStationId, |
| | | OutOrderDispatchDecision dispatchDecision) { |
| | | this.skip = skip; |
| | | this.skipReason = skipReason; |
| | | this.targetStationId = targetStationId; |
| | | this.dispatchDecision = dispatchDecision; |
| | | } |
| | | |
| | | static RerouteDecision skip(String reason) { |
| | | return new RerouteDecision(true, reason, null, null); |
| | | } |
| | | |
| | | static RerouteDecision proceed(Integer targetStationId) { |
| | | return new RerouteDecision(false, null, targetStationId, null); |
| | | } |
| | | |
| | | static RerouteDecision proceed(Integer targetStationId, |
| | | OutOrderDispatchDecision dispatchDecision) { |
| | | return new RerouteDecision(false, null, targetStationId, dispatchDecision); |
| | | } |
| | | |
| | | boolean skip() { |
| | | return skip; |
| | | } |
| | | |
| | | String skipReason() { |
| | | return skipReason; |
| | | } |
| | | |
| | | Integer targetStationId() { |
| | | return targetStationId; |
| | | } |
| | | |
| | | OutOrderDispatchDecision dispatchDecision() { |
| | | return dispatchDecision; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 重算/重发链路的一次性执行上下文。 |
| | | * |
| | | * <p>这个对象只在一次 reroute 执行过程中存在,不会落库,也不会长期缓存。 |
| | | * 它的职责不是表达业务状态,而是把“这次为什么进来、当前用哪套运行时对象、 |
| | | * 下发前后要不要启用额外控制逻辑”集中打包,供统一执行链路使用。 |
| | | * |
| | | * <p>统一执行链路大致分三段: |
| | | * 1. {@code resolveSharedRerouteDecision} 根据当前任务和场景先决策目标站。 |
| | | * 2. {@code buildRerouteCommandPlan} 决定用普通出库算路还是 run-block 专用算路。 |
| | | * 3. {@code executeReroutePlan} 按上下文里的开关决定是否做 suppress、是否加锁、 |
| | | * 是否先 cancel 旧 session、是否先清旧分段命令,然后真正下发。 |
| | | * |
| | | * <p>字段可以分成四组理解: |
| | | * 1. 场景与运行时对象: |
| | | * {@code sceneType} / {@code basDevp} / {@code stationThread} / |
| | | * {@code stationProtocol} / {@code wrkMast} |
| | | * 表示“谁在什么场景下触发了这次重算”。 |
| | | * 2. 目标决策输入: |
| | | * {@code outOrderStationIds} / {@code pathLenFactor} |
| | | * 表示“目标站如何算、路径倾向系数是多少”。 |
| | | * 3. 下发目标信息: |
| | | * {@code dispatchScene} / {@code dispatchDeviceNo} |
| | | * 表示“这次命令最终往哪个输送设备队列发,以及日志/去重场景名是什么”。 |
| | | * 4. 执行控制开关: |
| | | * {@code useRunBlockCommand} / {@code checkSuppressDispatch} / |
| | | * {@code requireOutOrderDispatchLock} / {@code cancelSessionBeforeDispatch} / |
| | | * {@code resetSegmentCommandsBeforeDispatch} / {@code clearIdleIssuedCommands} / |
| | | * {@code checkRecentDispatch} / {@code executionLockKey} / {@code executionLockSeconds} |
| | | * 表示“真正执行前后要打开哪些保护动作”。 |
| | | * |
| | | * <p>它本质上是一个参数对象加布尔开关集合: |
| | | * {@code create(...)} 先把这次 reroute 的基础现场填进来, |
| | | * 再通过 {@code withXxx(...)} 逐项声明这次执行需要附加哪些控制语义。 |
| | | * |
| | | * <p>例如: |
| | | * RUN_BLOCK_REROUTE 会打开 {@code useRunBlockCommand}, |
| | | * 并要求在下发前先 {@code cancelSession}、先清旧分段命令; |
| | | * OUT_ORDER 会打开 suppress guard 和 out-order lock; |
| | | * IDLE_RECOVER 则会打开 recent dispatch guard,并记录/清理停留期间已下发命令。 |
| | | */ |
| | | static final class RerouteContext { |
| | | // 本次 reroute 的触发来源。决定后面走哪类目标裁决、日志文案和特殊保护分支。 |
| | | private final RerouteSceneType sceneType; |
| | | // 当前输送设备配置,主要用于拿默认下发设备号和相关站点配置。 |
| | | private final BasDevp basDevp; |
| | | // 当前站点线程,后面构造输送命令时直接依赖它取 command。 |
| | | private final StationThread stationThread; |
| | | // 触发这次 reroute 的现场站点状态快照,包含 stationId/taskNo/runBlock/targetStaNo 等运行时信息。 |
| | | private final StationProtocol stationProtocol; |
| | | // 当前工作主表记录,表示这次重算对应的任务主状态。 |
| | | private final WrkMast wrkMast; |
| | | // 当前设备的出库排序点列表。目标站决策时需要知道哪些站点属于 out-order 节点。 |
| | | private final List<Integer> outOrderStationIds; |
| | | // 路径长度倾向系数。批次出库时用于让不同任务对路径选择有稳定偏好。 |
| | | private final Double pathLenFactor; |
| | | // 这次派发在日志、去重、session 记录中使用的场景名,例如 checkStationRunBlock_reroute。 |
| | | private final String dispatchScene; |
| | | // 实际投递命令的设备号。默认取 basDevp.getDevpNo(),某些场景可显式覆盖。 |
| | | private Integer dispatchDeviceNo; |
| | | // true 表示命令构造阶段改走 stationThread.getRunBlockRerouteCommand,而不是普通出库算路。 |
| | | private boolean useRunBlockCommand; |
| | | // true 表示执行前要先做 active-session suppress,避免旧活动路线被重复插入新命令。 |
| | | private boolean checkSuppressDispatch; |
| | | // true 表示执行前要加 out-order 专用短锁,防止同一排序点短时间重复计算/下发。 |
| | | private boolean requireOutOrderDispatchLock; |
| | | // true 表示真正下发前先取消旧 session。通常用于 reroute 替换旧路线。 |
| | | private boolean cancelSessionBeforeDispatch; |
| | | // true 表示真正下发前先清理旧分段输送命令,避免 segment executor 还持有旧路线。 |
| | | private boolean resetSegmentCommandsBeforeDispatch; |
| | | // true 表示要清理 idle recover 期间已经下发过但未实际生效的旧命令痕迹。 |
| | | private boolean clearIdleIssuedCommands; |
| | | // true 表示执行前要检查“最近刚下发过”,用于 idle recover 避免刚发完就重算。 |
| | | private boolean checkRecentDispatch; |
| | | // 可选执行锁 key。用于给某个 reroute 场景加短时间互斥。 |
| | | private String executionLockKey; |
| | | // executionLockKey 对应的锁秒数。 |
| | | private int executionLockSeconds; |
| | | // 仅 idle recover 需要,记录停留跟踪上下文,供清理旧命令与更新时间使用。 |
| | | private StationTaskIdleTrack idleTrack; |
| | | |
| | | private RerouteContext(RerouteSceneType sceneType, |
| | | BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor, |
| | | String dispatchScene) { |
| | | this.sceneType = sceneType; |
| | | this.basDevp = basDevp; |
| | | this.stationThread = stationThread; |
| | | this.stationProtocol = stationProtocol; |
| | | this.wrkMast = wrkMast; |
| | | this.outOrderStationIds = outOrderStationIds == null ? Collections.emptyList() : outOrderStationIds; |
| | | this.pathLenFactor = pathLenFactor; |
| | | this.dispatchScene = dispatchScene; |
| | | this.dispatchDeviceNo = basDevp == null ? null : basDevp.getDevpNo(); |
| | | } |
| | | |
| | | static RerouteContext create(RerouteSceneType sceneType, |
| | | BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor, |
| | | String dispatchScene) { |
| | | // create 只负责填基础现场,不默认打开任何执行开关。 |
| | | // 每个场景后面通过 withXxx 明确声明自己需要哪些附加控制。 |
| | | return new RerouteContext(sceneType, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, dispatchScene); |
| | | } |
| | | |
| | | RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) { |
| | | // 覆盖默认下发设备号。典型场景是 out-order 站点配置的 deviceNo 与 basDevp 默认值不同。 |
| | | this.dispatchDeviceNo = dispatchDeviceNo; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext withRunBlockCommand() { |
| | | // 命令构造阶段切换到 run-block 专用算路器。 |
| | | // 目标站仍由统一决策逻辑决定,只是“去目标站的路径”改为堵塞重规划算法生成。 |
| | | this.useRunBlockCommand = true; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext withSuppressDispatchGuard() { |
| | | // 执行前启用 session suppress: |
| | | // 如果当前 task 在当前位置已经有一条活动中的同路径/同覆盖范围路线,则本次不再重复派发。 |
| | | this.checkSuppressDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext withOutOrderDispatchLock() { |
| | | // 执行前启用排序点短锁。 |
| | | // 主要防止同一个 out-order/watch-circle 触发点在极短时间内被并发重复重算。 |
| | | this.requireOutOrderDispatchLock = true; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext withCancelSessionBeforeDispatch() { |
| | | // 执行前显式取消旧 session。 |
| | | // 语义是“本次命令准备替换旧路线”,旧 routeVersion 之后不应再继续推进。 |
| | | this.cancelSessionBeforeDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext withResetSegmentCommandsBeforeDispatch() { |
| | | // 执行前清掉 segment executor 侧旧分段命令。 |
| | | // 这对 run-block/idle recover 很关键,否则系统可能还拿着旧 segment 状态阻断新路线。 |
| | | this.resetSegmentCommandsBeforeDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) { |
| | | // 仅 idle recover 使用: |
| | | // 表示重启前要把“停留期间已经发过但可能未真正执行的命令痕迹”清理掉。 |
| | | this.clearIdleIssuedCommands = true; |
| | | this.idleTrack = idleTrack; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext withRecentDispatchGuard() { |
| | | // 执行前检查最近是否刚下发过。 |
| | | // 避免 idle recover 在“刚重发完”的窗口内又马上触发一次。 |
| | | this.checkRecentDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) { |
| | | // 为某个场景挂一个独立执行锁。 |
| | | // 和 out-order lock 不同,这里是泛化锁,谁传 key 谁负责定义锁语义。 |
| | | this.executionLockKey = executionLockKey; |
| | | this.executionLockSeconds = executionLockSeconds; |
| | | return this; |
| | | } |
| | | |
| | | RerouteSceneType sceneType() { |
| | | return sceneType; |
| | | } |
| | | |
| | | BasDevp basDevp() { |
| | | return basDevp; |
| | | } |
| | | |
| | | StationThread stationThread() { |
| | | return stationThread; |
| | | } |
| | | |
| | | StationProtocol stationProtocol() { |
| | | return stationProtocol; |
| | | } |
| | | |
| | | WrkMast wrkMast() { |
| | | return wrkMast; |
| | | } |
| | | |
| | | List<Integer> outOrderStationIds() { |
| | | return outOrderStationIds; |
| | | } |
| | | |
| | | Double pathLenFactor() { |
| | | return pathLenFactor; |
| | | } |
| | | |
| | | String dispatchScene() { |
| | | return dispatchScene; |
| | | } |
| | | |
| | | Integer dispatchDeviceNo() { |
| | | return dispatchDeviceNo; |
| | | } |
| | | |
| | | boolean useRunBlockCommand() { |
| | | return useRunBlockCommand; |
| | | } |
| | | |
| | | boolean checkSuppressDispatch() { |
| | | return checkSuppressDispatch; |
| | | } |
| | | |
| | | boolean requireOutOrderDispatchLock() { |
| | | return requireOutOrderDispatchLock; |
| | | } |
| | | |
| | | boolean cancelSessionBeforeDispatch() { |
| | | return cancelSessionBeforeDispatch; |
| | | } |
| | | |
| | | boolean resetSegmentCommandsBeforeDispatch() { |
| | | return resetSegmentCommandsBeforeDispatch; |
| | | } |
| | | |
| | | boolean clearIdleIssuedCommands() { |
| | | return clearIdleIssuedCommands; |
| | | } |
| | | |
| | | boolean checkRecentDispatch() { |
| | | return checkRecentDispatch; |
| | | } |
| | | |
| | | String executionLockKey() { |
| | | return executionLockKey; |
| | | } |
| | | |
| | | int executionLockSeconds() { |
| | | return executionLockSeconds; |
| | | } |
| | | |
| | | StationTaskIdleTrack idleTrack() { |
| | | return idleTrack; |
| | | } |
| | | } |
| | | |
| | | static final class RerouteCommandPlan { |
| | | private final boolean skip; |
| | | private final String skipReason; |
| | | private final StationCommand command; |
| | | private final RerouteDecision decision; |
| | | private final String dispatchScene; |
| | | |
| | | private RerouteCommandPlan(boolean skip, |
| | | String skipReason, |
| | | StationCommand command, |
| | | RerouteDecision decision, |
| | | String dispatchScene) { |
| | | this.skip = skip; |
| | | this.skipReason = skipReason; |
| | | this.command = command; |
| | | this.decision = decision; |
| | | this.dispatchScene = dispatchScene; |
| | | } |
| | | |
| | | static RerouteCommandPlan skip(String reason) { |
| | | return new RerouteCommandPlan(true, reason, null, null, null); |
| | | } |
| | | |
| | | static RerouteCommandPlan dispatch(StationCommand command, |
| | | RerouteDecision decision, |
| | | String dispatchScene) { |
| | | return new RerouteCommandPlan(false, null, command, decision, dispatchScene); |
| | | } |
| | | |
| | | boolean skip() { |
| | | return skip; |
| | | } |
| | | |
| | | String skipReason() { |
| | | return skipReason; |
| | | } |
| | | |
| | | StationCommand command() { |
| | | return command; |
| | | } |
| | | |
| | | RerouteDecision decision() { |
| | | return decision; |
| | | } |
| | | |
| | | String dispatchScene() { |
| | | return dispatchScene; |
| | | } |
| | | } |
| | | |
| | | static final class RerouteExecutionResult { |
| | | private final boolean skipped; |
| | | private final String skipReason; |
| | | private final boolean dispatched; |
| | | private final StationCommand command; |
| | | private final int clearedCommandCount; |
| | | |
| | | private RerouteExecutionResult(boolean skipped, |
| | | String skipReason, |
| | | boolean dispatched, |
| | | StationCommand command, |
| | | int clearedCommandCount) { |
| | | this.skipped = skipped; |
| | | this.skipReason = skipReason; |
| | | this.dispatched = dispatched; |
| | | this.command = command; |
| | | this.clearedCommandCount = clearedCommandCount; |
| | | } |
| | | |
| | | static RerouteExecutionResult skip(String reason) { |
| | | return new RerouteExecutionResult(true, reason, false, null, 0); |
| | | } |
| | | |
| | | static RerouteExecutionResult dispatched(StationCommand command, |
| | | int clearedCommandCount) { |
| | | return new RerouteExecutionResult(false, null, true, command, clearedCommandCount); |
| | | } |
| | | |
| | | boolean skipped() { |
| | | return skipped; |
| | | } |
| | | |
| | | String skipReason() { |
| | | return skipReason; |
| | | } |
| | | |
| | | boolean dispatched() { |
| | | return dispatched; |
| | | } |
| | | |
| | | StationCommand command() { |
| | | return command; |
| | | } |
| | | |
| | | int clearedCommandCount() { |
| | | return clearedCommandCount; |
| | | } |
| | | } |
| | | |
| | | private static class OutOrderDispatchDecision { |
| | | private final Integer targetStationId; |
| | | private final boolean circle; |
| | | private final StationTaskLoopService.LoopEvaluation loopEvaluation; |
| | | private final boolean countLoopIssue; |
| | | |
| | | private OutOrderDispatchDecision(Integer targetStationId, boolean circle) { |
| | | this(targetStationId, circle, null, false); |
| | | } |
| | | |
| | | private OutOrderDispatchDecision(Integer targetStationId, |
| | | boolean circle, |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation, |
| | | boolean countLoopIssue) { |
| | | this.targetStationId = targetStationId; |
| | | this.circle = circle; |
| | | this.loopEvaluation = loopEvaluation; |
| | | this.countLoopIssue = countLoopIssue; |
| | | } |
| | | |
| | | private Integer getTargetStationId() { |
| | | return targetStationId; |
| | | } |
| | | |
| | | private boolean isCircle() { |
| | | return circle; |
| | | } |
| | | |
| | | private StationTaskLoopService.LoopEvaluation getLoopEvaluation() { |
| | | return loopEvaluation; |
| | | } |
| | | |
| | | private boolean shouldCountLoopIssue() { |
| | | return countLoopIssue; |
| | | } |
| | | } |
| | | |
| | | private static class CircleTargetCandidate { |
| | | private final Integer stationId; |
| | | private final Integer pathLength; |
| | | private final Integer offset; |
| | | |
| | | private CircleTargetCandidate(Integer stationId, Integer pathLength, Integer offset) { |
| | | this.stationId = stationId; |
| | | this.pathLength = pathLength == null ? 0 : pathLength; |
| | | this.offset = offset == null ? 0 : offset; |
| | | } |
| | | |
| | | private Integer getStationId() { |
| | | return stationId; |
| | | } |
| | | |
| | | private Integer getPathLength() { |
| | | return pathLength; |
| | | } |
| | | |
| | | private Integer getOffset() { |
| | | return offset; |
| | | } |
| | | } |
| | | |
| | | private void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) { |
| | | if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) { |
| | | return; |
| | | } |
| | | JSONObject reserveJson = new JSONObject(); |
| | | reserveJson.put("wrkNo", wrkNo); |
| | | reserveJson.put("loopNo", loopHitResult.getLoopNo()); |
| | | reserveJson.put("hitStationId", loopHitResult.getHitStationId()); |
| | | reserveJson.put("createTime", System.currentTimeMillis()); |
| | | redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString()); |
| | | redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS); |
| | | } |
| | | |
| | | private DispatchLimitConfig getDispatchLimitConfig(Integer startStationId, Integer endStationId) { |
| | | DispatchLimitConfig config = new DispatchLimitConfig(); |
| | | Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); |
| | | if (systemConfigMapObj instanceof Map) { |
| | | Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj; |
| | | config.circleMaxLoadLimit = parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.circleMaxLoadLimit); |
| | | String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable"); |
| | | if (isBlank(loopModeValue)) { |
| | | loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable"); |
| | | } |
| | | if (isBlank(loopModeValue)) { |
| | | loopModeValue = getConfigValue(systemConfigMap, "isCircleMode"); |
| | | } |
| | | config.loopModeEnable = parseBoolean(loopModeValue, config.loopModeEnable); |
| | | } |
| | | |
| | | if (stationPathPolicyService != null && startStationId != null && endStationId != null) { |
| | | try { |
| | | StationPathResolvedPolicy resolvedPolicy = stationPathPolicyService.resolvePolicy(startStationId, endStationId); |
| | | if (resolvedPolicy != null && resolvedPolicy.getProfileConfig() != null) { |
| | | config.circleMaxLoadLimit = parseLoadLimit(String.valueOf(resolvedPolicy.getProfileConfig().getCircleMaxLoadLimit()), config.circleMaxLoadLimit); |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | |
| | | return config; |
| | | } |
| | | |
| | | private String getConfigValue(Map<?, ?> configMap, String key) { |
| | | Object value = configMap.get(key); |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return String.valueOf(value).trim(); |
| | | } |
| | | |
| | | private boolean parseBoolean(String value, boolean defaultValue) { |
| | | if (isBlank(value)) { |
| | | return defaultValue; |
| | | } |
| | | String lowValue = value.toLowerCase(Locale.ROOT); |
| | | if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue) |
| | | || "1".equals(lowValue) || "on".equals(lowValue)) { |
| | | return true; |
| | | } |
| | | if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue) |
| | | || "0".equals(lowValue) || "off".equals(lowValue)) { |
| | | return false; |
| | | } |
| | | return defaultValue; |
| | | } |
| | | |
| | | private double parseLoadLimit(String value, double defaultValue) { |
| | | if (isBlank(value)) { |
| | | return defaultValue; |
| | | } |
| | | try { |
| | | String normalized = value.replace("%", "").trim(); |
| | | double parsed = Double.parseDouble(normalized); |
| | | if (parsed > 1.0) { |
| | | parsed = parsed / 100.0; |
| | | } |
| | | if (parsed < 0.0) { |
| | | return 0.0; |
| | | } |
| | | if (parsed > 1.0) { |
| | | return 1.0; |
| | | } |
| | | return parsed; |
| | | } catch (Exception e) { |
| | | return defaultValue; |
| | | } |
| | | } |
| | | |
| | | private int parseInt(String value, int defaultValue) { |
| | | if (isBlank(value)) { |
| | | return defaultValue; |
| | | } |
| | | try { |
| | | int parsed = Integer.parseInt(value.trim()); |
| | | return parsed < 0 ? defaultValue : parsed; |
| | | } catch (Exception e) { |
| | | return defaultValue; |
| | | } |
| | | } |
| | | |
| | | private String formatPercent(double value) { |
| | | return String.format(Locale.ROOT, "%.1f%%", value * 100.0); |
| | | } |
| | | |
| | | private boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | |
| | | private static class DispatchLimitConfig { |
| | | // 圈最大承载能力,默认80% |
| | | private double circleMaxLoadLimit = 0.8d; |
| | | // 是否启用绕圈模式(仅启用时才生效承载限制) |
| | | private boolean loopModeEnable = false; |
| | | } |
| | | |
| | | private static class LoadGuardState { |
| | | private int totalStationCount = 0; |
| | | private int projectedTaskStationCount = 0; |
| | | private final Map<Integer, Integer> stationLoopNoMap = new HashMap<>(); |
| | | |
| | | private double currentLoad() { |
| | | return calcLoad(this.projectedTaskStationCount, this.totalStationCount); |
| | | } |
| | | |
| | | private double loadAfterReserve() { |
| | | return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount); |
| | | } |
| | | |
| | | private void reserveLoopTask(Integer loopNo) { |
| | | if (loopNo == null || loopNo <= 0) { |
| | | return; |
| | | } |
| | | if (this.totalStationCount <= 0) { |
| | | return; |
| | | } |
| | | this.projectedTaskStationCount++; |
| | | } |
| | | |
| | | private double calcLoad(int taskCount, int stationCount) { |
| | | if (stationCount <= 0 || taskCount <= 0) { |
| | | return 0.0; |
| | | } |
| | | double load = (double) taskCount / (double) stationCount; |
| | | if (load < 0.0) { |
| | | return 0.0; |
| | | } |
| | | if (load > 1.0) { |
| | | return 1.0; |
| | | } |
| | | return load; |
| | | } |
| | | } |
| | | |
| | | private static class LoopHitResult { |
| | | private static final LoopHitResult NO_HIT = new LoopHitResult(false, null, null); |
| | | private final boolean throughLoop; |
| | | private final Integer loopNo; |
| | | private final Integer hitStationId; |
| | | |
| | | private LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) { |
| | | this.throughLoop = throughLoop; |
| | | this.loopNo = loopNo; |
| | | this.hitStationId = hitStationId; |
| | | } |
| | | |
| | | private boolean isThroughLoop() { |
| | | return throughLoop; |
| | | } |
| | | |
| | | private Integer getLoopNo() { |
| | | return loopNo; |
| | | } |
| | | |
| | | private Integer getHitStationId() { |
| | | return hitStationId; |
| | | } |
| | | } |
| | | |
| | | private static class StationTaskIdleTrack { |
| | | private Integer taskNo; |
| | | private Integer stationId; |
| | | private Long firstSeenTime; |
| | | |
| | | private StationTaskIdleTrack() {} |
| | | |
| | | private StationTaskIdleTrack(Integer taskNo, Integer stationId, Long firstSeenTime) { |
| | | this.taskNo = taskNo; |
| | | this.stationId = stationId; |
| | | this.firstSeenTime = firstSeenTime; |
| | | } |
| | | |
| | | private boolean isTimeout(int seconds) { |
| | | if (firstSeenTime == null) { |
| | | return false; |
| | | } |
| | | return System.currentTimeMillis() - firstSeenTime >= seconds * 1000L; |
| | | } |
| | | |
| | | public Integer getTaskNo() { |
| | | return taskNo; |
| | | } |
| | | |
| | | public void setTaskNo(Integer taskNo) { |
| | | this.taskNo = taskNo; |
| | | } |
| | | |
| | | public Integer getStationId() { |
| | | return stationId; |
| | | } |
| | | |
| | | public void setStationId(Integer stationId) { |
| | | this.stationId = stationId; |
| | | } |
| | | |
| | | public Long getFirstSeenTime() { |
| | | return firstSeenTime; |
| | | } |
| | | |
| | | public void setFirstSeenTime(Long firstSeenTime) { |
| | | this.firstSeenTime = firstSeenTime; |
| | | } |
| | | return stationRerouteProcessor.shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.zy.asrs.domain.path.StationPathResolvedPolicy; |
| | | import com.zy.asrs.domain.vo.StationCycleCapacityVo; |
| | | import com.zy.asrs.domain.vo.StationCycleLoopVo; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.StationCycleCapacityService; |
| | | import com.zy.asrs.service.StationPathPolicyService; |
| | | import com.zy.common.model.NavigateNode; |
| | | import com.zy.common.utils.NavigateUtils; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.model.DispatchLimitConfig; |
| | | import com.zy.core.utils.station.model.LoadGuardState; |
| | | import com.zy.core.utils.station.model.LoopHitResult; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | |
| | | @Component |
| | | public class StationDispatchLoadSupport { |
| | | |
| | | private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120; |
| | | |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | @Autowired |
| | | private BasDevpService basDevpService; |
| | | @Autowired |
| | | private StationCycleCapacityService stationCycleCapacityService; |
| | | @Autowired |
| | | private StationPathPolicyService stationPathPolicyService; |
| | | @Autowired |
| | | private NavigateUtils navigateUtils; |
| | | |
| | | public int countCurrentStationTask() { |
| | | int currentStationTaskCount = 0; |
| | | List<BasDevp> basDevps = basDevpService.list(); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | for (StationProtocol stationProtocol : stationThread.getStatus()) { |
| | | if (stationProtocol.getTaskNo() > 0) { |
| | | currentStationTaskCount++; |
| | | } |
| | | } |
| | | } |
| | | return currentStationTaskCount; |
| | | } |
| | | |
| | | public boolean isDispatchBlocked(DispatchLimitConfig config, |
| | | int currentStationTaskCount, |
| | | LoadGuardState loadGuardState, |
| | | boolean needReserveLoopLoad) { |
| | | if (config != null && config.isLoopModeEnable()) { |
| | | double currentLoad = loadGuardState.currentLoad(); |
| | | if (currentLoad >= config.getCircleMaxLoadLimit()) { |
| | | News.warn("当前承载量达到上限,已停止站点任务下发。当前承载量={},上限={}", formatPercent(currentLoad), formatPercent(config.getCircleMaxLoadLimit())); |
| | | return true; |
| | | } |
| | | |
| | | if (needReserveLoopLoad) { |
| | | double reserveLoad = loadGuardState.loadAfterReserve(); |
| | | if (reserveLoad >= config.getCircleMaxLoadLimit()) { |
| | | News.warn("预占后承载量达到上限,已停止站点任务下发。预占后承载量={},上限={}", formatPercent(reserveLoad), formatPercent(config.getCircleMaxLoadLimit())); |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public LoadGuardState buildLoadGuardState(DispatchLimitConfig config) { |
| | | LoadGuardState state = new LoadGuardState(); |
| | | if (config == null || !config.isLoopModeEnable()) { |
| | | return state; |
| | | } |
| | | |
| | | StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); |
| | | if (capacityVo == null) { |
| | | return state; |
| | | } |
| | | |
| | | Integer occupiedStationCount = capacityVo.getOccupiedStationCount(); |
| | | state.setTotalStationCount(toNonNegative(capacityVo.getTotalStationCount())); |
| | | state.setProjectedTaskStationCount(toNonNegative(occupiedStationCount != null ? occupiedStationCount : capacityVo.getTaskStationCount())); |
| | | |
| | | List<StationCycleLoopVo> loopList = capacityVo.getLoopList(); |
| | | if (loopList != null) { |
| | | for (StationCycleLoopVo loopVo : loopList) { |
| | | if (loopVo == null || loopVo.getStationIdList() == null) { |
| | | continue; |
| | | } |
| | | Integer loopNo = loopVo.getLoopNo(); |
| | | for (Integer stationId : loopVo.getStationIdList()) { |
| | | if (stationId != null && loopNo != null) { |
| | | state.putStationLoopNo(stationId, loopNo); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return state; |
| | | } |
| | | |
| | | public LoopHitResult findPathLoopHit(DispatchLimitConfig config, |
| | | Integer sourceStationId, |
| | | Integer targetStationId, |
| | | LoadGuardState loadGuardState) { |
| | | return findPathLoopHit(config, sourceStationId, targetStationId, loadGuardState, null, null); |
| | | } |
| | | |
| | | public LoopHitResult findPathLoopHit(DispatchLimitConfig config, |
| | | Integer sourceStationId, |
| | | Integer targetStationId, |
| | | LoadGuardState loadGuardState, |
| | | WrkMast wrkMast, |
| | | Double pathLenFactor) { |
| | | if (config == null || !config.isLoopModeEnable()) { |
| | | return LoopHitResult.noHit(); |
| | | } |
| | | if (sourceStationId == null || targetStationId == null) { |
| | | return LoopHitResult.noHit(); |
| | | } |
| | | if (loadGuardState == null || loadGuardState.getStationLoopNoMap().isEmpty()) { |
| | | return LoopHitResult.noHit(); |
| | | } |
| | | |
| | | try { |
| | | List<NavigateNode> nodes = wrkMast == null |
| | | ? navigateUtils.calcByStationId(sourceStationId, targetStationId) |
| | | : calcOutboundNavigatePath(wrkMast, sourceStationId, targetStationId, pathLenFactor); |
| | | if (nodes == null || nodes.isEmpty()) { |
| | | return LoopHitResult.noHit(); |
| | | } |
| | | |
| | | for (NavigateNode node : nodes) { |
| | | Integer stationId = getStationIdFromNode(node); |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | Integer loopNo = loadGuardState.getStationLoopNoMap().get(stationId); |
| | | if (loopNo != null) { |
| | | return new LoopHitResult(true, loopNo, stationId); |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | return LoopHitResult.noHit(); |
| | | } |
| | | |
| | | return LoopHitResult.noHit(); |
| | | } |
| | | |
| | | public void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) { |
| | | if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) { |
| | | return; |
| | | } |
| | | JSONObject reserveJson = new JSONObject(); |
| | | reserveJson.put("wrkNo", wrkNo); |
| | | reserveJson.put("loopNo", loopHitResult.getLoopNo()); |
| | | reserveJson.put("hitStationId", loopHitResult.getHitStationId()); |
| | | reserveJson.put("createTime", System.currentTimeMillis()); |
| | | redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString()); |
| | | redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS); |
| | | } |
| | | |
| | | public DispatchLimitConfig getDispatchLimitConfig(Integer startStationId, Integer endStationId) { |
| | | DispatchLimitConfig config = new DispatchLimitConfig(); |
| | | Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); |
| | | if (systemConfigMapObj instanceof Map) { |
| | | Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj; |
| | | config.setCircleMaxLoadLimit(parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.getCircleMaxLoadLimit())); |
| | | String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable"); |
| | | if (isBlank(loopModeValue)) { |
| | | loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable"); |
| | | } |
| | | if (isBlank(loopModeValue)) { |
| | | loopModeValue = getConfigValue(systemConfigMap, "isCircleMode"); |
| | | } |
| | | config.setLoopModeEnable(parseBoolean(loopModeValue, config.isLoopModeEnable())); |
| | | } |
| | | |
| | | if (stationPathPolicyService != null && startStationId != null && endStationId != null) { |
| | | try { |
| | | StationPathResolvedPolicy resolvedPolicy = stationPathPolicyService.resolvePolicy(startStationId, endStationId); |
| | | if (resolvedPolicy != null && resolvedPolicy.getProfileConfig() != null) { |
| | | config.setCircleMaxLoadLimit(parseLoadLimit(String.valueOf(resolvedPolicy.getProfileConfig().getCircleMaxLoadLimit()), config.getCircleMaxLoadLimit())); |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | |
| | | return config; |
| | | } |
| | | |
| | | private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast, |
| | | Integer sourceStationId, |
| | | Integer targetStationId, |
| | | Double pathLenFactor) { |
| | | Double normalizedFactor = normalizePathLenFactor(pathLenFactor); |
| | | Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo(); |
| | | if (currentTaskNo == null) { |
| | | return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor); |
| | | } |
| | | return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor); |
| | | } |
| | | |
| | | private Integer getStationIdFromNode(NavigateNode node) { |
| | | if (node == null || isBlank(node.getNodeValue())) { |
| | | return null; |
| | | } |
| | | try { |
| | | JSONObject value = JSONObject.parseObject(node.getNodeValue()); |
| | | return value == null ? null : value.getInteger("stationId"); |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private int toNonNegative(Integer value) { |
| | | if (value == null || value < 0) { |
| | | return 0; |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private Double normalizePathLenFactor(Double pathLenFactor) { |
| | | if (pathLenFactor == null || pathLenFactor < 0.0d) { |
| | | return 0.0d; |
| | | } |
| | | if (pathLenFactor > 1.0d) { |
| | | return 1.0d; |
| | | } |
| | | return pathLenFactor; |
| | | } |
| | | |
| | | private String getConfigValue(Map<?, ?> configMap, String key) { |
| | | Object value = configMap.get(key); |
| | | return value == null ? null : String.valueOf(value).trim(); |
| | | } |
| | | |
| | | private boolean parseBoolean(String value, boolean defaultValue) { |
| | | if (isBlank(value)) { |
| | | return defaultValue; |
| | | } |
| | | String lowValue = value.toLowerCase(Locale.ROOT); |
| | | if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue) |
| | | || "1".equals(lowValue) || "on".equals(lowValue)) { |
| | | return true; |
| | | } |
| | | if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue) |
| | | || "0".equals(lowValue) || "off".equals(lowValue)) { |
| | | return false; |
| | | } |
| | | return defaultValue; |
| | | } |
| | | |
| | | private double parseLoadLimit(String value, double defaultValue) { |
| | | if (isBlank(value)) { |
| | | return defaultValue; |
| | | } |
| | | try { |
| | | String normalized = value.replace("%", "").trim(); |
| | | double parsed = Double.parseDouble(normalized); |
| | | if (parsed > 1.0) { |
| | | parsed = parsed / 100.0; |
| | | } |
| | | if (parsed < 0.0) { |
| | | return 0.0; |
| | | } |
| | | if (parsed > 1.0) { |
| | | return 1.0; |
| | | } |
| | | return parsed; |
| | | } catch (Exception ignore) { |
| | | return defaultValue; |
| | | } |
| | | } |
| | | |
| | | private String formatPercent(double value) { |
| | | return String.format(Locale.ROOT, "%.1f%%", value * 100.0); |
| | | } |
| | | |
| | | private boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.serializer.SerializerFeature; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.core.common.Cools; |
| | | import com.zy.asrs.entity.BasStationOpt; |
| | | import com.zy.asrs.service.BasStationOptService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | @Component |
| | | public class StationDispatchRuntimeStateSupport { |
| | | private static final int STATION_IDLE_TRACK_EXPIRE_SECONDS = 60 * 60; |
| | | private static final String IDLE_RECOVER_CLEARED_MEMO = "idleRecoverRerouteCleared"; |
| | | |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | @Autowired |
| | | private BasStationOptService basStationOptService; |
| | | |
| | | public StationTaskIdleTrack touchIdleTrack(Integer taskNo, Integer stationId) { |
| | | if (taskNo == null || taskNo <= 0 || stationId == null) { |
| | | return null; |
| | | } |
| | | long now = System.currentTimeMillis(); |
| | | StationTaskIdleTrack idleTrack = loadIdleTrack(taskNo); |
| | | if (idleTrack == null || !Objects.equals(idleTrack.getStationId(), stationId)) { |
| | | idleTrack = new StationTaskIdleTrack(taskNo, stationId, now); |
| | | saveIdleTrack(idleTrack); |
| | | } |
| | | return idleTrack; |
| | | } |
| | | |
| | | public StationTaskIdleTrack loadIdleTrack(Integer taskNo) { |
| | | if (taskNo == null || taskNo <= 0 || redisUtil == null) { |
| | | return null; |
| | | } |
| | | Object obj = redisUtil.get(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + taskNo); |
| | | if (obj == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return JSON.parseObject(obj.toString(), StationTaskIdleTrack.class); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public void saveIdleTrack(StationTaskIdleTrack idleTrack) { |
| | | if (idleTrack == null || idleTrack.getTaskNo() == null || idleTrack.getTaskNo() <= 0 || redisUtil == null) { |
| | | return; |
| | | } |
| | | redisUtil.set( |
| | | RedisKeyType.STATION_TASK_IDLE_TRACK_.key + idleTrack.getTaskNo(), |
| | | JSON.toJSONString(idleTrack, SerializerFeature.DisableCircularReferenceDetect), |
| | | STATION_IDLE_TRACK_EXPIRE_SECONDS |
| | | ); |
| | | } |
| | | |
| | | public boolean hasRecentIssuedMoveCommand(Integer taskNo, Integer stationId, long thresholdMs) { |
| | | if (taskNo == null || taskNo <= 0 || stationId == null || thresholdMs <= 0L || basStationOptService == null) { |
| | | return false; |
| | | } |
| | | Date thresholdTime = new Date(System.currentTimeMillis() - thresholdMs); |
| | | List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>() |
| | | .select("id") |
| | | .eq("task_no", taskNo) |
| | | .eq("station_id", stationId) |
| | | .eq("mode", String.valueOf(StationCommandType.MOVE)) |
| | | .eq("send", 1) |
| | | .ge("send_time", thresholdTime) |
| | | .orderByDesc("send_time") |
| | | .last("limit 1")); |
| | | return optList != null && !optList.isEmpty(); |
| | | } |
| | | |
| | | public int clearIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack, |
| | | Integer taskNo, |
| | | Integer stationId) { |
| | | if (basStationOptService == null) { |
| | | return 0; |
| | | } |
| | | List<BasStationOpt> optList; |
| | | try { |
| | | optList = listIssuedMoveCommandsDuringIdleStay(idleTrack, taskNo); |
| | | } catch (Exception e) { |
| | | return 0; |
| | | } |
| | | if (optList == null || optList.isEmpty()) { |
| | | return 0; |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | String cleanupMemo = buildIdleRecoverClearedMemo(stationId); |
| | | int clearedCount = 0; |
| | | for (BasStationOpt opt : optList) { |
| | | if (opt == null || opt.getId() == null) { |
| | | continue; |
| | | } |
| | | opt.setSend(0); |
| | | opt.setUpdateTime(now); |
| | | opt.setMemo(appendCleanupMemo(opt.getMemo(), cleanupMemo)); |
| | | clearedCount++; |
| | | } |
| | | if (clearedCount > 0) { |
| | | basStationOptService.updateBatchById(optList); |
| | | } |
| | | return clearedCount; |
| | | } |
| | | |
| | | public boolean tryAcquireLock(String key, int seconds) { |
| | | if (redisUtil == null || isBlank(key)) { |
| | | return true; |
| | | } |
| | | Object lock = redisUtil.get(key); |
| | | if (lock != null) { |
| | | return false; |
| | | } |
| | | redisUtil.set(key, "lock", seconds); |
| | | return true; |
| | | } |
| | | |
| | | public boolean tryAcquireOutOrderDispatchLock(Integer wrkNo, Integer stationId, int seconds) { |
| | | if (wrkNo == null || wrkNo <= 0 || stationId == null) { |
| | | return true; |
| | | } |
| | | return tryAcquireLock(RedisKeyType.STATION_OUT_ORDER_DISPATCH_LIMIT_.key + wrkNo + "_" + stationId, seconds); |
| | | } |
| | | |
| | | public void signalSegmentReset(Integer taskNo, long waitMs) { |
| | | if (redisUtil == null || taskNo == null || taskNo <= 0) { |
| | | return; |
| | | } |
| | | String key = RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo; |
| | | redisUtil.set(key, "cancel", 3); |
| | | try { |
| | | if (waitMs > 0L) { |
| | | Thread.sleep(waitMs); |
| | | } |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | } catch (Exception ignore) { |
| | | } |
| | | redisUtil.del(key); |
| | | } |
| | | |
| | | public StationCommand loadWatchCircleCommand(Integer wrkNo) { |
| | | if (wrkNo == null || wrkNo <= 0 || redisUtil == null) { |
| | | return null; |
| | | } |
| | | Object circleObj = redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo); |
| | | if (circleObj == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | return JSON.parseObject(circleObj.toString(), StationCommand.class); |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public void saveWatchCircleCommand(Integer wrkNo, StationCommand command) { |
| | | if (wrkNo == null || wrkNo <= 0 || command == null || redisUtil == null) { |
| | | return; |
| | | } |
| | | redisUtil.set( |
| | | RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo, |
| | | JSON.toJSONString(command, SerializerFeature.DisableCircularReferenceDetect), |
| | | 60 * 60 * 24 |
| | | ); |
| | | } |
| | | |
| | | public void clearWatchCircleCommand(Integer wrkNo) { |
| | | if (wrkNo == null || wrkNo <= 0 || redisUtil == null) { |
| | | return; |
| | | } |
| | | redisUtil.del(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo); |
| | | } |
| | | |
| | | private List<BasStationOpt> listIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack, |
| | | Integer taskNo) { |
| | | if (idleTrack == null || taskNo == null || taskNo <= 0 || idleTrack.getFirstSeenTime() == null || basStationOptService == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>() |
| | | .select("id", "task_no", "send_time", "target_station_id", "memo", "send") |
| | | .eq("task_no", taskNo) |
| | | .eq("mode", String.valueOf(StationCommandType.MOVE)) |
| | | .eq("send", 1) |
| | | .ge("send_time", new Date(idleTrack.getFirstSeenTime())) |
| | | .orderByAsc("send_time")); |
| | | if (optList == null || optList.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return optList; |
| | | } |
| | | |
| | | private String buildIdleRecoverClearedMemo(Integer stationId) { |
| | | if (stationId == null) { |
| | | return IDLE_RECOVER_CLEARED_MEMO; |
| | | } |
| | | return IDLE_RECOVER_CLEARED_MEMO + "(stationId=" + stationId + ")"; |
| | | } |
| | | |
| | | private String appendCleanupMemo(String memo, String cleanupMemo) { |
| | | if (Cools.isEmpty(cleanupMemo)) { |
| | | return memo; |
| | | } |
| | | if (Cools.isEmpty(memo)) { |
| | | return cleanupMemo; |
| | | } |
| | | if (memo.contains(cleanupMemo)) { |
| | | return memo; |
| | | } |
| | | return memo + " | " + cleanupMemo; |
| | | } |
| | | |
| | | private boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.core.common.Cools; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.BasStation; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.BasStationService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.common.model.NavigateNode; |
| | | import com.zy.common.utils.NavigateUtils; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.enums.WrkIoType; |
| | | import com.zy.core.enums.WrkStsType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.move.StationMoveDispatchMode; |
| | | import com.zy.core.move.StationMoveSession; |
| | | import com.zy.core.service.StationTaskLoopService; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.model.CircleTargetCandidate; |
| | | import com.zy.core.utils.station.model.OutOrderDispatchDecision; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | @Component |
| | | public class StationOutboundDecisionSupport { |
| | | |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private BasDevpService basDevpService; |
| | | @Autowired |
| | | private BasStationService basStationService; |
| | | @Autowired |
| | | private NavigateUtils navigateUtils; |
| | | @Autowired |
| | | private StationTaskLoopService stationTaskLoopService; |
| | | @Autowired |
| | | private StationMoveCoordinator stationMoveCoordinator; |
| | | @Autowired |
| | | private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport; |
| | | |
| | | public StationCommand buildOutboundMoveCommand(StationThread stationThread, |
| | | WrkMast wrkMast, |
| | | Integer stationId, |
| | | Integer targetStationId, |
| | | Double pathLenFactor) { |
| | | if (stationThread == null || wrkMast == null) { |
| | | return null; |
| | | } |
| | | return stationThread.getCommand( |
| | | StationCommandType.MOVE, |
| | | wrkMast.getWrkNo(), |
| | | stationId, |
| | | targetStationId, |
| | | 0, |
| | | normalizePathLenFactor(pathLenFactor) |
| | | ); |
| | | } |
| | | |
| | | public Double resolveOutboundPathLenFactor(WrkMast wrkMast) { |
| | | if (!isBatchOutboundTaskWithSeq(wrkMast)) { |
| | | return 0.0d; |
| | | } |
| | | List<WrkMast> activeBatchTaskList = loadActiveBatchTaskList(wrkMast.getBatch()); |
| | | if (activeBatchTaskList.size() <= 1) { |
| | | return 0.0d; |
| | | } |
| | | |
| | | int activeTaskCount = 0; |
| | | int predecessorCount = 0; |
| | | for (WrkMast item : activeBatchTaskList) { |
| | | if (!isFactorCandidateTask(item)) { |
| | | continue; |
| | | } |
| | | activeTaskCount++; |
| | | if (item.getBatchSeq() < wrkMast.getBatchSeq()) { |
| | | predecessorCount++; |
| | | } |
| | | } |
| | | if (activeTaskCount <= 1 || predecessorCount <= 0) { |
| | | return 0.0d; |
| | | } |
| | | return normalizePathLenFactor((double) predecessorCount / (double) (activeTaskCount - 1)); |
| | | } |
| | | |
| | | public List<Integer> getAllOutOrderList() { |
| | | List<Integer> list = new ArrayList<>(); |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | List<Integer> orderList = basDevp.getOutOrderIntList(); |
| | | list.addAll(orderList); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | public OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor) { |
| | | if (wrkMast == null || wrkMast.getStaNo() == null) { |
| | | return null; |
| | | } |
| | | if (!shouldApplyOutOrder(wrkMast, outOrderStationIds)) { |
| | | return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); |
| | | } |
| | | Integer dispatchStationId = resolveDispatchOutOrderTarget( |
| | | wrkMast, |
| | | wrkMast.getSourceStaNo(), |
| | | wrkMast.getStaNo(), |
| | | outOrderStationIds, |
| | | pathLenFactor |
| | | ); |
| | | if (dispatchStationId == null) { |
| | | return null; |
| | | } |
| | | if (isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { |
| | | return resolveCurrentOutOrderDispatchDecision(currentStationId, wrkMast, outOrderStationIds, pathLenFactor); |
| | | } |
| | | if (!Objects.equals(dispatchStationId, wrkMast.getStaNo()) |
| | | && isCurrentOutOrderStation(currentStationId, outOrderStationIds) |
| | | && isWatchingCircleArrival(wrkMast.getWrkNo(), currentStationId)) { |
| | | return OutOrderDispatchDecision.circle(dispatchStationId, null, false); |
| | | } |
| | | return OutOrderDispatchDecision.direct(dispatchStationId); |
| | | } |
| | | |
| | | public void syncOutOrderWatchState(WrkMast wrkMast, |
| | | Integer currentStationId, |
| | | List<Integer> outOrderStationIds, |
| | | OutOrderDispatchDecision dispatchDecision, |
| | | StationCommand command) { |
| | | if (dispatchDecision == null || command == null || !shouldApplyOutOrder(wrkMast, outOrderStationIds)) { |
| | | return; |
| | | } |
| | | if (dispatchDecision.isCircle()) { |
| | | stationDispatchRuntimeStateSupport.saveWatchCircleCommand(wrkMast.getWrkNo(), command); |
| | | if (dispatchDecision.shouldCountLoopIssue() |
| | | && stationTaskLoopService != null |
| | | && dispatchDecision.getLoopEvaluation() != null) { |
| | | stationTaskLoopService.recordLoopIssue(dispatchDecision.getLoopEvaluation(), "OUT_ORDER_CIRCLE"); |
| | | } |
| | | } else { |
| | | stationDispatchRuntimeStateSupport.clearWatchCircleCommand(wrkMast.getWrkNo()); |
| | | } |
| | | } |
| | | |
| | | public boolean shouldSkipOutOrderDispatchForExistingRoute(Integer wrkNo, Integer stationId) { |
| | | if (stationMoveCoordinator == null || wrkNo == null || wrkNo <= 0 || stationId == null) { |
| | | return false; |
| | | } |
| | | StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); |
| | | if (session == null) { |
| | | return false; |
| | | } |
| | | if (!session.isActive() || !session.containsStation(stationId)) { |
| | | return false; |
| | | } |
| | | if (StationMoveDispatchMode.CIRCLE == session.getDispatchMode()) { |
| | | return true; |
| | | } |
| | | return !Objects.equals(stationId, session.getCurrentRouteTargetStationId()); |
| | | } |
| | | |
| | | public boolean isWatchingCircleArrival(Integer wrkNo, Integer stationId) { |
| | | if (stationMoveCoordinator != null) { |
| | | StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); |
| | | if (session != null && session.isActive() && stationId != null) { |
| | | if (stationId.equals(session.getNextDecisionStationId())) { |
| | | return true; |
| | | } |
| | | if (session.containsStation(stationId)) { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | StationCommand command = stationDispatchRuntimeStateSupport.loadWatchCircleCommand(wrkNo); |
| | | return command != null && stationId != null && stationId.equals(command.getTargetStaNo()); |
| | | } |
| | | |
| | | private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast, |
| | | Integer sourceStationId, |
| | | Integer targetStationId, |
| | | Double pathLenFactor) { |
| | | Double normalizedFactor = normalizePathLenFactor(pathLenFactor); |
| | | Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo(); |
| | | if (currentTaskNo == null) { |
| | | return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor); |
| | | } |
| | | return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor); |
| | | } |
| | | |
| | | private boolean isBatchOutboundTaskWithSeq(WrkMast wrkMast) { |
| | | return wrkMast != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) |
| | | && !Cools.isEmpty(wrkMast.getBatch()) |
| | | && wrkMast.getBatchSeq() != null |
| | | && wrkMast.getWrkNo() != null; |
| | | } |
| | | |
| | | private List<WrkMast> loadActiveBatchTaskList(String batch) { |
| | | if (Cools.isEmpty(batch)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("io_type", WrkIoType.OUT.id) |
| | | .eq("batch", batch) |
| | | .notIn("wrk_sts", |
| | | WrkStsType.STATION_RUN_COMPLETE.sts, |
| | | WrkStsType.COMPLETE_OUTBOUND.sts, |
| | | WrkStsType.SETTLE_OUTBOUND.sts)); |
| | | } |
| | | |
| | | private boolean isFactorCandidateTask(WrkMast wrkMast) { |
| | | return wrkMast != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) |
| | | && wrkMast.getBatchSeq() != null |
| | | && !"taskCancel".equals(wrkMast.getMk()); |
| | | } |
| | | |
| | | private boolean shouldApplyOutOrder(WrkMast wrkMast, List<Integer> outOrderStationIds) { |
| | | return wrkMast != null |
| | | && wrkMast.getStaNo() != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) |
| | | && !Cools.isEmpty(wrkMast.getBatch()) |
| | | && wrkMast.getBatchSeq() != null |
| | | && outOrderStationIds != null |
| | | && !outOrderStationIds.isEmpty(); |
| | | } |
| | | |
| | | private boolean isCurrentOutOrderDispatchStation(Integer currentStationId, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor) { |
| | | if (!shouldApplyOutOrder(wrkMast, outOrderStationIds) || currentStationId == null) { |
| | | return false; |
| | | } |
| | | Integer dispatchStationId = resolveDispatchOutOrderTarget( |
| | | wrkMast, |
| | | wrkMast.getSourceStaNo(), |
| | | wrkMast.getStaNo(), |
| | | outOrderStationIds, |
| | | pathLenFactor |
| | | ); |
| | | return dispatchStationId != null |
| | | && !Objects.equals(dispatchStationId, wrkMast.getStaNo()) |
| | | && Objects.equals(currentStationId, dispatchStationId); |
| | | } |
| | | |
| | | private boolean isCurrentOutOrderStation(Integer currentStationId, |
| | | List<Integer> outOrderStationIds) { |
| | | return currentStationId != null |
| | | && outOrderStationIds != null |
| | | && outOrderStationIds.contains(currentStationId); |
| | | } |
| | | |
| | | private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor) { |
| | | if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { |
| | | return null; |
| | | } |
| | | |
| | | List<WrkMast> batchWrkList = wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("io_type", WrkIoType.OUT.id) |
| | | .notIn("wrk_sts", |
| | | WrkStsType.STATION_RUN_COMPLETE.sts, |
| | | WrkStsType.COMPLETE_OUTBOUND.sts, |
| | | WrkStsType.SETTLE_OUTBOUND.sts) |
| | | .eq("batch", wrkMast.getBatch()) |
| | | .orderByAsc("batch_seq") |
| | | .orderByAsc("wrk_no")); |
| | | if (batchWrkList.isEmpty()) { |
| | | return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); |
| | | } |
| | | |
| | | WrkMast firstWrkMast = batchWrkList.get(0); |
| | | Integer currentBatchSeq = firstWrkMast.getBatchSeq(); |
| | | if (currentBatchSeq == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "批次:{} 首个未完成任务缺少批次序号,当前任务暂不放行", wrkMast.getBatch()); |
| | | return null; |
| | | } |
| | | |
| | | List<NavigateNode> initPath; |
| | | try { |
| | | initPath = calcOutboundNavigatePath(wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), pathLenFactor); |
| | | } catch (Exception e) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "批次:{} 计算排序路径失败,当前站点={}", wrkMast.getBatch(), currentStationId); |
| | | return null; |
| | | } |
| | | |
| | | Integer seq = getOutStationBatchSeq(initPath, currentStationId, wrkMast.getBatch()); |
| | | boolean toTarget = seq == null |
| | | ? currentBatchSeq.equals(wrkMast.getBatchSeq()) |
| | | : Integer.valueOf(seq + 1).equals(wrkMast.getBatchSeq()); |
| | | if (toTarget) { |
| | | if (hasReachableOutReleaseSlot(wrkMast, currentStationId, wrkMast.getStaNo(), pathLenFactor)) { |
| | | return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); |
| | | } |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( |
| | | wrkMast.getWrkNo(), |
| | | currentStationId, |
| | | outOrderStationIds |
| | | ); |
| | | Integer circleTarget = resolveNextCircleOrderTarget( |
| | | wrkMast, |
| | | currentStationId, |
| | | outOrderStationIds, |
| | | loopEvaluation.getExpectedLoopIssueCount(), |
| | | pathLenFactor |
| | | ); |
| | | if (circleTarget == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "目标站当前不可进,且未找到可执行的下一排序检测点,当前站点={}", currentStationId); |
| | | return null; |
| | | } |
| | | return OutOrderDispatchDecision.circle(circleTarget, loopEvaluation, true); |
| | | } |
| | | |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( |
| | | wrkMast.getWrkNo(), |
| | | currentStationId, |
| | | outOrderStationIds |
| | | ); |
| | | Integer circleTarget = resolveNextCircleOrderTarget( |
| | | wrkMast, |
| | | currentStationId, |
| | | outOrderStationIds, |
| | | loopEvaluation.getExpectedLoopIssueCount(), |
| | | pathLenFactor |
| | | ); |
| | | if (circleTarget == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "未找到可执行的下一排序检测点,当前站点={}", currentStationId); |
| | | return null; |
| | | } |
| | | return OutOrderDispatchDecision.circle(circleTarget, loopEvaluation, true); |
| | | } |
| | | |
| | | private StationTaskLoopService.LoopEvaluation evaluateOutOrderLoop(Integer taskNo, |
| | | Integer currentStationId, |
| | | List<Integer> outOrderStationIds) { |
| | | if (stationTaskLoopService == null) { |
| | | return new StationTaskLoopService.LoopEvaluation( |
| | | taskNo, |
| | | currentStationId, |
| | | StationTaskLoopService.LoopIdentitySnapshot.empty(), |
| | | 0, |
| | | 0, |
| | | false |
| | | ); |
| | | } |
| | | return stationTaskLoopService.evaluateLoop( |
| | | taskNo, |
| | | currentStationId, |
| | | true, |
| | | outOrderStationIds, |
| | | "outOrderCircle" |
| | | ); |
| | | } |
| | | |
| | | private Integer resolveDispatchOutOrderTarget(WrkMast wrkMast, |
| | | Integer sourceStationId, |
| | | Integer finalTargetStationId, |
| | | List<Integer> outOrderList, |
| | | Double pathLenFactor) { |
| | | if (finalTargetStationId == null) { |
| | | return null; |
| | | } |
| | | if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) { |
| | | return finalTargetStationId; |
| | | } |
| | | |
| | | try { |
| | | List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, sourceStationId, finalTargetStationId, pathLenFactor); |
| | | for (int i = nodes.size() - 1; i >= 0; i--) { |
| | | Integer stationId = getStationIdFromNode(nodes.get(i)); |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | if (Objects.equals(stationId, finalTargetStationId)) { |
| | | continue; |
| | | } |
| | | if (outOrderList.contains(stationId)) { |
| | | return stationId; |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return finalTargetStationId; |
| | | } |
| | | |
| | | private boolean hasReachableOutReleaseSlot(WrkMast wrkMast, |
| | | Integer currentStationId, |
| | | Integer finalTargetStationId, |
| | | Double pathLenFactor) { |
| | | if (currentStationId == null || finalTargetStationId == null) { |
| | | return true; |
| | | } |
| | | |
| | | try { |
| | | List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, currentStationId, finalTargetStationId, pathLenFactor); |
| | | if (nodes == null || nodes.isEmpty()) { |
| | | return true; |
| | | } |
| | | |
| | | for (NavigateNode node : nodes) { |
| | | Integer stationId = getStationIdFromNode(node); |
| | | if (stationId == null || Objects.equals(stationId, currentStationId)) { |
| | | continue; |
| | | } |
| | | if (!isPathStationBlocked(stationId)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } catch (Exception ignore) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | private boolean isPathStationBlocked(Integer stationId) { |
| | | if (stationId == null) { |
| | | return true; |
| | | } |
| | | |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", stationId)); |
| | | if (basStation == null) { |
| | | return true; |
| | | } |
| | | |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | return true; |
| | | } |
| | | |
| | | StationProtocol stationProtocol = stationThread.getStatusMap().get(stationId); |
| | | if (stationProtocol == null) { |
| | | return true; |
| | | } |
| | | |
| | | return !stationProtocol.isAutoing() |
| | | || stationProtocol.isLoading() |
| | | || (stationProtocol.getTaskNo() != null && stationProtocol.getTaskNo() > 0); |
| | | } |
| | | |
| | | private Integer resolveNextCircleOrderTarget(WrkMast wrkMast, |
| | | Integer currentStationId, |
| | | List<Integer> orderedOutStationList, |
| | | Integer expectedLoopIssueCount, |
| | | Double pathLenFactor) { |
| | | if (currentStationId == null || orderedOutStationList == null || orderedOutStationList.size() <= 1) { |
| | | return null; |
| | | } |
| | | |
| | | int startIndex = orderedOutStationList.indexOf(currentStationId); |
| | | int total = orderedOutStationList.size(); |
| | | List<CircleTargetCandidate> candidateList = new ArrayList<>(); |
| | | for (int offset = 1; offset < total; offset++) { |
| | | int candidateIndex = (startIndex + offset + total) % total; |
| | | Integer candidateStationId = orderedOutStationList.get(candidateIndex); |
| | | if (candidateStationId == null || currentStationId.equals(candidateStationId)) { |
| | | continue; |
| | | } |
| | | try { |
| | | List<NavigateNode> path = calcOutboundNavigatePath(wrkMast, currentStationId, candidateStationId, pathLenFactor); |
| | | if (path != null && !path.isEmpty()) { |
| | | candidateList.add(new CircleTargetCandidate(candidateStationId, path.size(), offset)); |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | if (candidateList.isEmpty()) { |
| | | return null; |
| | | } |
| | | candidateList.sort(new Comparator<CircleTargetCandidate>() { |
| | | @Override |
| | | public int compare(CircleTargetCandidate left, CircleTargetCandidate right) { |
| | | if (left == right) { |
| | | return 0; |
| | | } |
| | | if (left == null) { |
| | | return 1; |
| | | } |
| | | if (right == null) { |
| | | return -1; |
| | | } |
| | | int pathCompare = Integer.compare(left.getPathLength(), right.getPathLength()); |
| | | if (pathCompare != 0) { |
| | | return pathCompare; |
| | | } |
| | | return Integer.compare(left.getOffset(), right.getOffset()); |
| | | } |
| | | }); |
| | | return resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor); |
| | | } |
| | | |
| | | private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount, |
| | | List<CircleTargetCandidate> candidateList, |
| | | Double pathLenFactor) { |
| | | if (candidateList == null || candidateList.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | List<CircleTargetCandidate> tierList = new ArrayList<>(); |
| | | Integer lastPathLength = null; |
| | | for (CircleTargetCandidate candidate : candidateList) { |
| | | if (candidate == null) { |
| | | continue; |
| | | } |
| | | if (lastPathLength == null || !Objects.equals(lastPathLength, candidate.getPathLength())) { |
| | | tierList.add(candidate); |
| | | lastPathLength = candidate.getPathLength(); |
| | | } |
| | | } |
| | | if (tierList.isEmpty()) { |
| | | return candidateList.get(0).getStationId(); |
| | | } |
| | | int defaultTierIndex = expectedLoopIssueCount == null || expectedLoopIssueCount <= 2 |
| | | ? 0 |
| | | : Math.min(expectedLoopIssueCount - 2, tierList.size() - 1); |
| | | int factorTierIndex = (int) Math.round(normalizePathLenFactor(pathLenFactor) * (tierList.size() - 1)); |
| | | int tierIndex = Math.max(defaultTierIndex, factorTierIndex); |
| | | return tierList.get(tierIndex).getStationId(); |
| | | } |
| | | |
| | | private Integer getOutStationBatchSeq(List<NavigateNode> pathList, Integer searchStationId, String searchBatch) { |
| | | if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) { |
| | | return null; |
| | | } |
| | | |
| | | List<Integer> checkList = new ArrayList<>(); |
| | | for (int i = pathList.size() - 1; i >= 0; i--) { |
| | | NavigateNode node = pathList.get(i); |
| | | JSONObject value = JSONObject.parseObject(node.getNodeValue()); |
| | | if (value == null) { |
| | | continue; |
| | | } |
| | | Integer stationId = value.getInteger("stationId"); |
| | | if (searchStationId.equals(stationId)) { |
| | | break; |
| | | } |
| | | checkList.add(stationId); |
| | | } |
| | | |
| | | HashMap<String, Integer> batchMap = new HashMap<>(); |
| | | for (Integer station : checkList) { |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", station)); |
| | | if (basStation == null) { |
| | | continue; |
| | | } |
| | | |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | StationProtocol checkStationProtocol = stationThread.getStatusMap().get(station); |
| | | if (checkStationProtocol == null) { |
| | | continue; |
| | | } |
| | | if (checkStationProtocol.getTaskNo() > 0) { |
| | | WrkMast checkWrkMast = wrkMastService.selectByWorkNo(checkStationProtocol.getTaskNo()); |
| | | if (checkWrkMast == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (!Cools.isEmpty(checkWrkMast.getBatch())) { |
| | | batchMap.put(checkWrkMast.getBatch(), checkWrkMast.getBatchSeq()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return batchMap.get(searchBatch); |
| | | } |
| | | |
| | | private Integer getStationIdFromNode(NavigateNode node) { |
| | | if (node == null || isBlank(node.getNodeValue())) { |
| | | return null; |
| | | } |
| | | try { |
| | | JSONObject value = JSONObject.parseObject(node.getNodeValue()); |
| | | return value == null ? null : value.getInteger("stationId"); |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Double normalizePathLenFactor(Double pathLenFactor) { |
| | | if (pathLenFactor == null || pathLenFactor < 0.0d) { |
| | | return 0.0d; |
| | | } |
| | | if (pathLenFactor > 1.0d) { |
| | | return 1.0d; |
| | | } |
| | | return pathLenFactor; |
| | | } |
| | | |
| | | private boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.zy.asrs.domain.enums.NotifyMsgType; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.WrkAnalysisService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.asrs.utils.NotifyUtils; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.WrkStsType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.model.DispatchLimitConfig; |
| | | import com.zy.core.utils.station.model.LoadGuardState; |
| | | import com.zy.core.utils.station.model.LoopHitResult; |
| | | import com.zy.core.utils.station.model.OutOrderDispatchDecision; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Component |
| | | public class StationOutboundDispatchProcessor { |
| | | |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private WrkAnalysisService wrkAnalysisService; |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | @Autowired |
| | | private NotifyUtils notifyUtils; |
| | | @Autowired |
| | | private StationMoveCoordinator stationMoveCoordinator; |
| | | @Autowired(required = false) |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | @Autowired |
| | | private StationDispatchLoadSupport stationDispatchLoadSupport; |
| | | @Autowired |
| | | private StationOutboundDecisionSupport stationOutboundDecisionSupport; |
| | | |
| | | public void crnStationOutExecute() { |
| | | try { |
| | | DispatchLimitConfig baseLimitConfig = |
| | | stationDispatchLoadSupport.getDispatchLimitConfig(null, null); |
| | | int[] currentStationTaskCountRef = new int[]{stationDispatchLoadSupport.countCurrentStationTask()}; |
| | | LoadGuardState loadGuardState = |
| | | stationDispatchLoadSupport.buildLoadGuardState(baseLimitConfig); |
| | | |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts) |
| | | .isNotNull("crn_no")); |
| | | List<Integer> outOrderList = stationOutboundDecisionSupport.getAllOutOrderList(); |
| | | |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Object infoObj = redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | if (infoObj == null) { |
| | | News.info("出库任务{}数据缓存不存在", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class); |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId()); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId()); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() == 0) { |
| | | Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast); |
| | | OutOrderDispatchDecision dispatchDecision = |
| | | stationOutboundDecisionSupport.resolveOutboundDispatchDecision( |
| | | stationProtocol.getStationId(), |
| | | wrkMast, |
| | | outOrderList, |
| | | pathLenFactor |
| | | ); |
| | | Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId(); |
| | | if (moveStaNo == null) { |
| | | continue; |
| | | } |
| | | |
| | | DispatchLimitConfig limitConfig = |
| | | stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo); |
| | | LoopHitResult loopHitResult = |
| | | stationDispatchLoadSupport.findPathLoopHit( |
| | | limitConfig, |
| | | stationProtocol.getStationId(), |
| | | moveStaNo, |
| | | loadGuardState, |
| | | wrkMast, |
| | | pathLenFactor |
| | | ); |
| | | if (stationDispatchLoadSupport.isDispatchBlocked( |
| | | limitConfig, |
| | | currentStationTaskCountRef[0], |
| | | loadGuardState, |
| | | loopHitResult.isThroughLoop())) { |
| | | return; |
| | | } |
| | | |
| | | StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand( |
| | | stationThread, |
| | | wrkMast, |
| | | stationProtocol.getStationId(), |
| | | moveStaNo, |
| | | pathLenFactor |
| | | ); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); |
| | | continue; |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); |
| | | wrkMast.setSystemMsg(""); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setModiTime(now); |
| | | if (wrkMastService.updateById(wrkMast)) { |
| | | wrkAnalysisService.markOutboundStationStart(wrkMast, now); |
| | | boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute"); |
| | | if (offered && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "crnStationOutExecute", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | News.info("输送站点出库命令下发成功,站点号={},工作号={},命令数据={}", |
| | | stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command)); |
| | | redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5); |
| | | redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | currentStationTaskCountRef[0]++; |
| | | loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); |
| | | stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | public void dualCrnStationOutExecute() { |
| | | try { |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>() |
| | | .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts) |
| | | .isNotNull("dual_crn_no")); |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Object infoObj = redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | if (infoObj == null) { |
| | | News.info("出库任务{}数据缓存不存在", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class); |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId()); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId()); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() == 0) { |
| | | Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast); |
| | | StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand( |
| | | stationThread, |
| | | wrkMast, |
| | | stationProtocol.getStationId(), |
| | | wrkMast.getStaNo(), |
| | | pathLenFactor |
| | | ); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); |
| | | continue; |
| | | } |
| | | |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); |
| | | wrkMast.setSystemMsg(""); |
| | | wrkMast.setIoTime(new Date()); |
| | | if (wrkMastService.updateById(wrkMast)) { |
| | | boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute"); |
| | | if (offered && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "dualCrnStationOutExecute", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(), |
| | | String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), |
| | | NotifyMsgType.STATION_OUT_TASK_RUN, null); |
| | | News.info("输送站点出库命令下发成功,站点号={},工作号={},命令数据={}", |
| | | stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command)); |
| | | redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5); |
| | | redisUtil.del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo()); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) { |
| | | StationCommandDispatchResult dispatchResult = stationCommandDispatcher |
| | | .dispatch(deviceNo, command, "station-operate-process", scene); |
| | | return dispatchResult.isAccepted(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.zy.asrs.domain.enums.NotifyMsgType; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.BasStation; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.BasStationService; |
| | | import com.zy.asrs.service.WrkAnalysisService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.asrs.utils.NotifyUtils; |
| | | import com.zy.common.entity.FindCrnNoResult; |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.enums.WrkStsType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.model.DispatchLimitConfig; |
| | | import com.zy.core.utils.station.model.LoadGuardState; |
| | | import com.zy.core.utils.station.model.LoopHitResult; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | @Component |
| | | public class StationRegularDispatchProcessor { |
| | | |
| | | @Autowired |
| | | private BasDevpService basDevpService; |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private CommonService commonService; |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | @Autowired |
| | | private WrkAnalysisService wrkAnalysisService; |
| | | @Autowired |
| | | private BasStationService basStationService; |
| | | @Autowired |
| | | private NotifyUtils notifyUtils; |
| | | @Autowired |
| | | private StationMoveCoordinator stationMoveCoordinator; |
| | | @Autowired(required = false) |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | @Autowired |
| | | private StationDispatchLoadSupport stationDispatchLoadSupport; |
| | | |
| | | public void stationInExecute() { |
| | | try { |
| | | DispatchLimitConfig baseLimitConfig = stationDispatchLoadSupport.getDispatchLimitConfig(null, null); |
| | | int[] currentStationTaskCountRef = new int[]{stationDispatchLoadSupport.countCurrentStationTask()}; |
| | | LoadGuardState loadGuardState = stationDispatchLoadSupport.buildLoadGuardState(baseLimitConfig); |
| | | |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap(); |
| | | List<StationObjModel> stationList = basDevp.getBarcodeStationList$(); |
| | | for (StationObjModel entity : stationList) { |
| | | Integer stationId = entity.getStationId(); |
| | | if (!stationMap.containsKey(stationId)) { |
| | | continue; |
| | | } |
| | | |
| | | StationProtocol stationProtocol = stationMap.get(stationId); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | if (!stationProtocol.isAutoing() |
| | | || !stationProtocol.isLoading() |
| | | || stationProtocol.getTaskNo() <= 0) { |
| | | continue; |
| | | } |
| | | |
| | | WrkMast wrkMast = wrkMastService.getOne(new QueryWrapper<WrkMast>().eq("barcode", stationProtocol.getBarcode())); |
| | | if (wrkMast == null || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.NEW_INBOUND.sts)) { |
| | | continue; |
| | | } |
| | | |
| | | String locNo = wrkMast.getLocNo(); |
| | | FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo); |
| | | if (findCrnNoResult == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,未匹配到堆垛机", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId); |
| | | if (targetStationId == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}站点,搜索入库站点失败", stationId); |
| | | continue; |
| | | } |
| | | |
| | | DispatchLimitConfig limitConfig = stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), targetStationId); |
| | | LoopHitResult loopHitResult = stationDispatchLoadSupport.findPathLoopHit( |
| | | limitConfig, |
| | | stationProtocol.getStationId(), |
| | | targetStationId, |
| | | loadGuardState |
| | | ); |
| | | if (stationDispatchLoadSupport.isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) { |
| | | return; |
| | | } |
| | | |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,获取输送线命令失败", wrkMast.getWrkNo()); |
| | | continue; |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | wrkMast.setWrkSts(WrkStsType.INBOUND_STATION_RUN.sts); |
| | | wrkMast.setSourceStaNo(stationProtocol.getStationId()); |
| | | wrkMast.setStaNo(targetStationId); |
| | | wrkMast.setSystemMsg(""); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setModiTime(now); |
| | | if (wrkMastService.updateById(wrkMast)) { |
| | | wrkAnalysisService.markInboundStationStart(wrkMast, now); |
| | | boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "stationInExecute"); |
| | | if (offered && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "stationInExecute", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | News.info("输送站点入库命令下发成功,站点号={},工作号={},命令数据={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command)); |
| | | redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5); |
| | | loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); |
| | | stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | public void stationOutExecuteFinish() { |
| | | try { |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN.sts)); |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Integer wrkNo = wrkMast.getWrkNo(); |
| | | Integer targetStaNo = wrkMast.getStaNo(); |
| | | if (wrkNo == null || targetStaNo == null) { |
| | | continue; |
| | | } |
| | | |
| | | boolean complete = false; |
| | | Integer targetDeviceNo = null; |
| | | StationThread stationThread = null; |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo)); |
| | | if (basStation != null) { |
| | | targetDeviceNo = basStation.getDeviceNo(); |
| | | stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread != null) { |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = statusMap.get(basStation.getStationId()); |
| | | if (stationProtocol != null && wrkNo.equals(stationProtocol.getTaskNo())) { |
| | | complete = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (complete) { |
| | | attemptClearTaskPath(stationThread, wrkNo); |
| | | completeStationRunTask(wrkMast, targetDeviceNo); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | public void checkTaskToComplete() { |
| | | try { |
| | | List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts)); |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | Integer wrkNo = wrkMast.getWrkNo(); |
| | | Integer targetStaNo = wrkMast.getStaNo(); |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkNo); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | |
| | | BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo)); |
| | | if (basStation == null) { |
| | | continue; |
| | | } |
| | | |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | StationProtocol stationProtocol = statusMap.get(basStation.getStationId()); |
| | | if (stationProtocol == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (!Objects.equals(stationProtocol.getTaskNo(), wrkNo)) { |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.finishSession(wrkNo); |
| | | } |
| | | wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts); |
| | | wrkMast.setIoTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | private void attemptClearTaskPath(StationThread stationThread, Integer taskNo) { |
| | | if (stationThread == null || taskNo == null || taskNo <= 0) { |
| | | return; |
| | | } |
| | | try { |
| | | boolean cleared = stationThread.clearPath(taskNo); |
| | | if (cleared) { |
| | | News.info("输送站点任务运行完成后清理残留路径,工作号={}", taskNo); |
| | | } |
| | | } catch (Exception e) { |
| | | News.error("输送站点任务运行完成后清理残留路径异常,工作号={}", taskNo, e); |
| | | } |
| | | } |
| | | |
| | | private void completeStationRunTask(WrkMast wrkMast, Integer deviceNo) { |
| | | if (wrkMast == null || wrkMast.getWrkNo() == null) { |
| | | return; |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.finishSession(wrkMast.getWrkNo()); |
| | | } |
| | | Date now = new Date(); |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setModiTime(now); |
| | | wrkMastService.updateById(wrkMast); |
| | | wrkAnalysisService.markOutboundStationComplete(wrkMast, now); |
| | | if (deviceNo != null) { |
| | | notifyUtils.notify(String.valueOf(SlaveType.Devp), deviceNo, String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null); |
| | | } |
| | | redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60); |
| | | } |
| | | |
| | | private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) { |
| | | StationCommandDispatchResult dispatchResult = stationCommandDispatcher.dispatch(deviceNo, command, "station-operate-process", scene); |
| | | return dispatchResult.isAccepted(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.core.common.Cools; |
| | | import com.core.exception.CoolException; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.LocMast; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.LocMastService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.common.entity.FindCrnNoResult; |
| | | import com.zy.common.model.StartupDto; |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.enums.WrkIoType; |
| | | import com.zy.core.enums.WrkStsType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.move.StationMoveSession; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.model.OutOrderDispatchDecision; |
| | | import com.zy.core.utils.station.model.RerouteCommandPlan; |
| | | import com.zy.core.utils.station.model.RerouteContext; |
| | | import com.zy.core.utils.station.model.RerouteDecision; |
| | | import com.zy.core.utils.station.model.RerouteExecutionResult; |
| | | import com.zy.core.utils.station.model.RerouteSceneType; |
| | | import com.zy.core.utils.WmsOperateUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | @Component |
| | | public class StationRerouteProcessor { |
| | | private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2; |
| | | private static final int STATION_IDLE_RECOVER_SECONDS = 10; |
| | | private static final int STATION_IDLE_RECOVER_LIMIT_SECONDS = 30; |
| | | private static final long STATION_MOVE_RESET_WAIT_MS = 1000L; |
| | | |
| | | @Autowired |
| | | private BasDevpService basDevpService; |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private CommonService commonService; |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | @Autowired |
| | | private LocMastService locMastService; |
| | | @Autowired |
| | | private WmsOperateUtils wmsOperateUtils; |
| | | @Autowired |
| | | private StationMoveCoordinator stationMoveCoordinator; |
| | | @Autowired |
| | | private StationCommandDispatcher stationCommandDispatcher; |
| | | @Autowired |
| | | private StationOutboundDecisionSupport stationOutboundDecisionSupport; |
| | | @Autowired |
| | | private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport; |
| | | |
| | | public void checkStationRunBlock() { |
| | | try { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | List<Integer> runBlockReassignLocStationList = new ArrayList<>(); |
| | | for (StationObjModel stationObjModel : basDevp.getRunBlockReassignLocStationList$()) { |
| | | runBlockReassignLocStationList.add(stationObjModel.getStationId()); |
| | | } |
| | | List<Integer> outOrderStationIds = basDevp.getOutOrderIntList(); |
| | | |
| | | for (StationProtocol stationProtocol : stationThread.getStatus()) { |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() > 0 |
| | | && stationProtocol.isRunBlock()) { |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (wrkMast == null) { |
| | | News.info("输送站点号={} 运行阻塞,但无法找到对应任务,工作号={}", stationProtocol.getStationId(), stationProtocol.getTaskNo()); |
| | | continue; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo()); |
| | | if (lock != null) { |
| | | continue; |
| | | } |
| | | redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15); |
| | | |
| | | if (shouldUseRunBlockDirectReassign(wrkMast, stationProtocol.getStationId(), runBlockReassignLocStationList)) { |
| | | executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast); |
| | | continue; |
| | | } |
| | | |
| | | Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast); |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderStationIds, |
| | | pathLenFactor, |
| | | "checkStationRunBlock_reroute" |
| | | ).withRunBlockCommand() |
| | | .withSuppressDispatchGuard() |
| | | .withCancelSessionBeforeDispatch() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | executeSharedReroute(context); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | public void checkStationIdleRecover() { |
| | | try { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | for (StationProtocol stationProtocol : stationThread.getStatus()) { |
| | | if (stationProtocol.isAutoing() |
| | | && stationProtocol.isLoading() |
| | | && stationProtocol.getTaskNo() > 0 |
| | | && !stationProtocol.isRunBlock()) { |
| | | checkStationIdleRecover(basDevp, stationThread, stationProtocol, basDevp.getOutOrderIntList()); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | public void checkStationOutOrder() { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | List<StationObjModel> orderList = basDevp.getOutOrderList$(); |
| | | List<Integer> outOrderStationIds = basDevp.getOutOrderIntList(); |
| | | for (StationObjModel stationObjModel : orderList) { |
| | | StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId()); |
| | | if (stationProtocol == null |
| | | || !stationProtocol.isAutoing() |
| | | || !stationProtocol.isLoading() |
| | | || stationProtocol.getTaskNo() <= 0 |
| | | || stationProtocol.isRunBlock() |
| | | || !stationProtocol.getStationId().equals(stationProtocol.getTargetStaNo())) { |
| | | continue; |
| | | } |
| | | |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (wrkMast == null |
| | | || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts) |
| | | || Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) { |
| | | continue; |
| | | } |
| | | if (stationOutboundDecisionSupport.shouldSkipOutOrderDispatchForExistingRoute(wrkMast.getWrkNo(), stationProtocol.getStationId())) { |
| | | continue; |
| | | } |
| | | |
| | | Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast); |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.OUT_ORDER, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderStationIds, |
| | | pathLenFactor, |
| | | "checkStationOutOrder" |
| | | ).withDispatchDeviceNo(stationObjModel.getDeviceNo()) |
| | | .withSuppressDispatchGuard() |
| | | .withOutOrderDispatchLock() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | executeSharedReroute(context); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public void watchCircleStation() { |
| | | List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>()); |
| | | for (BasDevp basDevp : basDevps) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | |
| | | List<Integer> outOrderList = basDevp.getOutOrderIntList(); |
| | | for (StationProtocol stationProtocol : stationThread.getStatus()) { |
| | | if (!stationProtocol.isAutoing() |
| | | || !stationProtocol.isLoading() |
| | | || stationProtocol.getTaskNo() <= 0 |
| | | || !stationOutboundDecisionSupport.isWatchingCircleArrival(stationProtocol.getTaskNo(), stationProtocol.getStationId())) { |
| | | continue; |
| | | } |
| | | |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (wrkMast == null |
| | | || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts) |
| | | || Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) { |
| | | continue; |
| | | } |
| | | |
| | | Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast); |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.WATCH_CIRCLE, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderList, |
| | | pathLenFactor, |
| | | "watchCircleStation" |
| | | ).withSuppressDispatchGuard() |
| | | .withOutOrderDispatchLock() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | executeSharedReroute(context); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public RerouteCommandPlan buildRerouteCommandPlan(RerouteContext context, |
| | | RerouteDecision decision) { |
| | | if (context == null) { |
| | | return RerouteCommandPlan.skip("missing-context"); |
| | | } |
| | | if (decision == null) { |
| | | return RerouteCommandPlan.skip("missing-decision"); |
| | | } |
| | | if (decision.skip()) { |
| | | return RerouteCommandPlan.skip(decision.skipReason()); |
| | | } |
| | | if (context.stationThread() == null || context.stationProtocol() == null || context.wrkMast() == null) { |
| | | return RerouteCommandPlan.skip("missing-runtime-dependency"); |
| | | } |
| | | Integer currentStationId = context.stationProtocol().getStationId(); |
| | | Integer targetStationId = decision.targetStationId(); |
| | | if (currentStationId == null || targetStationId == null) { |
| | | return RerouteCommandPlan.skip("missing-target-station"); |
| | | } |
| | | if (Objects.equals(currentStationId, targetStationId)) { |
| | | return RerouteCommandPlan.skip("same-station"); |
| | | } |
| | | |
| | | StationCommand command = context.useRunBlockCommand() |
| | | ? context.stationThread().getRunBlockRerouteCommand( |
| | | context.wrkMast().getWrkNo(), |
| | | currentStationId, |
| | | targetStationId, |
| | | 0, |
| | | context.pathLenFactor() |
| | | ) |
| | | : stationOutboundDecisionSupport.buildOutboundMoveCommand( |
| | | context.stationThread(), |
| | | context.wrkMast(), |
| | | currentStationId, |
| | | targetStationId, |
| | | context.pathLenFactor() |
| | | ); |
| | | if (command == null) { |
| | | if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) { |
| | | News.taskInfo(context.wrkMast().getWrkNo(), |
| | | "输送站点堵塞重规划未找到可下发路线,当前站点={},目标站点={}", |
| | | currentStationId, |
| | | targetStationId); |
| | | } else if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { |
| | | News.taskInfo(context.wrkMast().getWrkNo(), |
| | | "站点任务停留超时后重算路径失败,当前站点={},目标站点={}", |
| | | currentStationId, |
| | | targetStationId); |
| | | } else { |
| | | News.taskInfo(context.wrkMast().getWrkNo(), "获取输送线命令失败"); |
| | | } |
| | | return RerouteCommandPlan.skip("missing-command"); |
| | | } |
| | | return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene()); |
| | | } |
| | | |
| | | public RerouteExecutionResult executeReroutePlan(RerouteContext context, |
| | | RerouteCommandPlan plan) { |
| | | if (context == null) { |
| | | return RerouteExecutionResult.skip("missing-context"); |
| | | } |
| | | if (plan == null) { |
| | | return RerouteExecutionResult.skip("missing-plan"); |
| | | } |
| | | if (plan.skip()) { |
| | | return RerouteExecutionResult.skip(plan.skipReason()); |
| | | } |
| | | StationProtocol stationProtocol = context.stationProtocol(); |
| | | if (stationProtocol == null) { |
| | | return RerouteExecutionResult.skip("missing-station-protocol"); |
| | | } |
| | | Integer taskNo = stationProtocol.getTaskNo(); |
| | | Integer stationId = stationProtocol.getStationId(); |
| | | if (taskNo == null || taskNo <= 0 || stationId == null) { |
| | | return RerouteExecutionResult.skip("invalid-station-task"); |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | return stationMoveCoordinator.withTaskDispatchLock(taskNo, |
| | | () -> executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId)); |
| | | } |
| | | return executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId); |
| | | } |
| | | |
| | | public RerouteDecision resolveSharedRerouteDecision(RerouteContext context) { |
| | | if (context == null || context.wrkMast() == null || context.stationProtocol() == null) { |
| | | return RerouteDecision.skip("missing-runtime-dependency"); |
| | | } |
| | | Integer currentStationId = context.stationProtocol().getStationId(); |
| | | if (currentStationId == null) { |
| | | return RerouteDecision.skip("missing-current-station"); |
| | | } |
| | | |
| | | if (context.sceneType() == RerouteSceneType.IDLE_RECOVER |
| | | && !Objects.equals(context.wrkMast().getWrkSts(), WrkStsType.STATION_RUN.sts)) { |
| | | Integer targetStationId = context.wrkMast().getStaNo(); |
| | | return targetStationId == null || Objects.equals(targetStationId, currentStationId) |
| | | ? RerouteDecision.skip("same-station") |
| | | : RerouteDecision.proceed(targetStationId); |
| | | } |
| | | |
| | | OutOrderDispatchDecision dispatchDecision = |
| | | stationOutboundDecisionSupport.resolveOutboundDispatchDecision( |
| | | currentStationId, |
| | | context.wrkMast(), |
| | | context.outOrderStationIds(), |
| | | context.pathLenFactor() |
| | | ); |
| | | Integer targetStationId = dispatchDecision == null ? null : dispatchDecision.getTargetStationId(); |
| | | if (targetStationId == null || Objects.equals(targetStationId, currentStationId)) { |
| | | return RerouteDecision.skip("same-station"); |
| | | } |
| | | return RerouteDecision.proceed(targetStationId, dispatchDecision); |
| | | } |
| | | |
| | | public boolean shouldUseRunBlockDirectReassign(WrkMast wrkMast, |
| | | Integer stationId, |
| | | List<Integer> runBlockReassignLocStationList) { |
| | | return wrkMast != null |
| | | && Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id) |
| | | && stationId != null |
| | | && runBlockReassignLocStationList != null |
| | | && runBlockReassignLocStationList.contains(stationId); |
| | | } |
| | | |
| | | public boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) { |
| | | if (taskNo == null || taskNo <= 0 || stationId == null) { |
| | | return false; |
| | | } |
| | | long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L; |
| | | StationMoveSession session = stationMoveCoordinator == null ? null : stationMoveCoordinator.loadSession(taskNo); |
| | | if (session != null && session.isActive() && session.getLastIssuedAt() != null) { |
| | | if (Objects.equals(stationId, session.getCurrentStationId()) |
| | | || Objects.equals(stationId, session.getDispatchStationId()) |
| | | || session.containsStation(stationId)) { |
| | | long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt(); |
| | | if (elapsedMs < thresholdMs) { |
| | | stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis())); |
| | | News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距上次下发={}ms,routeVersion={}", |
| | | stationId, taskNo, elapsedMs, session.getRouteVersion()); |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | if (!stationDispatchRuntimeStateSupport.hasRecentIssuedMoveCommand(taskNo, stationId, thresholdMs)) { |
| | | return false; |
| | | } |
| | | stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis())); |
| | | News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距最近命令下发<{}ms,routeVersion={}", |
| | | stationId, taskNo, thresholdMs, session == null ? null : session.getRouteVersion()); |
| | | return true; |
| | | } |
| | | |
| | | private RerouteExecutionResult executeReroutePlanWithTaskLock(RerouteContext context, |
| | | RerouteCommandPlan plan, |
| | | StationProtocol stationProtocol, |
| | | Integer taskNo, |
| | | Integer stationId) { |
| | | boolean runBlockReroute = context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE; |
| | | if (context.checkRecentDispatch() |
| | | && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) { |
| | | return RerouteExecutionResult.skip("recent-dispatch"); |
| | | } |
| | | int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo); |
| | | if (currentTaskBufferCommandCount > 0 && !runBlockReroute) { |
| | | if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { |
| | | News.info("输送站点任务停留超时,但缓存区仍存在当前任务命令,已跳过重算。站点号={},工作号={},当前任务命令数={}", |
| | | stationId, |
| | | taskNo, |
| | | currentTaskBufferCommandCount); |
| | | } |
| | | return RerouteExecutionResult.skip("buffer-has-current-task"); |
| | | } |
| | | if (currentTaskBufferCommandCount > 0 && runBlockReroute) { |
| | | News.info("输送站点运行堵塞重规划检测到旧分段命令残留,已先清理本地状态后继续重发。站点号={},工作号={},当前任务命令数={}", |
| | | stationId, |
| | | taskNo, |
| | | currentTaskBufferCommandCount); |
| | | } |
| | | if (!runBlockReroute |
| | | && context.checkSuppressDispatch() |
| | | && stationMoveCoordinator != null |
| | | && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) { |
| | | return RerouteExecutionResult.skip("dispatch-suppressed"); |
| | | } |
| | | if (context.requireOutOrderDispatchLock() |
| | | && !stationDispatchRuntimeStateSupport.tryAcquireOutOrderDispatchLock(taskNo, stationId, OUT_ORDER_DISPATCH_LIMIT_SECONDS)) { |
| | | return RerouteExecutionResult.skip("out-order-lock"); |
| | | } |
| | | |
| | | if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.markCancelPending(taskNo, "reroute_pending"); |
| | | } |
| | | |
| | | if (runBlockReroute) { |
| | | if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.cancelSession(taskNo); |
| | | } |
| | | if (context.resetSegmentCommandsBeforeDispatch()) { |
| | | stationDispatchRuntimeStateSupport.signalSegmentReset(taskNo, STATION_MOVE_RESET_WAIT_MS); |
| | | } |
| | | } |
| | | |
| | | if (!runBlockReroute |
| | | && context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.cancelSession(taskNo); |
| | | } |
| | | if (!isBlank(context.executionLockKey()) |
| | | && !stationDispatchRuntimeStateSupport.tryAcquireLock(context.executionLockKey(), context.executionLockSeconds())) { |
| | | return RerouteExecutionResult.skip("scene-lock"); |
| | | } |
| | | if (!runBlockReroute && context.resetSegmentCommandsBeforeDispatch()) { |
| | | stationDispatchRuntimeStateSupport.signalSegmentReset(taskNo, STATION_MOVE_RESET_WAIT_MS); |
| | | } |
| | | |
| | | int clearedCommandCount = 0; |
| | | if (context.clearIdleIssuedCommands()) { |
| | | clearedCommandCount = stationDispatchRuntimeStateSupport.clearIssuedMoveCommandsDuringIdleStay(context.idleTrack(), taskNo, stationId); |
| | | } |
| | | |
| | | boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene()); |
| | | if (!offered) { |
| | | return RerouteExecutionResult.skip("dispatch-dedup"); |
| | | } |
| | | |
| | | applyRerouteDispatchEffects(context, plan, clearedCommandCount); |
| | | return RerouteExecutionResult.dispatched(plan.command(), clearedCommandCount); |
| | | } |
| | | |
| | | private RerouteExecutionResult executeSharedReroute(RerouteContext context) { |
| | | RerouteDecision decision = resolveSharedRerouteDecision(context); |
| | | if (decision.skip()) { |
| | | return RerouteExecutionResult.skip(decision.skipReason()); |
| | | } |
| | | RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision); |
| | | return executeReroutePlan(context, plan); |
| | | } |
| | | |
| | | private void applyRerouteDispatchEffects(RerouteContext context, |
| | | RerouteCommandPlan plan, |
| | | int clearedCommandCount) { |
| | | if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) { |
| | | return; |
| | | } |
| | | WrkMast wrkMast = context.wrkMast(); |
| | | StationProtocol stationProtocol = context.stationProtocol(); |
| | | OutOrderDispatchDecision dispatchDecision = |
| | | plan.decision() == null ? null : plan.decision().dispatchDecision(); |
| | | |
| | | stationOutboundDecisionSupport.syncOutOrderWatchState( |
| | | wrkMast, |
| | | stationProtocol.getStationId(), |
| | | context.outOrderStationIds(), |
| | | dispatchDecision, |
| | | plan.command() |
| | | ); |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | plan.dispatchScene(), |
| | | plan.command(), |
| | | dispatchDecision != null && dispatchDecision.isCircle() |
| | | ); |
| | | } |
| | | if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { |
| | | stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis())); |
| | | News.info("输送站点任务停留{}秒未运行,已重新计算路径并重启运行,站点号={},目标站={},工作号={},清理旧分段命令数={},命令数据={}", |
| | | STATION_IDLE_RECOVER_SECONDS, |
| | | stationProtocol.getStationId(), |
| | | plan.command().getTargetStaNo(), |
| | | wrkMast.getWrkNo(), |
| | | clearedCommandCount, |
| | | JSON.toJSONString(plan.command())); |
| | | return; |
| | | } |
| | | if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) { |
| | | News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}", |
| | | stationProtocol.getStationId(), |
| | | wrkMast.getWrkNo(), |
| | | JSON.toJSONString(plan.command())); |
| | | return; |
| | | } |
| | | if (context.sceneType() == RerouteSceneType.OUT_ORDER) { |
| | | News.info(dispatchDecision != null && dispatchDecision.isCircle() ? "{}任务进行绕圈" : "{}任务直接去目标点", wrkMast.getWrkNo()); |
| | | } |
| | | } |
| | | |
| | | private void checkStationIdleRecover(BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | List<Integer> outOrderList) { |
| | | if (stationProtocol == null || stationProtocol.getTaskNo() == null || stationProtocol.getTaskNo() <= 0) { |
| | | return; |
| | | } |
| | | if (!Objects.equals(stationProtocol.getStationId(), stationProtocol.getTargetStaNo())) { |
| | | return; |
| | | } |
| | | |
| | | StationTaskIdleTrack idleTrack = stationDispatchRuntimeStateSupport.touchIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId()); |
| | | if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) { |
| | | return; |
| | | } |
| | | if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) { |
| | | return; |
| | | } |
| | | |
| | | WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); |
| | | if (!canRecoverIdleStationTask(wrkMast, stationProtocol.getStationId())) { |
| | | return; |
| | | } |
| | | |
| | | Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo()); |
| | | if (lock != null) { |
| | | return; |
| | | } |
| | | Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast); |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.IDLE_RECOVER, |
| | | basDevp, |
| | | stationThread, |
| | | stationProtocol, |
| | | wrkMast, |
| | | outOrderList, |
| | | pathLenFactor, |
| | | "checkStationIdleRecover" |
| | | ).withCancelSessionBeforeDispatch() |
| | | .withExecutionLock(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), STATION_IDLE_RECOVER_LIMIT_SECONDS) |
| | | .withResetSegmentCommandsBeforeDispatch() |
| | | .clearIdleIssuedCommands(idleTrack); |
| | | executeSharedReroute(context); |
| | | } |
| | | |
| | | private void executeRunBlockDirectReassign(BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | WrkMast wrkMast) { |
| | | if (basDevp == null || stationThread == null || stationProtocol == null || wrkMast == null) { |
| | | return; |
| | | } |
| | | int currentTaskBufferCommandCount = countCurrentTaskBufferCommands( |
| | | stationProtocol.getTaskBufferItems(), |
| | | stationProtocol.getTaskNo() |
| | | ); |
| | | if (currentTaskBufferCommandCount > 0) { |
| | | News.info("输送站点运行堵塞重分配已跳过,缓存区仍存在当前任务命令。站点号={},工作号={},当前任务命令数={}", |
| | | stationProtocol.getStationId(), |
| | | stationProtocol.getTaskNo(), |
| | | currentTaskBufferCommandCount); |
| | | return; |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.cancelSession(wrkMast.getWrkNo()); |
| | | } |
| | | String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId()); |
| | | if (Cools.isEmpty(response)) { |
| | | News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,接口未响应!!!response:{}", response); |
| | | return; |
| | | } |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (!jsonObject.getInteger("code").equals(200)) { |
| | | News.error("请求WMS接口失败!!!response:{}", response); |
| | | return; |
| | | } |
| | | |
| | | StartupDto dto = jsonObject.getObject("data", StartupDto.class); |
| | | String sourceLocNo = wrkMast.getLocNo(); |
| | | String locNo = dto.getLocNo(); |
| | | |
| | | LocMast sourceLocMast = locMastService.queryByLoc(sourceLocNo); |
| | | if (sourceLocMast == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 源库位信息不存在", sourceLocNo); |
| | | return; |
| | | } |
| | | if (!sourceLocMast.getLocSts().equals("S")) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 源库位状态不处于入库预约", sourceLocNo); |
| | | return; |
| | | } |
| | | |
| | | LocMast locMast = locMastService.queryByLoc(locNo); |
| | | if (locMast == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 目标库位信息不存在", locNo); |
| | | return; |
| | | } |
| | | if (!locMast.getLocSts().equals("O")) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 目标库位状态不处于空库位", locNo); |
| | | return; |
| | | } |
| | | |
| | | FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo); |
| | | if (findCrnNoResult == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,未匹配到堆垛机", wrkMast.getWrkNo()); |
| | | return; |
| | | } |
| | | Integer crnNo = findCrnNoResult.getCrnNo(); |
| | | |
| | | Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId()); |
| | | if (targetStationId == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}站点,搜索入库站点失败", stationProtocol.getStationId()); |
| | | return; |
| | | } |
| | | |
| | | StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), targetStationId, 0); |
| | | if (command == null) { |
| | | News.taskInfo(wrkMast.getWrkNo(), "{}工作,获取输送线命令失败", wrkMast.getWrkNo()); |
| | | return; |
| | | } |
| | | |
| | | sourceLocMast.setLocSts("O"); |
| | | sourceLocMast.setModiTime(new Date()); |
| | | locMastService.updateById(sourceLocMast); |
| | | |
| | | locMast.setLocSts("S"); |
| | | locMast.setModiTime(new Date()); |
| | | locMastService.updateById(locMast); |
| | | |
| | | wrkMast.setLocNo(locNo); |
| | | wrkMast.setStaNo(targetStationId); |
| | | |
| | | if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) { |
| | | wrkMast.setCrnNo(crnNo); |
| | | } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) { |
| | | wrkMast.setDualCrnNo(crnNo); |
| | | } else { |
| | | throw new CoolException("未知设备类型"); |
| | | } |
| | | |
| | | if (!wrkMastService.updateById(wrkMast)) { |
| | | return; |
| | | } |
| | | boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct"); |
| | | if (!offered) { |
| | | return; |
| | | } |
| | | if (stationMoveCoordinator != null) { |
| | | stationMoveCoordinator.recordDispatch( |
| | | wrkMast.getWrkNo(), |
| | | stationProtocol.getStationId(), |
| | | "checkStationRunBlock_direct", |
| | | command, |
| | | false |
| | | ); |
| | | } |
| | | } |
| | | |
| | | private boolean canRecoverIdleStationTask(WrkMast wrkMast, Integer currentStationId) { |
| | | if (wrkMast == null || currentStationId == null || wrkMast.getStaNo() == null) { |
| | | return false; |
| | | } |
| | | if (Objects.equals(currentStationId, wrkMast.getStaNo())) { |
| | | return false; |
| | | } |
| | | return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts) |
| | | || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts); |
| | | } |
| | | |
| | | private int countCurrentTaskBufferCommands(List<StationTaskBufferItem> taskBufferItems, Integer currentTaskNo) { |
| | | if (taskBufferItems == null || taskBufferItems.isEmpty() || currentTaskNo == null || currentTaskNo <= 0) { |
| | | return 0; |
| | | } |
| | | int count = 0; |
| | | for (StationTaskBufferItem item : taskBufferItems) { |
| | | if (item == null || item.getTaskNo() == null) { |
| | | continue; |
| | | } |
| | | if (currentTaskNo.equals(item.getTaskNo())) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) { |
| | | StationCommandDispatchResult dispatchResult = stationCommandDispatcher |
| | | .dispatch(deviceNo, command, "station-operate-process", scene); |
| | | return dispatchResult.isAccepted(); |
| | | } |
| | | |
| | | private boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | public class StationTaskIdleTrack { |
| | | private Integer taskNo; |
| | | private Integer stationId; |
| | | private Long firstSeenTime; |
| | | |
| | | public StationTaskIdleTrack() { |
| | | } |
| | | |
| | | public StationTaskIdleTrack(Integer taskNo, Integer stationId, Long firstSeenTime) { |
| | | this.taskNo = taskNo; |
| | | this.stationId = stationId; |
| | | this.firstSeenTime = firstSeenTime; |
| | | } |
| | | |
| | | public boolean isTimeout(int seconds) { |
| | | if (firstSeenTime == null) { |
| | | return false; |
| | | } |
| | | return System.currentTimeMillis() - firstSeenTime >= seconds * 1000L; |
| | | } |
| | | |
| | | public Integer getTaskNo() { |
| | | return taskNo; |
| | | } |
| | | |
| | | public void setTaskNo(Integer taskNo) { |
| | | this.taskNo = taskNo; |
| | | } |
| | | |
| | | public Integer getStationId() { |
| | | return stationId; |
| | | } |
| | | |
| | | public void setStationId(Integer stationId) { |
| | | this.stationId = stationId; |
| | | } |
| | | |
| | | public Long getFirstSeenTime() { |
| | | return firstSeenTime; |
| | | } |
| | | |
| | | public void setFirstSeenTime(Long firstSeenTime) { |
| | | this.firstSeenTime = firstSeenTime; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | public final class CircleTargetCandidate { |
| | | private final Integer stationId; |
| | | private final Integer pathLength; |
| | | private final Integer offset; |
| | | |
| | | public CircleTargetCandidate(Integer stationId, Integer pathLength, Integer offset) { |
| | | this.stationId = stationId; |
| | | this.pathLength = pathLength == null ? 0 : pathLength; |
| | | this.offset = offset == null ? 0 : offset; |
| | | } |
| | | |
| | | public Integer getStationId() { |
| | | return stationId; |
| | | } |
| | | |
| | | public Integer getPathLength() { |
| | | return pathLength; |
| | | } |
| | | |
| | | public Integer getOffset() { |
| | | return offset; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | |
| | | @Getter |
| | | @Setter |
| | | public class DispatchLimitConfig { |
| | | private double circleMaxLoadLimit = 0.8d; |
| | | private boolean loopModeEnable = false; |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | @Getter |
| | | public class LoadGuardState { |
| | | @Setter |
| | | private int totalStationCount = 0; |
| | | @Setter |
| | | private int projectedTaskStationCount = 0; |
| | | private final Map<Integer, Integer> stationLoopNoMap = new HashMap<>(); |
| | | |
| | | public double currentLoad() { |
| | | return calcLoad(this.projectedTaskStationCount, this.totalStationCount); |
| | | } |
| | | |
| | | public double loadAfterReserve() { |
| | | return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount); |
| | | } |
| | | |
| | | public void reserveLoopTask(Integer loopNo) { |
| | | if (loopNo == null || loopNo <= 0 || this.totalStationCount <= 0) { |
| | | return; |
| | | } |
| | | this.projectedTaskStationCount++; |
| | | } |
| | | |
| | | public void putStationLoopNo(Integer stationId, Integer loopNo) { |
| | | if (stationId == null || loopNo == null) { |
| | | return; |
| | | } |
| | | this.stationLoopNoMap.put(stationId, loopNo); |
| | | } |
| | | |
| | | private double calcLoad(int taskCount, int stationCount) { |
| | | if (stationCount <= 0 || taskCount <= 0) { |
| | | return 0.0; |
| | | } |
| | | double load = (double) taskCount / (double) stationCount; |
| | | if (load < 0.0) { |
| | | return 0.0; |
| | | } |
| | | if (load > 1.0) { |
| | | return 1.0; |
| | | } |
| | | return load; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import lombok.Getter; |
| | | |
| | | @Getter |
| | | public class LoopHitResult { |
| | | private final boolean throughLoop; |
| | | private final Integer loopNo; |
| | | private final Integer hitStationId; |
| | | |
| | | public LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) { |
| | | this.throughLoop = throughLoop; |
| | | this.loopNo = loopNo; |
| | | this.hitStationId = hitStationId; |
| | | } |
| | | |
| | | public static LoopHitResult noHit() { |
| | | return new LoopHitResult(false, null, null); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import com.zy.core.service.StationTaskLoopService; |
| | | |
| | | public final class OutOrderDispatchDecision { |
| | | private final Integer targetStationId; |
| | | private final boolean circle; |
| | | private final StationTaskLoopService.LoopEvaluation loopEvaluation; |
| | | private final boolean countLoopIssue; |
| | | |
| | | private OutOrderDispatchDecision(Integer targetStationId, |
| | | boolean circle, |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation, |
| | | boolean countLoopIssue) { |
| | | this.targetStationId = targetStationId; |
| | | this.circle = circle; |
| | | this.loopEvaluation = loopEvaluation; |
| | | this.countLoopIssue = countLoopIssue; |
| | | } |
| | | |
| | | public static OutOrderDispatchDecision direct(Integer targetStationId) { |
| | | return new OutOrderDispatchDecision(targetStationId, false, null, false); |
| | | } |
| | | |
| | | public static OutOrderDispatchDecision circle(Integer targetStationId, |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation, |
| | | boolean countLoopIssue) { |
| | | return new OutOrderDispatchDecision(targetStationId, true, loopEvaluation, countLoopIssue); |
| | | } |
| | | |
| | | public Integer getTargetStationId() { |
| | | return targetStationId; |
| | | } |
| | | |
| | | public boolean isCircle() { |
| | | return circle; |
| | | } |
| | | |
| | | public StationTaskLoopService.LoopEvaluation getLoopEvaluation() { |
| | | return loopEvaluation; |
| | | } |
| | | |
| | | public boolean shouldCountLoopIssue() { |
| | | return countLoopIssue; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import com.zy.core.model.command.StationCommand; |
| | | |
| | | public final class RerouteCommandPlan { |
| | | private final boolean skip; |
| | | private final String skipReason; |
| | | private final StationCommand command; |
| | | private final RerouteDecision decision; |
| | | private final String dispatchScene; |
| | | |
| | | private RerouteCommandPlan(boolean skip, |
| | | String skipReason, |
| | | StationCommand command, |
| | | RerouteDecision decision, |
| | | String dispatchScene) { |
| | | this.skip = skip; |
| | | this.skipReason = skipReason; |
| | | this.command = command; |
| | | this.decision = decision; |
| | | this.dispatchScene = dispatchScene; |
| | | } |
| | | |
| | | public static RerouteCommandPlan skip(String reason) { |
| | | return new RerouteCommandPlan(true, reason, null, null, null); |
| | | } |
| | | |
| | | public static RerouteCommandPlan dispatch(StationCommand command, |
| | | RerouteDecision decision, |
| | | String dispatchScene) { |
| | | return new RerouteCommandPlan(false, null, command, decision, dispatchScene); |
| | | } |
| | | |
| | | public boolean skip() { |
| | | return skip; |
| | | } |
| | | |
| | | public String skipReason() { |
| | | return skipReason; |
| | | } |
| | | |
| | | public StationCommand command() { |
| | | return command; |
| | | } |
| | | |
| | | public RerouteDecision decision() { |
| | | return decision; |
| | | } |
| | | |
| | | public String dispatchScene() { |
| | | return dispatchScene; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.StationTaskIdleTrack; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | public final class RerouteContext { |
| | | private final RerouteSceneType sceneType; |
| | | private final BasDevp basDevp; |
| | | private final StationThread stationThread; |
| | | private final StationProtocol stationProtocol; |
| | | private final WrkMast wrkMast; |
| | | private final List<Integer> outOrderStationIds; |
| | | private final Double pathLenFactor; |
| | | private final String dispatchScene; |
| | | private Integer dispatchDeviceNo; |
| | | private boolean useRunBlockCommand; |
| | | private boolean checkSuppressDispatch; |
| | | private boolean requireOutOrderDispatchLock; |
| | | private boolean cancelSessionBeforeDispatch; |
| | | private boolean resetSegmentCommandsBeforeDispatch; |
| | | private boolean clearIdleIssuedCommands; |
| | | private boolean checkRecentDispatch; |
| | | private String executionLockKey; |
| | | private int executionLockSeconds; |
| | | private StationTaskIdleTrack idleTrack; |
| | | |
| | | private RerouteContext(RerouteSceneType sceneType, |
| | | BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor, |
| | | String dispatchScene) { |
| | | this.sceneType = sceneType; |
| | | this.basDevp = basDevp; |
| | | this.stationThread = stationThread; |
| | | this.stationProtocol = stationProtocol; |
| | | this.wrkMast = wrkMast; |
| | | this.outOrderStationIds = outOrderStationIds == null ? Collections.emptyList() : outOrderStationIds; |
| | | this.pathLenFactor = pathLenFactor; |
| | | this.dispatchScene = dispatchScene; |
| | | this.dispatchDeviceNo = basDevp == null ? null : basDevp.getDevpNo(); |
| | | } |
| | | |
| | | public static RerouteContext create(RerouteSceneType sceneType, |
| | | BasDevp basDevp, |
| | | StationThread stationThread, |
| | | StationProtocol stationProtocol, |
| | | WrkMast wrkMast, |
| | | List<Integer> outOrderStationIds, |
| | | Double pathLenFactor, |
| | | String dispatchScene) { |
| | | return new RerouteContext(sceneType, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, dispatchScene); |
| | | } |
| | | |
| | | public RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) { |
| | | this.dispatchDeviceNo = dispatchDeviceNo; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext withRunBlockCommand() { |
| | | this.useRunBlockCommand = true; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext withSuppressDispatchGuard() { |
| | | this.checkSuppressDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext withOutOrderDispatchLock() { |
| | | this.requireOutOrderDispatchLock = true; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext withCancelSessionBeforeDispatch() { |
| | | this.cancelSessionBeforeDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext withResetSegmentCommandsBeforeDispatch() { |
| | | this.resetSegmentCommandsBeforeDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) { |
| | | this.clearIdleIssuedCommands = true; |
| | | this.idleTrack = idleTrack; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext withRecentDispatchGuard() { |
| | | this.checkRecentDispatch = true; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) { |
| | | this.executionLockKey = executionLockKey; |
| | | this.executionLockSeconds = executionLockSeconds; |
| | | return this; |
| | | } |
| | | |
| | | public RerouteSceneType sceneType() { |
| | | return sceneType; |
| | | } |
| | | |
| | | public BasDevp basDevp() { |
| | | return basDevp; |
| | | } |
| | | |
| | | public StationThread stationThread() { |
| | | return stationThread; |
| | | } |
| | | |
| | | public StationProtocol stationProtocol() { |
| | | return stationProtocol; |
| | | } |
| | | |
| | | public WrkMast wrkMast() { |
| | | return wrkMast; |
| | | } |
| | | |
| | | public List<Integer> outOrderStationIds() { |
| | | return outOrderStationIds; |
| | | } |
| | | |
| | | public Double pathLenFactor() { |
| | | return pathLenFactor; |
| | | } |
| | | |
| | | public String dispatchScene() { |
| | | return dispatchScene; |
| | | } |
| | | |
| | | public Integer dispatchDeviceNo() { |
| | | return dispatchDeviceNo; |
| | | } |
| | | |
| | | public boolean useRunBlockCommand() { |
| | | return useRunBlockCommand; |
| | | } |
| | | |
| | | public boolean checkSuppressDispatch() { |
| | | return checkSuppressDispatch; |
| | | } |
| | | |
| | | public boolean requireOutOrderDispatchLock() { |
| | | return requireOutOrderDispatchLock; |
| | | } |
| | | |
| | | public boolean cancelSessionBeforeDispatch() { |
| | | return cancelSessionBeforeDispatch; |
| | | } |
| | | |
| | | public boolean resetSegmentCommandsBeforeDispatch() { |
| | | return resetSegmentCommandsBeforeDispatch; |
| | | } |
| | | |
| | | public boolean clearIdleIssuedCommands() { |
| | | return clearIdleIssuedCommands; |
| | | } |
| | | |
| | | public boolean checkRecentDispatch() { |
| | | return checkRecentDispatch; |
| | | } |
| | | |
| | | public String executionLockKey() { |
| | | return executionLockKey; |
| | | } |
| | | |
| | | public int executionLockSeconds() { |
| | | return executionLockSeconds; |
| | | } |
| | | |
| | | public StationTaskIdleTrack idleTrack() { |
| | | return idleTrack; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | public final class RerouteDecision { |
| | | private final boolean skip; |
| | | private final String skipReason; |
| | | private final Integer targetStationId; |
| | | private final OutOrderDispatchDecision dispatchDecision; |
| | | |
| | | private RerouteDecision(boolean skip, |
| | | String skipReason, |
| | | Integer targetStationId, |
| | | OutOrderDispatchDecision dispatchDecision) { |
| | | this.skip = skip; |
| | | this.skipReason = skipReason; |
| | | this.targetStationId = targetStationId; |
| | | this.dispatchDecision = dispatchDecision; |
| | | } |
| | | |
| | | public static RerouteDecision skip(String reason) { |
| | | return new RerouteDecision(true, reason, null, null); |
| | | } |
| | | |
| | | public static RerouteDecision proceed(Integer targetStationId) { |
| | | return new RerouteDecision(false, null, targetStationId, null); |
| | | } |
| | | |
| | | public static RerouteDecision proceed(Integer targetStationId, |
| | | OutOrderDispatchDecision dispatchDecision) { |
| | | return new RerouteDecision(false, null, targetStationId, dispatchDecision); |
| | | } |
| | | |
| | | public boolean skip() { |
| | | return skip; |
| | | } |
| | | |
| | | public String skipReason() { |
| | | return skipReason; |
| | | } |
| | | |
| | | public Integer targetStationId() { |
| | | return targetStationId; |
| | | } |
| | | |
| | | public OutOrderDispatchDecision dispatchDecision() { |
| | | return dispatchDecision; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import com.zy.core.model.command.StationCommand; |
| | | |
| | | public final class RerouteExecutionResult { |
| | | private final boolean skipped; |
| | | private final String skipReason; |
| | | private final boolean dispatched; |
| | | private final StationCommand command; |
| | | private final int clearedCommandCount; |
| | | |
| | | private RerouteExecutionResult(boolean skipped, |
| | | String skipReason, |
| | | boolean dispatched, |
| | | StationCommand command, |
| | | int clearedCommandCount) { |
| | | this.skipped = skipped; |
| | | this.skipReason = skipReason; |
| | | this.dispatched = dispatched; |
| | | this.command = command; |
| | | this.clearedCommandCount = clearedCommandCount; |
| | | } |
| | | |
| | | public static RerouteExecutionResult skip(String reason) { |
| | | return new RerouteExecutionResult(true, reason, false, null, 0); |
| | | } |
| | | |
| | | public static RerouteExecutionResult dispatched(StationCommand command, |
| | | int clearedCommandCount) { |
| | | return new RerouteExecutionResult(false, null, true, command, clearedCommandCount); |
| | | } |
| | | |
| | | public boolean skipped() { |
| | | return skipped; |
| | | } |
| | | |
| | | public String skipReason() { |
| | | return skipReason; |
| | | } |
| | | |
| | | public boolean dispatched() { |
| | | return dispatched; |
| | | } |
| | | |
| | | public StationCommand command() { |
| | | return command; |
| | | } |
| | | |
| | | public int clearedCommandCount() { |
| | | return clearedCommandCount; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | public enum RerouteSceneType { |
| | | RUN_BLOCK_REROUTE, |
| | | IDLE_RECOVER, |
| | | OUT_ORDER, |
| | | WATCH_CIRCLE |
| | | } |
| | |
| | | import com.zy.asrs.domain.param.StationCommandMoveParam; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.thread.StationThread; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | |
| | | import java.util.Collections; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.ArgumentMatchers.same; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationControllerTest { |
| | | |
| | | @Test |
| | | void controller_noLongerKeepsDispatcherFallbackHelper() { |
| | | assertThrows(NoSuchMethodException.class, |
| | | () -> StationController.class.getDeclaredMethod("getStationCommandDispatcher")); |
| | | } |
| | | |
| | | @Test |
| | | void commandClearPath_callsThreadClearPath() { |
| | |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void commandMove_dispatchesViaStationCommandDispatcher() { |
| | | StationController controller = new StationController(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class); |
| | | StationCommand command = new StationCommand(); |
| | | |
| | | ReflectionTestUtils.setField(controller, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(controller, "stationCommandDispatcher", dispatcher); |
| | | |
| | | BasDevp basDevp = new BasDevp(); |
| | | basDevp.setStationList("[{\"deviceNo\":1,\"stationId\":145}]"); |
| | | when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(Collections.singletonList(basDevp)); |
| | | when(stationThread.getCommand(StationCommandType.MOVE, 10335, 145, 188, 0)).thenReturn(command); |
| | | when(dispatcher.dispatch(1, command, "station-controller", "manual-move")) |
| | | .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-controller", "manual-move")); |
| | | |
| | | StationCommandMoveParam param = new StationCommandMoveParam(); |
| | | param.setStationId(145); |
| | | param.setTaskNo(10335); |
| | | param.setTargetStationId(188); |
| | | |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | R result = controller.commandMove(param); |
| | | |
| | | assertEquals(200, result.get("code")); |
| | | verify(dispatcher).dispatch(eq(1), same(command), eq("station-controller"), eq("manual-move")); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.dispatch; |
| | | |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | import static org.mockito.ArgumentMatchers.anyLong; |
| | | import static org.mockito.ArgumentMatchers.anyString; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationCommandDispatcherTest { |
| | | |
| | | @Test |
| | | void dispatch_acceptsMoveCommandAndReturnsQueueDepth() { |
| | | StationCommandDispatcher dispatcher = new StationCommandDispatcher(); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setCommandType(StationCommandType.MOVE); |
| | | command.setTaskNo(100); |
| | | command.setStationId(10); |
| | | command.setTargetStaNo(20); |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | try { |
| | | StationCommandDispatchResult result = dispatcher.dispatch(1, command, "unit-test", "move"); |
| | | |
| | | assertTrue(result.isAccepted()); |
| | | assertEquals("accepted", result.getReason()); |
| | | assertEquals(1, result.getQueueDepth()); |
| | | assertEquals("unit-test", result.getSource()); |
| | | assertEquals("move", result.getScene()); |
| | | } finally { |
| | | MessageQueue.clear(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void dispatch_suppressesDuplicateMoveCommandWithinDedupWindow() { |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationCommandDispatcher dispatcher = new StationCommandDispatcher(redisUtil, coordinator); |
| | | Map<String, Object> dedupStore = new HashMap<>(); |
| | | |
| | | when(redisUtil.get(anyString())).thenAnswer(invocation -> dedupStore.get(invocation.getArgument(0))); |
| | | when(redisUtil.set(anyString(), eq("lock"), anyLong())).thenAnswer(invocation -> { |
| | | dedupStore.put(invocation.getArgument(0), invocation.getArgument(1)); |
| | | return true; |
| | | }); |
| | | when(coordinator.buildPathSignatureHash(org.mockito.ArgumentMatchers.any(StationCommand.class))) |
| | | .thenReturn("same-path"); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setCommandType(StationCommandType.MOVE); |
| | | command.setTaskNo(100); |
| | | command.setStationId(10); |
| | | command.setTargetStaNo(20); |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | try { |
| | | StationCommandDispatchResult first = dispatcher.dispatch(1, command, "unit-test", "move"); |
| | | StationCommandDispatchResult second = dispatcher.dispatch(1, command, "unit-test", "move"); |
| | | |
| | | assertTrue(first.isAccepted()); |
| | | assertFalse(second.isAccepted()); |
| | | assertEquals("dedup-suppressed", second.getReason()); |
| | | assertEquals(1, second.getQueueDepth()); |
| | | } finally { |
| | | MessageQueue.clear(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void dispatch_rejectsWhenDevpQueueIsNotInitialized() { |
| | | StationCommandDispatcher dispatcher = new StationCommandDispatcher(); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setCommandType(StationCommandType.MOVE); |
| | | command.setTaskNo(100); |
| | | command.setStationId(10); |
| | | command.setTargetStaNo(20); |
| | | |
| | | StationCommandDispatchResult result = dispatcher.dispatch(999, command, "unit-test", "move"); |
| | | |
| | | assertFalse(result.isAccepted()); |
| | | assertEquals("queue-not-initialized", result.getReason()); |
| | | assertEquals(0, result.getQueueDepth()); |
| | | } |
| | | } |
| | |
| | | |
| | | import com.zy.asrs.entity.DeviceConfig; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.cache.MessageQueue; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.model.Task; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.network.ZyStationConnectDriver; |
| | | import com.zy.core.thread.impl.v5.StationV5SegmentExecutor; |
| | | import com.zy.core.thread.impl.v5.StationV5StatusReader; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.timeout; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class ZyStationV5ThreadTest { |
| | | |
| | | @Test |
| | | void pollAndDispatchQueuedCommand_submitsQueuedMoveCommandToSegmentExecutor() { |
| | | DeviceConfig deviceConfig = new DeviceConfig(); |
| | | deviceConfig.setDeviceNo(1); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | StationCommand command = new StationCommand(); |
| | | StationV5SegmentExecutor segmentExecutor = mock(StationV5SegmentExecutor.class); |
| | | |
| | | ZyStationV5Thread thread = new ZyStationV5Thread(deviceConfig, redisUtil); |
| | | ReflectionTestUtils.setField(thread, "segmentExecutor", segmentExecutor); |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | try { |
| | | MessageQueue.offer(SlaveType.Devp, 1, new Task(2, command)); |
| | | |
| | | ReflectionTestUtils.invokeMethod(thread, "pollAndDispatchQueuedCommand"); |
| | | |
| | | verify(segmentExecutor, timeout(1000)).execute(command); |
| | | } finally { |
| | | MessageQueue.clear(SlaveType.Devp, 1); |
| | | thread.close(); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void clearPath_delegatesPureSlotClearingToDriver() { |
| | |
| | | station10.setStationId(10); |
| | | station10.setTaskBufferItems(List.of(hitItem)); |
| | | |
| | | ReflectionTestUtils.setField(thread, "statusList", Arrays.asList(station20, station10)); |
| | | StationV5StatusReader statusReader = (StationV5StatusReader) ReflectionTestUtils.getField(thread, "statusReader"); |
| | | ReflectionTestUtils.setField(statusReader, "statusList", Arrays.asList(station20, station10)); |
| | | |
| | | boolean result = thread.clearPath(100); |
| | | |
| New file |
| | |
| | | package com.zy.core.thread.impl.v5; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.service.StationTaskLoopService; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.util.HashSet; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertSame; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationV5RunBlockReroutePlannerTest { |
| | | |
| | | @Test |
| | | void plan_prefersLongerCandidateWhenShortestPathIsOverusedInsideTriggeredLoop() { |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | String stateKey = RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + "100_10"; |
| | | when(redisUtil.get(stateKey)).thenReturn(JSON.toJSONString(seedState( |
| | | 100, |
| | | 10, |
| | | 2, |
| | | List.of(), |
| | | Map.of("10->11->20", 2) |
| | | ))); |
| | | |
| | | StationV5RunBlockReroutePlanner planner = new StationV5RunBlockReroutePlanner(redisUtil); |
| | | StationCommand shortest = moveCommand(100, 10, 20, 10, 11, 20); |
| | | StationCommand longer = moveCommand(100, 10, 20, 10, 31, 32, 20); |
| | | |
| | | StationTaskLoopService.LoopIdentitySnapshot loopIdentity = |
| | | new StationTaskLoopService.LoopIdentitySnapshot("10|11|20", new HashSet<>(List.of(10, 11, 20)), 3, 3, "wholeLoop"); |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = |
| | | new StationTaskLoopService.LoopEvaluation(100, 10, loopIdentity, 2, 3, true); |
| | | |
| | | StationV5RunBlockReroutePlanner.PlanResult result = planner.plan( |
| | | 100, |
| | | 10, |
| | | loopEvaluation, |
| | | List.of(shortest, longer) |
| | | ); |
| | | |
| | | assertSame(longer, result.getCommand()); |
| | | assertEquals(3, result.getPlanCount()); |
| | | } |
| | | |
| | | @Test |
| | | void plan_resetsIssuedRoutesWhenAllCandidatesHaveBeenTried() { |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | String stateKey = RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + "100_10"; |
| | | when(redisUtil.get(stateKey)).thenReturn(JSON.toJSONString(seedState( |
| | | 100, |
| | | 10, |
| | | 1, |
| | | List.of(List.of(10, 11, 20), List.of(10, 31, 32, 20)), |
| | | Map.of("10->11->20", 1, "10->31->32->20", 1) |
| | | ))); |
| | | |
| | | StationV5RunBlockReroutePlanner planner = new StationV5RunBlockReroutePlanner(redisUtil); |
| | | StationCommand first = moveCommand(100, 10, 20, 10, 11, 20); |
| | | StationCommand second = moveCommand(100, 10, 20, 10, 31, 32, 20); |
| | | |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = |
| | | new StationTaskLoopService.LoopEvaluation(100, 10, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false); |
| | | |
| | | StationV5RunBlockReroutePlanner.PlanResult result = planner.plan( |
| | | 100, |
| | | 10, |
| | | loopEvaluation, |
| | | List.of(first, second) |
| | | ); |
| | | |
| | | assertSame(first, result.getCommand()); |
| | | assertEquals(2, result.getPlanCount()); |
| | | } |
| | | |
| | | private static StationCommand moveCommand(Integer taskNo, |
| | | Integer stationId, |
| | | Integer targetStationId, |
| | | Integer... path) { |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(taskNo); |
| | | command.setStationId(stationId); |
| | | command.setTargetStaNo(targetStationId); |
| | | command.setNavigatePath(List.of(path)); |
| | | return command; |
| | | } |
| | | |
| | | private static JSONObject seedState(Integer taskNo, |
| | | Integer blockStationId, |
| | | Integer planCount, |
| | | List<List<Integer>> issuedRoutePathList, |
| | | Map<String, Integer> routeIssueCountMap) { |
| | | JSONObject state = new JSONObject(); |
| | | state.put("taskNo", taskNo); |
| | | state.put("blockStationId", blockStationId); |
| | | state.put("planCount", planCount); |
| | | state.put("issuedRoutePathList", issuedRoutePathList); |
| | | state.put("routeIssueCountMap", new HashMap<>(routeIssueCountMap)); |
| | | return state; |
| | | } |
| | | } |
| | |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.move.StationMoveDispatchMode; |
| | | import com.zy.core.move.StationMoveSession; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.utils.station.StationDispatchLoadSupport; |
| | | import com.zy.core.utils.station.StationDispatchRuntimeStateSupport; |
| | | import com.zy.core.utils.station.StationOutboundDecisionSupport; |
| | | import com.zy.core.utils.station.StationOutboundDispatchProcessor; |
| | | import com.zy.core.utils.station.StationRegularDispatchProcessor; |
| | | import com.zy.core.utils.station.StationRerouteProcessor; |
| | | import com.zy.core.utils.station.model.DispatchLimitConfig; |
| | | import com.zy.core.utils.station.model.LoadGuardState; |
| | | import com.zy.core.utils.station.model.LoopHitResult; |
| | | import com.zy.core.utils.station.model.RerouteCommandPlan; |
| | | import com.zy.core.utils.station.model.RerouteContext; |
| | | import com.zy.core.utils.station.model.RerouteDecision; |
| | | import com.zy.core.utils.station.model.RerouteExecutionResult; |
| | | import com.zy.core.utils.station.model.RerouteSceneType; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.util.Date; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.IdentityHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationOperateProcessUtilsReroutePipelineTest { |
| | | private final Map<StationOperateProcessUtils, Map<String, Object>> dependencyOverrides = new IdentityHashMap<>(); |
| | | |
| | | private StationOperateProcessUtils newUtils() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | stash(utils, "stationOutboundDecisionSupport", new StationOutboundDecisionSupport()); |
| | | wireRerouteProcessor(utils); |
| | | return utils; |
| | | } |
| | | |
| | | private void wireOutboundSupport(StationOperateProcessUtils utils) { |
| | | StationOutboundDecisionSupport support = new StationOutboundDecisionSupport(); |
| | | copyIfPresent(utils, support, "wrkMastService"); |
| | | copyIfPresent(utils, support, "basDevpService"); |
| | | copyIfPresent(utils, support, "basStationService"); |
| | | copyIfPresent(utils, support, "navigateUtils"); |
| | | copyIfPresent(utils, support, "redisUtil"); |
| | | copyIfPresent(utils, support, "stationTaskLoopService"); |
| | | copyIfPresent(utils, support, "stationMoveCoordinator"); |
| | | StationDispatchRuntimeStateSupport runtimeStateSupport = new StationDispatchRuntimeStateSupport(); |
| | | copyIfPresent(utils, runtimeStateSupport, "redisUtil"); |
| | | copyIfPresent(utils, runtimeStateSupport, "basStationOptService"); |
| | | ReflectionTestUtils.setField(support, "stationDispatchRuntimeStateSupport", runtimeStateSupport); |
| | | stash(utils, "stationOutboundDecisionSupport", support); |
| | | } |
| | | |
| | | private void wireRerouteProcessor(StationOperateProcessUtils utils) { |
| | | StationRerouteProcessor processor = new StationRerouteProcessor(); |
| | | copyIfPresent(utils, processor, "basDevpService"); |
| | | copyIfPresent(utils, processor, "wrkMastService"); |
| | | copyIfPresent(utils, processor, "commonService"); |
| | | copyIfPresent(utils, processor, "redisUtil"); |
| | | copyIfPresent(utils, processor, "locMastService"); |
| | | copyIfPresent(utils, processor, "wmsOperateUtils"); |
| | | copyIfPresent(utils, processor, "basStationOptService"); |
| | | copyIfPresent(utils, processor, "stationMoveCoordinator"); |
| | | Object dispatcher = readIfPresent(utils, "stationCommandDispatcher"); |
| | | if (dispatcher == null) { |
| | | dispatcher = new StationCommandDispatcher( |
| | | (RedisUtil) readIfPresent(utils, "redisUtil"), |
| | | (StationMoveCoordinator) readIfPresent(utils, "stationMoveCoordinator") |
| | | ); |
| | | } |
| | | ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher); |
| | | Object outboundSupport = readIfPresent(utils, "stationOutboundDecisionSupport"); |
| | | if (outboundSupport == null) { |
| | | wireOutboundSupport(utils); |
| | | outboundSupport = readIfPresent(utils, "stationOutboundDecisionSupport"); |
| | | } |
| | | if (outboundSupport != null) { |
| | | ReflectionTestUtils.setField(processor, "stationOutboundDecisionSupport", outboundSupport); |
| | | } |
| | | StationDispatchRuntimeStateSupport runtimeStateSupport = new StationDispatchRuntimeStateSupport(); |
| | | copyIfPresent(utils, runtimeStateSupport, "redisUtil"); |
| | | copyIfPresent(utils, runtimeStateSupport, "basStationOptService"); |
| | | ReflectionTestUtils.setField(processor, "stationDispatchRuntimeStateSupport", runtimeStateSupport); |
| | | ReflectionTestUtils.setField(utils, "stationRerouteProcessor", processor); |
| | | } |
| | | |
| | | private void stash(StationOperateProcessUtils utils, String fieldName, Object value) { |
| | | dependencyOverrides.computeIfAbsent(utils, key -> new HashMap<>()).put(fieldName, value); |
| | | } |
| | | |
| | | private Object readIfPresent(Object source, String fieldName) { |
| | | try { |
| | | return ReflectionTestUtils.getField(source, fieldName); |
| | | } catch (IllegalArgumentException ignore) { |
| | | if (source instanceof StationOperateProcessUtils) { |
| | | Map<String, Object> values = dependencyOverrides.get(source); |
| | | return values == null ? null : values.get(fieldName); |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private void copyIfPresent(Object source, Object target, String fieldName) { |
| | | Object value = readIfPresent(source, fieldName); |
| | | if (value != null) { |
| | | try { |
| | | ReflectionTestUtils.setField(target, fieldName, value); |
| | | } catch (IllegalArgumentException ignore) { |
| | | } |
| | | } |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private void stubTaskDispatchLock(StationMoveCoordinator coordinator) { |
| | |
| | | |
| | | @Test |
| | | void choosesRunBlockCommandBuilderForRunBlockRerouteScene() { |
| | | StationOperateProcessUtils.RerouteSceneType scene = |
| | | StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE; |
| | | RerouteSceneType scene = RerouteSceneType.RUN_BLOCK_REROUTE; |
| | | |
| | | assertSame(StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE, scene); |
| | | assertSame(RerouteSceneType.RUN_BLOCK_REROUTE, scene); |
| | | } |
| | | |
| | | @Test |
| | | void resolveExecutionTarget_skipsWhenTargetEqualsCurrentStation() { |
| | | StationOperateProcessUtils.RerouteDecision decision = |
| | | StationOperateProcessUtils.RerouteDecision.skip("same-station"); |
| | | RerouteDecision decision = RerouteDecision.skip("same-station"); |
| | | |
| | | assertTrue(decision.skip()); |
| | | assertEquals("same-station", decision.skipReason()); |
| | |
| | | |
| | | @Test |
| | | void buildCommandPlan_usesRunBlockCommandBuilderForRunBlockScene() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(100); |
| | |
| | | command.setTargetStaNo(20); |
| | | when(stationThread.getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d)).thenReturn(command); |
| | | |
| | | StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | buildBasDevp(1), |
| | | stationThread, |
| | | buildStationProtocol(10, 100, 10), |
| | |
| | | .withCancelSessionBeforeDispatch() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | |
| | | StationOperateProcessUtils.RerouteCommandPlan plan = utils.buildRerouteCommandPlan( |
| | | RerouteCommandPlan plan = utils.buildRerouteCommandPlan( |
| | | context, |
| | | StationOperateProcessUtils.RerouteDecision.proceed(20) |
| | | RerouteDecision.proceed(20) |
| | | ); |
| | | |
| | | verify(stationThread).getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d); |
| | |
| | | |
| | | @Test |
| | | void executePlan_skipsWhenCurrentTaskStillExistsInBuffer() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(100); |
| | | command.setStationId(10); |
| | |
| | | StationTaskBufferItem bufferItem = new StationTaskBufferItem(); |
| | | bufferItem.setTaskNo(100); |
| | | |
| | | StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.OUT_ORDER, |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.OUT_ORDER, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(10, 100, 10, Collections.singletonList(bufferItem)), |
| | |
| | | "checkStationOutOrder" |
| | | ); |
| | | |
| | | StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan( |
| | | RerouteExecutionResult result = utils.executeReroutePlan( |
| | | context, |
| | | StationOperateProcessUtils.RerouteCommandPlan.dispatch( |
| | | RerouteCommandPlan.dispatch( |
| | | command, |
| | | StationOperateProcessUtils.RerouteDecision.proceed(20), |
| | | RerouteDecision.proceed(20), |
| | | "checkStationOutOrder" |
| | | ) |
| | | ); |
| | |
| | | |
| | | @Test |
| | | void outOrderAndWatchCircle_shareDecisionFlow() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | WrkMast wrkMast = buildWrkMast(100, 20); |
| | | |
| | | StationOperateProcessUtils.RerouteContext outOrderContext = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.OUT_ORDER, |
| | | RerouteContext outOrderContext = RerouteContext.create( |
| | | RerouteSceneType.OUT_ORDER, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(10, 100, 10), |
| | |
| | | 0.0d, |
| | | "checkStationOutOrder" |
| | | ); |
| | | StationOperateProcessUtils.RerouteContext watchCircleContext = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.WATCH_CIRCLE, |
| | | RerouteContext watchCircleContext = RerouteContext.create( |
| | | RerouteSceneType.WATCH_CIRCLE, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(10, 100, 10), |
| | |
| | | "watchCircleStation" |
| | | ); |
| | | |
| | | StationOperateProcessUtils.RerouteDecision outOrderDecision = utils.resolveSharedRerouteDecision(outOrderContext); |
| | | StationOperateProcessUtils.RerouteDecision watchCircleDecision = utils.resolveSharedRerouteDecision(watchCircleContext); |
| | | RerouteDecision outOrderDecision = utils.resolveSharedRerouteDecision(outOrderContext); |
| | | RerouteDecision watchCircleDecision = utils.resolveSharedRerouteDecision(watchCircleContext); |
| | | |
| | | assertEquals(20, outOrderDecision.targetStationId()); |
| | | assertEquals(20, watchCircleDecision.targetStationId()); |
| | |
| | | |
| | | @Test |
| | | void runBlockReroute_keepsDirectReassignAndNormalRerouteSeparate() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | WrkMast inboundWrkMast = buildWrkMast(100, 20); |
| | | inboundWrkMast.setIoType(WrkIoType.IN.id); |
| | | |
| | |
| | | |
| | | @Test |
| | | void idleRecover_skipsWhenLastDispatchIsTooRecent() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | StationMoveSession session = new StationMoveSession(); |
| | | session.setStatus(StationMoveSession.STATUS_RUNNING); |
| | |
| | | command.setStationId(10); |
| | | command.setTargetStaNo(20); |
| | | |
| | | StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.IDLE_RECOVER, |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.IDLE_RECOVER, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(10, 100, 10), |
| | |
| | | "checkStationIdleRecover" |
| | | ).withRecentDispatchGuard(); |
| | | |
| | | StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan( |
| | | RerouteExecutionResult result = utils.executeReroutePlan( |
| | | context, |
| | | StationOperateProcessUtils.RerouteCommandPlan.dispatch( |
| | | RerouteCommandPlan.dispatch( |
| | | command, |
| | | StationOperateProcessUtils.RerouteDecision.proceed(20), |
| | | RerouteDecision.proceed(20), |
| | | "checkStationIdleRecover" |
| | | ) |
| | | ); |
| | |
| | | |
| | | @Test |
| | | void idleRecover_skipsWhenCurrentStationIsStillInsideRecentlyIssuedActiveRoute() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | StationMoveSession session = new StationMoveSession(); |
| | | session.setStatus(StationMoveSession.STATUS_RUNNING); |
| | |
| | | command.setStationId(121); |
| | | command.setTargetStaNo(124); |
| | | |
| | | StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.IDLE_RECOVER, |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.IDLE_RECOVER, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(121, 10510, 121), |
| | |
| | | "checkStationIdleRecover" |
| | | ).withRecentDispatchGuard(); |
| | | |
| | | StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan( |
| | | RerouteExecutionResult result = utils.executeReroutePlan( |
| | | context, |
| | | StationOperateProcessUtils.RerouteCommandPlan.dispatch( |
| | | RerouteCommandPlan.dispatch( |
| | | command, |
| | | StationOperateProcessUtils.RerouteDecision.proceed(124), |
| | | RerouteDecision.proceed(124), |
| | | "checkStationIdleRecover" |
| | | ) |
| | | ); |
| | |
| | | |
| | | @Test |
| | | void idleRecover_skipsWhenStationCommandLogShowsRecentIssuedMove() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | BasStationOptService basStationOptService = mock(BasStationOptService.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "basStationOptService", basStationOptService); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "basStationOptService", basStationOptService); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | StationMoveSession session = new StationMoveSession(); |
| | | session.setStatus(StationMoveSession.STATUS_RUNNING); |
| | |
| | | |
| | | @Test |
| | | void checkStationOutOrder_skipsWhenActiveSessionAlreadyOwnsCurrentStation() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | |
| | | StationThread stationThread = mock(StationThread.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | |
| | | ReflectionTestUtils.setField(utils, "basDevpService", basDevpService); |
| | | stash(utils, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireOutboundSupport(utils); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | BasDevp basDevp = buildBasDevp(1); |
| | | basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]"); |
| | |
| | | |
| | | @Test |
| | | void checkStationOutOrder_restartsWhenBlockedSessionExistsButStationNoLongerRunBlock() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | |
| | | StationThread stationThread = mock(StationThread.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | |
| | | ReflectionTestUtils.setField(utils, "basDevpService", basDevpService); |
| | | stash(utils, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireOutboundSupport(utils); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | BasDevp basDevp = buildBasDevp(1); |
| | | basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]"); |
| | |
| | | |
| | | @Test |
| | | void checkStationOutOrder_skipsRunBlockStationBeforePlanning() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(utils, "basDevpService", basDevpService); |
| | | stash(utils, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireOutboundSupport(utils); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | BasDevp basDevp = buildBasDevp(1); |
| | | basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]"); |
| | |
| | | |
| | | @Test |
| | | void executePlan_runBlockReroute_reissuesWhenBlockedSessionMatchesCandidatePath() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(100); |
| | |
| | | when(coordinator.loadSession(100)).thenReturn(session); |
| | | when(coordinator.buildPathSignature(command)).thenReturn("same-path"); |
| | | |
| | | StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(145, 100, 145), |
| | |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | try { |
| | | StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan( |
| | | RerouteExecutionResult result = utils.executeReroutePlan( |
| | | context, |
| | | StationOperateProcessUtils.RerouteCommandPlan.dispatch( |
| | | RerouteCommandPlan.dispatch( |
| | | command, |
| | | StationOperateProcessUtils.RerouteDecision.proceed(111), |
| | | RerouteDecision.proceed(111), |
| | | "checkStationRunBlock_reroute" |
| | | ) |
| | | ); |
| | |
| | | |
| | | @Test |
| | | void executePlan_runBlockReroute_ignoresCurrentTaskBufferAfterReset() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(10388); |
| | |
| | | |
| | | when(redisUtil.get(anyString())).thenReturn(null); |
| | | |
| | | StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(186, 10388, 189, Collections.singletonList(bufferItem)), |
| | |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | try { |
| | | StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan( |
| | | RerouteExecutionResult result = utils.executeReroutePlan( |
| | | context, |
| | | StationOperateProcessUtils.RerouteCommandPlan.dispatch( |
| | | RerouteCommandPlan.dispatch( |
| | | command, |
| | | StationOperateProcessUtils.RerouteDecision.proceed(124), |
| | | RerouteDecision.proceed(124), |
| | | "checkStationRunBlock_reroute" |
| | | ) |
| | | ); |
| | |
| | | |
| | | @Test |
| | | void executePlan_runBlockReroute_bypassesSuppressDispatchAfterReset() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(10388); |
| | |
| | | when(redisUtil.get(anyString())).thenReturn(null); |
| | | when(coordinator.shouldSuppressDispatch(10388, 186, command)).thenReturn(true); |
| | | |
| | | StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create( |
| | | StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(186, 10388, 186), |
| | |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | try { |
| | | StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan( |
| | | RerouteExecutionResult result = utils.executeReroutePlan( |
| | | context, |
| | | StationOperateProcessUtils.RerouteCommandPlan.dispatch( |
| | | RerouteCommandPlan.dispatch( |
| | | command, |
| | | StationOperateProcessUtils.RerouteDecision.proceed(124), |
| | | RerouteDecision.proceed(124), |
| | | "checkStationRunBlock_reroute" |
| | | ) |
| | | ); |
| | |
| | | |
| | | @Test |
| | | void stationInExecute_recordsDispatchSessionAfterIssuingMoveCommand() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | CommonService commonService = mock(CommonService.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class); |
| | | StationDispatchLoadSupport loadSupport = mock(StationDispatchLoadSupport.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | |
| | | ReflectionTestUtils.setField(utils, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(utils, "commonService", commonService); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(utils, "wrkAnalysisService", wrkAnalysisService); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor(); |
| | | ReflectionTestUtils.setField(processor, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(processor, "commonService", commonService); |
| | | ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService); |
| | | ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher); |
| | | ReflectionTestUtils.setField(processor, "stationDispatchLoadSupport", loadSupport); |
| | | ReflectionTestUtils.setField(utils, "stationRegularDispatchProcessor", processor); |
| | | |
| | | BasDevp basDevp = buildBasDevp(1); |
| | | basDevp.setBarcodeStationList("[{\"deviceNo\":1,\"stationId\":101}]"); |
| | |
| | | when(stationThread.getCommand(com.zy.core.enums.StationCommandType.MOVE, 500670, 101, 102, 0)) |
| | | .thenReturn(command); |
| | | when(redisUtil.get(anyString())).thenReturn(null); |
| | | when(dispatcher.dispatch(1, command, "station-operate-process", "stationInExecute")) |
| | | .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "stationInExecute")); |
| | | |
| | | DispatchLimitConfig baseConfig = new DispatchLimitConfig(); |
| | | LoadGuardState loadGuardState = new LoadGuardState(); |
| | | LoopHitResult noHit = LoopHitResult.noHit(); |
| | | when(loadSupport.getDispatchLimitConfig(null, null)).thenReturn(baseConfig); |
| | | when(loadSupport.countCurrentStationTask()).thenReturn(0); |
| | | when(loadSupport.buildLoadGuardState(baseConfig)).thenReturn(loadGuardState); |
| | | when(loadSupport.getDispatchLimitConfig(101, 102)).thenReturn(baseConfig); |
| | | when(loadSupport.findPathLoopHit(baseConfig, 101, 102, loadGuardState)).thenReturn(noHit); |
| | | when(loadSupport.isDispatchBlocked(baseConfig, 0, loadGuardState, false)).thenReturn(false); |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void dualCrnStationOutExecute_recordsDispatchSessionAfterIssuingMoveCommand() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | NotifyUtils notifyUtils = mock(NotifyUtils.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | void dualCrnStationOutExecute_delegatesToOutboundProcessor() { |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | StationOutboundDispatchProcessor processor = mock(StationOutboundDispatchProcessor.class); |
| | | ReflectionTestUtils.setField(utils, "stationOutboundDispatchProcessor", processor); |
| | | |
| | | ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(utils, "notifyUtils", notifyUtils); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | |
| | | WrkMast wrkMast = buildWrkMast(10335, 145); |
| | | wrkMast.setDualCrnNo(1); |
| | | wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN_COMPLETE.sts); |
| | | when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(Collections.singletonList(wrkMast)); |
| | | when(wrkMastService.updateById(wrkMast)).thenReturn(true); |
| | | |
| | | when(redisUtil.get(anyString())).thenAnswer(invocation -> { |
| | | String key = invocation.getArgument(0); |
| | | if (Objects.equals(key, RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + 10335)) { |
| | | return "{\"deviceNo\":1,\"stationId\":198}"; |
| | | } |
| | | return null; |
| | | }); |
| | | |
| | | StationProtocol stationProtocol = buildStationProtocol(198, 0, 198); |
| | | stationProtocol.setAutoing(true); |
| | | stationProtocol.setLoading(true); |
| | | when(stationThread.getStatusMap()).thenReturn(Map.of(198, stationProtocol)); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(10335); |
| | | command.setStationId(198); |
| | | command.setTargetStaNo(145); |
| | | when(stationThread.getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10335), eq(198), eq(145), eq(0), eq(0.0d))) |
| | | .thenReturn(command); |
| | | |
| | | MessageQueue.init(SlaveType.Devp, 1); |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | utils.dualCrnStationOutExecute(); |
| | | |
| | | verify(coordinator, times(1)).recordDispatch(eq(10335), eq(198), eq("dualCrnStationOutExecute"), same(command), eq(false)); |
| | | } finally { |
| | | MessageQueue.clear(SlaveType.Devp, 1); |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | verify(processor, times(1)).dualCrnStationOutExecute(); |
| | | } |
| | | |
| | | @Test |
| | | void stationOutExecuteFinish_attemptsClearPathBeforeCompletingTask() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | BasStationService basStationService = mock(BasStationService.class); |
| | | WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class); |
| | |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(utils, "basStationService", basStationService); |
| | | ReflectionTestUtils.setField(utils, "wrkAnalysisService", wrkAnalysisService); |
| | | ReflectionTestUtils.setField(utils, "notifyUtils", notifyUtils); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor(); |
| | | ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(processor, "basStationService", basStationService); |
| | | ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService); |
| | | ReflectionTestUtils.setField(processor, "notifyUtils", notifyUtils); |
| | | ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "stationRegularDispatchProcessor", processor); |
| | | |
| | | WrkMast wrkMast = buildWrkMast(10335, 145); |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); |
| | |
| | | |
| | | @Test |
| | | void watchCircleStation_usesSessionArrivalStateWhenLegacyCommandMissing() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | StationOperateProcessUtils utils = newUtils(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | |
| | | StationThread stationThread = mock(StationThread.class); |
| | | stubTaskDispatchLock(coordinator); |
| | | |
| | | ReflectionTestUtils.setField(utils, "basDevpService", basDevpService); |
| | | stash(utils, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(utils, "redisUtil", redisUtil); |
| | | stash(utils, "stationMoveCoordinator", coordinator); |
| | | stash(utils, "redisUtil", redisUtil); |
| | | wireOutboundSupport(utils); |
| | | wireRerouteProcessor(utils); |
| | | |
| | | BasDevp basDevp = buildBasDevp(1); |
| | | when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| New file |
| | |
| | | package com.zy.core.utils; |
| | | |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.StationRerouteProcessor; |
| | | import com.zy.core.utils.station.model.RerouteCommandPlan; |
| | | import com.zy.core.utils.station.model.RerouteContext; |
| | | import com.zy.core.utils.station.model.RerouteDecision; |
| | | import com.zy.core.utils.station.model.RerouteExecutionResult; |
| | | import com.zy.core.utils.station.model.RerouteSceneType; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertSame; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationRerouteProcessorTest { |
| | | |
| | | @Test |
| | | void rerouteProcessor_noLongerKeepsDispatcherFallbackHelper() { |
| | | assertThrows(NoSuchMethodException.class, |
| | | () -> StationRerouteProcessor.class.getDeclaredMethod("getStationCommandDispatcher")); |
| | | } |
| | | |
| | | @Test |
| | | void buildCommandPlan_usesRunBlockCommandBuilderForRunBlockScene() { |
| | | StationRerouteProcessor processor = new StationRerouteProcessor(); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(100); |
| | | command.setStationId(10); |
| | | command.setTargetStaNo(20); |
| | | when(stationThread.getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d)).thenReturn(command); |
| | | |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.RUN_BLOCK_REROUTE, |
| | | buildBasDevp(1), |
| | | stationThread, |
| | | buildStationProtocol(10, 100, 10), |
| | | buildWrkMast(100, 99), |
| | | Collections.emptyList(), |
| | | 0.25d, |
| | | "checkStationRunBlock_reroute" |
| | | ).withRunBlockCommand() |
| | | .withCancelSessionBeforeDispatch() |
| | | .withResetSegmentCommandsBeforeDispatch(); |
| | | |
| | | RerouteCommandPlan plan = processor.buildRerouteCommandPlan( |
| | | context, |
| | | RerouteDecision.proceed(20) |
| | | ); |
| | | |
| | | verify(stationThread).getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d); |
| | | assertSame(command, plan.command()); |
| | | } |
| | | |
| | | @Test |
| | | void executePlan_skipsWhenCurrentTaskStillExistsInBuffer() { |
| | | StationRerouteProcessor processor = new StationRerouteProcessor(); |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(100); |
| | | command.setStationId(10); |
| | | command.setTargetStaNo(20); |
| | | |
| | | StationTaskBufferItem bufferItem = new StationTaskBufferItem(); |
| | | bufferItem.setTaskNo(100); |
| | | |
| | | RerouteContext context = RerouteContext.create( |
| | | RerouteSceneType.OUT_ORDER, |
| | | buildBasDevp(1), |
| | | mock(StationThread.class), |
| | | buildStationProtocol(10, 100, 10, Collections.singletonList(bufferItem)), |
| | | buildWrkMast(100, 20), |
| | | List.of(10, 20), |
| | | 0.0d, |
| | | "checkStationOutOrder" |
| | | ); |
| | | |
| | | RerouteExecutionResult result = processor.executeReroutePlan( |
| | | context, |
| | | RerouteCommandPlan.dispatch( |
| | | command, |
| | | RerouteDecision.proceed(20), |
| | | "checkStationOutOrder" |
| | | ) |
| | | ); |
| | | |
| | | assertTrue(result.skipped()); |
| | | assertEquals("buffer-has-current-task", result.skipReason()); |
| | | } |
| | | |
| | | private static BasDevp buildBasDevp(int devpNo) { |
| | | BasDevp basDevp = new BasDevp(); |
| | | basDevp.setDevpNo(devpNo); |
| | | return basDevp; |
| | | } |
| | | |
| | | private static WrkMast buildWrkMast(int wrkNo, int targetStationId) { |
| | | WrkMast wrkMast = new WrkMast(); |
| | | wrkMast.setWrkNo(wrkNo); |
| | | wrkMast.setStaNo(targetStationId); |
| | | return wrkMast; |
| | | } |
| | | |
| | | private static StationProtocol buildStationProtocol(int stationId, |
| | | int taskNo, |
| | | int targetStationId) { |
| | | return buildStationProtocol(stationId, taskNo, targetStationId, Collections.emptyList()); |
| | | } |
| | | |
| | | private static StationProtocol buildStationProtocol(int stationId, |
| | | int taskNo, |
| | | int targetStationId, |
| | | List<StationTaskBufferItem> taskBufferItems) { |
| | | StationProtocol stationProtocol = new StationProtocol(); |
| | | stationProtocol.setStationId(stationId); |
| | | stationProtocol.setTaskNo(taskNo); |
| | | stationProtocol.setTargetStaNo(targetStationId); |
| | | stationProtocol.setTaskBufferItems(taskBufferItems); |
| | | return stationProtocol; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import com.zy.asrs.entity.BasStationOpt; |
| | | import com.zy.asrs.service.BasStationOptService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationDispatchRuntimeStateSupportTest { |
| | | |
| | | @Test |
| | | void idleTrack_isTimeoutUsesFirstSeenTime() { |
| | | StationTaskIdleTrack track = new StationTaskIdleTrack(100, 145, System.currentTimeMillis() - 11_000L); |
| | | |
| | | assertTrue(track.isTimeout(10)); |
| | | } |
| | | |
| | | @Test |
| | | void touchIdleTrack_replacesTrackWhenStationChanges() { |
| | | StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport(); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | ReflectionTestUtils.setField(support, "redisUtil", redisUtil); |
| | | when(redisUtil.get(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + 100)) |
| | | .thenReturn(JSON.toJSONString(new StationTaskIdleTrack(100, 145, System.currentTimeMillis() - 3_000L))); |
| | | |
| | | StationTaskIdleTrack track = support.touchIdleTrack(100, 146); |
| | | |
| | | assertEquals(146, track.getStationId()); |
| | | verify(redisUtil).set(eq(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + 100), org.mockito.ArgumentMatchers.any(String.class), eq(3600L)); |
| | | } |
| | | |
| | | @Test |
| | | void hasRecentIssuedMoveCommand_returnsTrueForRecentMoveLog() { |
| | | StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport(); |
| | | BasStationOptService basStationOptService = mock(BasStationOptService.class); |
| | | ReflectionTestUtils.setField(support, "basStationOptService", basStationOptService); |
| | | when(basStationOptService.list(org.mockito.ArgumentMatchers.<Wrapper<BasStationOpt>>any())) |
| | | .thenReturn(Collections.singletonList(new BasStationOpt())); |
| | | |
| | | boolean result = support.hasRecentIssuedMoveCommand(100, 145, 10_000L); |
| | | |
| | | assertTrue(result); |
| | | } |
| | | |
| | | @Test |
| | | void clearIssuedMoveCommandsDuringIdleStay_marksSendZeroAndAppendsMemo() { |
| | | StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport(); |
| | | BasStationOptService basStationOptService = mock(BasStationOptService.class); |
| | | ReflectionTestUtils.setField(support, "basStationOptService", basStationOptService); |
| | | BasStationOpt opt = new BasStationOpt(); |
| | | opt.setId(1L); |
| | | opt.setSend(1); |
| | | opt.setMemo("existing"); |
| | | opt.setSendTime(new Date()); |
| | | when(basStationOptService.list(org.mockito.ArgumentMatchers.<Wrapper<BasStationOpt>>any())) |
| | | .thenReturn(Collections.singletonList(opt)); |
| | | |
| | | int clearedCount = support.clearIssuedMoveCommandsDuringIdleStay( |
| | | new StationTaskIdleTrack(100, 145, System.currentTimeMillis() - 10_000L), |
| | | 100, |
| | | 145 |
| | | ); |
| | | |
| | | assertEquals(1, clearedCount); |
| | | assertEquals(0, opt.getSend()); |
| | | assertTrue(opt.getMemo().contains("existing")); |
| | | assertTrue(opt.getMemo().contains("idleRecoverRerouteCleared(stationId=145)")); |
| | | assertFalse(opt.getUpdateTime() == null); |
| | | verify(basStationOptService).updateBatchById(Collections.singletonList(opt)); |
| | | } |
| | | |
| | | @Test |
| | | void tryAcquireOutOrderDispatchLock_returnsFalseWhenKeyAlreadyExists() { |
| | | StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport(); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | ReflectionTestUtils.setField(support, "redisUtil", redisUtil); |
| | | when(redisUtil.get(RedisKeyType.STATION_OUT_ORDER_DISPATCH_LIMIT_.key + "100_145")).thenReturn("lock"); |
| | | |
| | | boolean acquired = support.tryAcquireOutOrderDispatchLock(100, 145, 2); |
| | | |
| | | assertFalse(acquired); |
| | | } |
| | | |
| | | @Test |
| | | void signalSegmentReset_setsAndClearsResetKey() { |
| | | StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport(); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | ReflectionTestUtils.setField(support, "redisUtil", redisUtil); |
| | | |
| | | support.signalSegmentReset(100, 0L); |
| | | |
| | | verify(redisUtil).set(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + 100, "cancel", 3); |
| | | verify(redisUtil).del(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + 100); |
| | | } |
| | | |
| | | @Test |
| | | void watchCircleCommand_roundTripsThroughRedis() { |
| | | StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport(); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | ReflectionTestUtils.setField(support, "redisUtil", redisUtil); |
| | | AtomicReference<Object> storedValue = new AtomicReference<>(); |
| | | when(redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + 100)) |
| | | .thenAnswer(invocation -> storedValue.get()); |
| | | when(redisUtil.set(eq(RedisKeyType.WATCH_CIRCLE_STATION_.key + 100), org.mockito.ArgumentMatchers.any(String.class), eq(60L * 60 * 24))) |
| | | .thenAnswer(invocation -> { |
| | | storedValue.set(invocation.getArgument(1)); |
| | | return true; |
| | | }); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(100); |
| | | command.setTargetStaNo(145); |
| | | |
| | | support.saveWatchCircleCommand(100, command); |
| | | StationCommand loaded = support.loadWatchCircleCommand(100); |
| | | |
| | | assertNotNull(loaded); |
| | | assertEquals(145, loaded.getTargetStaNo()); |
| | | support.clearWatchCircleCommand(100); |
| | | verify(redisUtil).del(RedisKeyType.WATCH_CIRCLE_STATION_.key + 100); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.zy.asrs.domain.enums.NotifyMsgType; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.WrkAnalysisService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.asrs.utils.NotifyUtils; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.WrkStsType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.model.DispatchLimitConfig; |
| | | import com.zy.core.utils.station.model.LoadGuardState; |
| | | import com.zy.core.utils.station.model.LoopHitResult; |
| | | import com.zy.core.utils.station.model.OutOrderDispatchDecision; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.ArgumentMatchers.anyString; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.same; |
| | | import static org.mockito.Mockito.times; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationOutboundDispatchProcessorTest { |
| | | |
| | | @Test |
| | | void crnStationOutExecute_recordsDispatchSessionAndClearsStationCacheAfterIssuingMoveCommand() { |
| | | StationOutboundDispatchProcessor processor = new StationOutboundDispatchProcessor(); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class); |
| | | StationDispatchLoadSupport loadSupport = mock(StationDispatchLoadSupport.class); |
| | | StationOutboundDecisionSupport decisionSupport = mock(StationOutboundDecisionSupport.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService); |
| | | ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher); |
| | | ReflectionTestUtils.setField(processor, "stationDispatchLoadSupport", loadSupport); |
| | | ReflectionTestUtils.setField(processor, "stationOutboundDecisionSupport", decisionSupport); |
| | | |
| | | WrkMast wrkMast = buildWrkMast(10001, 145); |
| | | wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN_COMPLETE.sts); |
| | | wrkMast.setCrnNo(4); |
| | | when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(Collections.singletonList(wrkMast)); |
| | | when(wrkMastService.updateById(wrkMast)).thenReturn(true); |
| | | |
| | | StationObjModel stationObjModel = new StationObjModel(); |
| | | stationObjModel.setDeviceNo(1); |
| | | stationObjModel.setStationId(101); |
| | | when(redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + 10001)) |
| | | .thenReturn(com.alibaba.fastjson.JSON.toJSONString(stationObjModel)); |
| | | when(redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 101)).thenReturn(null); |
| | | |
| | | StationProtocol stationProtocol = buildStationProtocol(101, 0, 101); |
| | | stationProtocol.setAutoing(true); |
| | | stationProtocol.setLoading(true); |
| | | when(stationThread.getStatusMap()).thenReturn(Map.of(101, stationProtocol)); |
| | | |
| | | DispatchLimitConfig limitConfig = new DispatchLimitConfig(); |
| | | LoadGuardState loadGuardState = new LoadGuardState(); |
| | | LoopHitResult noHit = LoopHitResult.noHit(); |
| | | OutOrderDispatchDecision decision = OutOrderDispatchDecision.direct(145); |
| | | StationCommand command = buildCommand(10001, 101, 145); |
| | | |
| | | when(loadSupport.getDispatchLimitConfig(null, null)).thenReturn(limitConfig); |
| | | when(loadSupport.countCurrentStationTask()).thenReturn(0); |
| | | when(loadSupport.buildLoadGuardState(limitConfig)).thenReturn(loadGuardState); |
| | | when(decisionSupport.getAllOutOrderList()).thenReturn(List.of(101, 121, 145)); |
| | | when(decisionSupport.resolveOutboundPathLenFactor(wrkMast)).thenReturn(0.25d); |
| | | when(decisionSupport.resolveOutboundDispatchDecision(101, wrkMast, List.of(101, 121, 145), 0.25d)).thenReturn(decision); |
| | | when(loadSupport.getDispatchLimitConfig(101, 145)).thenReturn(limitConfig); |
| | | when(loadSupport.findPathLoopHit(limitConfig, 101, 145, loadGuardState, wrkMast, 0.25d)).thenReturn(noHit); |
| | | when(loadSupport.isDispatchBlocked(limitConfig, 0, loadGuardState, false)).thenReturn(false); |
| | | when(decisionSupport.buildOutboundMoveCommand(stationThread, wrkMast, 101, 145, 0.25d)).thenReturn(command); |
| | | when(dispatcher.dispatch(1, command, "station-operate-process", "crnStationOutExecute")) |
| | | .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "crnStationOutExecute")); |
| | | |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | processor.crnStationOutExecute(); |
| | | |
| | | verify(wrkAnalysisService, times(1)).markOutboundStationStart(eq(wrkMast), any(Date.class)); |
| | | verify(coordinator, times(1)).recordDispatch(eq(10001), eq(101), eq("crnStationOutExecute"), same(command), eq(false)); |
| | | verify(redisUtil, times(1)).set(eq(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 101), eq("lock"), eq(5L)); |
| | | verify(redisUtil, times(1)).del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + 10001); |
| | | verify(loadSupport, times(1)).saveLoopLoadReserve(10001, noHit); |
| | | assertEquals(WrkStsType.STATION_RUN.sts, wrkMast.getWrkSts()); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void dualCrnStationOutExecute_recordsDispatchSessionAndClearsDualCacheAfterIssuingMoveCommand() { |
| | | StationOutboundDispatchProcessor processor = new StationOutboundDispatchProcessor(); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class); |
| | | NotifyUtils notifyUtils = mock(NotifyUtils.class); |
| | | StationOutboundDecisionSupport decisionSupport = mock(StationOutboundDecisionSupport.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher); |
| | | ReflectionTestUtils.setField(processor, "notifyUtils", notifyUtils); |
| | | ReflectionTestUtils.setField(processor, "stationOutboundDecisionSupport", decisionSupport); |
| | | |
| | | WrkMast wrkMast = buildWrkMast(10002, 188); |
| | | wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN_COMPLETE.sts); |
| | | wrkMast.setDualCrnNo(2); |
| | | when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(Collections.singletonList(wrkMast)); |
| | | when(wrkMastService.updateById(wrkMast)).thenReturn(true); |
| | | |
| | | StationObjModel stationObjModel = new StationObjModel(); |
| | | stationObjModel.setDeviceNo(1); |
| | | stationObjModel.setStationId(121); |
| | | when(redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + 10002)) |
| | | .thenReturn(com.alibaba.fastjson.JSON.toJSONString(stationObjModel)); |
| | | when(redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 121)).thenReturn(null); |
| | | |
| | | StationProtocol stationProtocol = buildStationProtocol(121, 0, 121); |
| | | stationProtocol.setAutoing(true); |
| | | stationProtocol.setLoading(true); |
| | | when(stationThread.getStatusMap()).thenReturn(Map.of(121, stationProtocol)); |
| | | |
| | | StationCommand command = buildCommand(10002, 121, 188); |
| | | when(decisionSupport.resolveOutboundPathLenFactor(wrkMast)).thenReturn(0.5d); |
| | | when(decisionSupport.buildOutboundMoveCommand(stationThread, wrkMast, 121, 188, 0.5d)).thenReturn(command); |
| | | when(dispatcher.dispatch(1, command, "station-operate-process", "dualCrnStationOutExecute")) |
| | | .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "dualCrnStationOutExecute")); |
| | | |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | processor.dualCrnStationOutExecute(); |
| | | |
| | | verify(coordinator, times(1)).recordDispatch(eq(10002), eq(121), eq("dualCrnStationOutExecute"), same(command), eq(false)); |
| | | verify(notifyUtils, times(1)).notify(String.valueOf(SlaveType.Devp), 1, "10002", wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN, null); |
| | | verify(redisUtil, times(1)).set(eq(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 121), eq("lock"), eq(5L)); |
| | | verify(redisUtil, times(1)).del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + 10002); |
| | | assertEquals(WrkStsType.STATION_RUN.sts, wrkMast.getWrkSts()); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | private static WrkMast buildWrkMast(int wrkNo, int targetStationId) { |
| | | WrkMast wrkMast = new WrkMast(); |
| | | wrkMast.setWrkNo(wrkNo); |
| | | wrkMast.setStaNo(targetStationId); |
| | | wrkMast.setWmsWrkNo("WMS-" + wrkNo); |
| | | wrkMast.setIoTime(new Date()); |
| | | return wrkMast; |
| | | } |
| | | |
| | | private static StationProtocol buildStationProtocol(int stationId, |
| | | int taskNo, |
| | | int targetStationId) { |
| | | StationProtocol stationProtocol = new StationProtocol(); |
| | | stationProtocol.setStationId(stationId); |
| | | stationProtocol.setTaskNo(taskNo); |
| | | stationProtocol.setTargetStaNo(targetStationId); |
| | | return stationProtocol; |
| | | } |
| | | |
| | | private static StationCommand buildCommand(int wrkNo, |
| | | int stationId, |
| | | int targetStationId) { |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(wrkNo); |
| | | command.setStationId(stationId); |
| | | command.setTargetStaNo(targetStationId); |
| | | return command; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station; |
| | | |
| | | import com.zy.asrs.domain.enums.NotifyMsgType; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.BasStation; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.BasStationService; |
| | | import com.zy.asrs.service.WrkAnalysisService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.asrs.utils.NotifyUtils; |
| | | import com.zy.common.entity.FindCrnNoResult; |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.dispatch.StationCommandDispatchResult; |
| | | import com.zy.core.dispatch.StationCommandDispatcher; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.enums.WrkStsType; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.move.StationMoveCoordinator; |
| | | import com.zy.core.thread.StationThread; |
| | | import com.zy.core.utils.station.model.DispatchLimitConfig; |
| | | import com.zy.core.utils.station.model.LoadGuardState; |
| | | import com.zy.core.utils.station.model.LoopHitResult; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.ArgumentMatchers.anyString; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.same; |
| | | import static org.mockito.Mockito.times; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationRegularDispatchProcessorTest { |
| | | |
| | | @Test |
| | | void stationInExecute_recordsDispatchSessionAfterIssuingMoveCommand() { |
| | | StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | CommonService commonService = mock(CommonService.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class); |
| | | StationDispatchLoadSupport loadSupport = mock(StationDispatchLoadSupport.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(processor, "basDevpService", basDevpService); |
| | | ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(processor, "commonService", commonService); |
| | | ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService); |
| | | ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher); |
| | | ReflectionTestUtils.setField(processor, "stationDispatchLoadSupport", loadSupport); |
| | | |
| | | BasDevp basDevp = buildBasDevp(1); |
| | | basDevp.setBarcodeStationList("[{\"deviceNo\":1,\"stationId\":101}]"); |
| | | when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(Collections.singletonList(basDevp)); |
| | | |
| | | StationProtocol stationProtocol = buildStationProtocol(101, 500670, 101); |
| | | stationProtocol.setAutoing(true); |
| | | stationProtocol.setLoading(true); |
| | | stationProtocol.setBarcode("GSL110005"); |
| | | when(stationThread.getStatusMap()).thenReturn(Map.of(101, stationProtocol)); |
| | | |
| | | WrkMast wrkMast = buildWrkMast(500670, 102); |
| | | wrkMast.setWrkSts(WrkStsType.NEW_INBOUND.sts); |
| | | wrkMast.setLocNo("8-4-1"); |
| | | when(wrkMastService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))).thenReturn(wrkMast); |
| | | when(wrkMastService.updateById(wrkMast)).thenReturn(true); |
| | | |
| | | FindCrnNoResult findCrnNoResult = new FindCrnNoResult(); |
| | | findCrnNoResult.setCrnNo(4); |
| | | findCrnNoResult.setCrnType(SlaveType.Crn); |
| | | when(commonService.findCrnNoByLocNo("8-4-1")).thenReturn(findCrnNoResult); |
| | | when(commonService.findInStationId(findCrnNoResult, 101)).thenReturn(102); |
| | | |
| | | StationCommand command = new StationCommand(); |
| | | command.setTaskNo(500670); |
| | | command.setStationId(101); |
| | | command.setTargetStaNo(102); |
| | | when(stationThread.getCommand(com.zy.core.enums.StationCommandType.MOVE, 500670, 101, 102, 0)) |
| | | .thenReturn(command); |
| | | when(redisUtil.get(anyString())).thenReturn(null); |
| | | when(dispatcher.dispatch(1, command, "station-operate-process", "stationInExecute")) |
| | | .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "stationInExecute")); |
| | | |
| | | DispatchLimitConfig baseConfig = new DispatchLimitConfig(); |
| | | LoadGuardState loadGuardState = new LoadGuardState(); |
| | | LoopHitResult noHit = LoopHitResult.noHit(); |
| | | when(loadSupport.getDispatchLimitConfig(null, null)).thenReturn(baseConfig); |
| | | when(loadSupport.countCurrentStationTask()).thenReturn(0); |
| | | when(loadSupport.buildLoadGuardState(baseConfig)).thenReturn(loadGuardState); |
| | | when(loadSupport.getDispatchLimitConfig(101, 102)).thenReturn(baseConfig); |
| | | when(loadSupport.findPathLoopHit(baseConfig, 101, 102, loadGuardState)).thenReturn(noHit); |
| | | when(loadSupport.isDispatchBlocked(baseConfig, 0, loadGuardState, false)).thenReturn(false); |
| | | |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | processor.stationInExecute(); |
| | | |
| | | verify(coordinator, times(1)).recordDispatch(eq(500670), eq(101), eq("stationInExecute"), same(command), eq(false)); |
| | | verify(redisUtil, times(1)).set(eq(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + 101), eq("lock"), eq(5L)); |
| | | verify(loadSupport, times(1)).saveLoopLoadReserve(500670, noHit); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void stationOutExecuteFinish_attemptsClearPathBeforeCompletingTask() { |
| | | StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor(); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | BasStationService basStationService = mock(BasStationService.class); |
| | | WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class); |
| | | NotifyUtils notifyUtils = mock(NotifyUtils.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(processor, "basStationService", basStationService); |
| | | ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService); |
| | | ReflectionTestUtils.setField(processor, "notifyUtils", notifyUtils); |
| | | ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); |
| | | ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); |
| | | |
| | | WrkMast wrkMast = buildWrkMast(10335, 145); |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); |
| | | when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(Collections.singletonList(wrkMast)); |
| | | when(wrkMastService.updateById(wrkMast)).thenReturn(true); |
| | | |
| | | BasStation basStation = new BasStation(); |
| | | basStation.setStationId(145); |
| | | basStation.setDeviceNo(1); |
| | | when(basStationService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(basStation); |
| | | |
| | | StationProtocol stationProtocol = buildStationProtocol(145, 10335, 145); |
| | | when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol)); |
| | | when(stationThread.clearPath(10335)).thenReturn(true); |
| | | |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | processor.stationOutExecuteFinish(); |
| | | |
| | | verify(stationThread, times(1)).clearPath(10335); |
| | | verify(coordinator, times(1)).finishSession(10335); |
| | | verify(wrkMastService, times(1)).updateById(wrkMast); |
| | | verify(notifyUtils, times(1)).notify(String.valueOf(SlaveType.Devp), 1, "10335", wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null); |
| | | assertEquals(WrkStsType.STATION_RUN_COMPLETE.sts, wrkMast.getWrkSts()); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void checkTaskToComplete_marksTaskCompleteAfterLeavingTargetStation() { |
| | | StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor(); |
| | | WrkMastService wrkMastService = mock(WrkMastService.class); |
| | | BasStationService basStationService = mock(BasStationService.class); |
| | | StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService); |
| | | ReflectionTestUtils.setField(processor, "basStationService", basStationService); |
| | | ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator); |
| | | ReflectionTestUtils.setField(processor, "redisUtil", redisUtil); |
| | | |
| | | WrkMast wrkMast = buildWrkMast(10335, 145); |
| | | wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts); |
| | | when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(Collections.singletonList(wrkMast)); |
| | | |
| | | BasStation basStation = new BasStation(); |
| | | basStation.setStationId(145); |
| | | basStation.setDeviceNo(1); |
| | | when(basStationService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))) |
| | | .thenReturn(basStation); |
| | | |
| | | StationProtocol stationProtocol = buildStationProtocol(145, 0, 145); |
| | | when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol)); |
| | | when(redisUtil.get(anyString())).thenReturn(null); |
| | | |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | processor.checkTaskToComplete(); |
| | | |
| | | verify(coordinator, times(1)).finishSession(10335); |
| | | verify(wrkMastService, times(1)).updateById(wrkMast); |
| | | assertEquals(WrkStsType.COMPLETE_OUTBOUND.sts, wrkMast.getWrkSts()); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | private static BasDevp buildBasDevp(int devpNo) { |
| | | BasDevp basDevp = new BasDevp(); |
| | | basDevp.setDevpNo(devpNo); |
| | | return basDevp; |
| | | } |
| | | |
| | | private static WrkMast buildWrkMast(int wrkNo, int targetStationId) { |
| | | WrkMast wrkMast = new WrkMast(); |
| | | wrkMast.setWrkNo(wrkNo); |
| | | wrkMast.setStaNo(targetStationId); |
| | | wrkMast.setWmsWrkNo("WMS-" + wrkNo); |
| | | wrkMast.setIoTime(new Date()); |
| | | return wrkMast; |
| | | } |
| | | |
| | | private static StationProtocol buildStationProtocol(int stationId, |
| | | int taskNo, |
| | | int targetStationId) { |
| | | StationProtocol stationProtocol = new StationProtocol(); |
| | | stationProtocol.setStationId(stationId); |
| | | stationProtocol.setTaskNo(taskNo); |
| | | stationProtocol.setTargetStaNo(targetStationId); |
| | | return stationProtocol; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.utils.station.model; |
| | | |
| | | import com.zy.core.utils.StationOperateProcessUtils; |
| | | import com.zy.core.utils.station.StationDispatchLoadSupport; |
| | | import com.zy.core.utils.station.StationOutboundDecisionSupport; |
| | | import com.zy.core.utils.station.StationRerouteProcessor; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | |
| | | class StationModelTypePlacementTest { |
| | | |
| | | @Test |
| | | void rerouteTypes_areNoLongerNestedInsideStationOperateProcessUtils() { |
| | | assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteSceneType")); |
| | | assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteDecision")); |
| | | assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteContext")); |
| | | assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteCommandPlan")); |
| | | assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteExecutionResult")); |
| | | } |
| | | |
| | | @Test |
| | | void dispatchLoadTypes_areNoLongerNestedInsideStationDispatchLoadSupport() { |
| | | assertFalse(hasDeclaredClass(StationDispatchLoadSupport.class, "DispatchLimitConfig")); |
| | | assertFalse(hasDeclaredClass(StationDispatchLoadSupport.class, "LoadGuardState")); |
| | | assertFalse(hasDeclaredClass(StationDispatchLoadSupport.class, "LoopHitResult")); |
| | | } |
| | | |
| | | @Test |
| | | void outboundDecisionTypes_areNoLongerNestedInsideStationOutboundDecisionSupport() { |
| | | assertFalse(hasDeclaredClass(StationOutboundDecisionSupport.class, "OutOrderDispatchDecision")); |
| | | assertFalse(hasDeclaredClass(StationOutboundDecisionSupport.class, "CircleTargetCandidate")); |
| | | } |
| | | |
| | | @Test |
| | | void rerouteProcessor_isLocatedUnderStationPackage() { |
| | | assertEquals("com.zy.core.utils.station", StationRerouteProcessor.class.getPackageName()); |
| | | } |
| | | |
| | | private static boolean hasDeclaredClass(Class<?> ownerType, String simpleName) { |
| | | for (Class<?> declaredClass : ownerType.getDeclaredClasses()) { |
| | | if (simpleName.equals(declaredClass.getSimpleName())) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | } |