| | |
| | | return R.ok(); |
| | | } |
| | | |
| | | @PostMapping("/command/clearPath") |
| | | public R commandClearPath(@RequestBody StationCommandMoveParam param) { |
| | | if (Cools.isEmpty(param) || Cools.isEmpty(param.getStationId()) || Cools.isEmpty(param.getTaskNo())) { |
| | | return R.error("缺少参数"); |
| | | } |
| | | |
| | | StationObjModel finalStation = findStation(param.getStationId()); |
| | | if (finalStation == null) { |
| | | return R.error("站点不存在"); |
| | | } |
| | | |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, finalStation.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | return R.error("线程不存在"); |
| | | } |
| | | |
| | | boolean cleared = stationThread.clearPath(param.getTaskNo()); |
| | | if (!cleared) { |
| | | return R.error("未匹配到可清理路径或清理失败"); |
| | | } |
| | | return R.ok("清理路径成功"); |
| | | } |
| | | |
| | | private StationObjModel findStation(Integer stationId) { |
| | | if (Cools.isEmpty(stationId)) { |
| | | return null; |
| | |
| | | return connectApi.sendOriginCommand(address, data); |
| | | } |
| | | |
| | | public boolean clearTaskBufferSlot(Integer stationId, Integer slotIdx) { |
| | | ZyStationConnectApi connectApi = zyStationConnectApi; |
| | | if (!connected || connecting || connectApi == null) { |
| | | return false; |
| | | } |
| | | CommandResponse response = connectApi.clearTaskBufferSlot(deviceConfig.getDeviceNo(), stationId, slotIdx); |
| | | return response != null && Boolean.TRUE.equals(response.getResult()); |
| | | } |
| | | |
| | | public byte[] readOriginCommand(String address, int length) { |
| | | ZyStationConnectApi connectApi = zyStationConnectApi; |
| | | if (!connected || connecting || connectApi == null) { |
| | |
| | | |
| | | CommandResponse sendCommand(Integer deviceNo, StationCommand command);//下发命令 |
| | | |
| | | default CommandResponse clearTaskBufferSlot(Integer deviceNo, Integer stationId, Integer slotIdx) { |
| | | return new CommandResponse(false, "当前连接不支持清理缓存区槽位"); |
| | | } |
| | | |
| | | CommandResponse sendOriginCommand(String address, short[] data);//原始命令 |
| | | |
| | | byte[] readOriginCommand(String address, int length);//读取原始数据 |
| | |
| | | import com.zy.core.enums.StationCommandType; |
| | | import com.zy.core.model.CommandResponse; |
| | | import com.zy.core.model.command.StationCommand; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.network.api.ZyStationConnectApi; |
| | | import com.zy.core.network.entity.ZyStationStatusEntity; |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public CommandResponse clearTaskBufferSlot(Integer deviceNo, Integer stationId, Integer slotIdx) { |
| | | if (deviceNo == null || stationId == null || slotIdx == null || slotIdx <= 0) { |
| | | return new CommandResponse(false, "清理缓存区槽位参数无效"); |
| | | } |
| | | List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo); |
| | | if (statusList == null) { |
| | | return new CommandResponse(false, "未找到设备状态"); |
| | | } |
| | | for (ZyStationStatusEntity status : statusList) { |
| | | if (status == null || !stationId.equals(status.getStationId())) { |
| | | continue; |
| | | } |
| | | List<StationTaskBufferItem> taskBufferItems = status.getTaskBufferItems(); |
| | | if (taskBufferItems == null || taskBufferItems.isEmpty()) { |
| | | return new CommandResponse(true, "缓存区槽位已为空"); |
| | | } |
| | | for (StationTaskBufferItem item : taskBufferItems) { |
| | | if (item != null && slotIdx.equals(item.getSlotIdx())) { |
| | | item.setTaskNo(0); |
| | | item.setTargetStaNo(0); |
| | | return new CommandResponse(true, "缓存区槽位清理成功"); |
| | | } |
| | | } |
| | | return new CommandResponse(false, "未找到缓存区槽位"); |
| | | } |
| | | return new CommandResponse(false, "未找到站点状态"); |
| | | } |
| | | |
| | | @Override |
| | | public byte[] readOriginCommand(String address, int length) { |
| | | return new byte[0]; |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | public CommandResponse clearTaskBufferSlot(Integer deviceNo, Integer stationId, Integer slotIdx) { |
| | | CommandResponse commandResponse = new CommandResponse(false); |
| | | if (stationId == null) { |
| | | commandResponse.setMessage("站点号为空"); |
| | | return commandResponse; |
| | | } |
| | | if (slotIdx == null || slotIdx <= 0 || slotIdx > TASK_AREA_SLOT_COUNT) { |
| | | commandResponse.setMessage("缓存区槽位无效"); |
| | | return commandResponse; |
| | | } |
| | | |
| | | getStatus(deviceNo); |
| | | |
| | | int stationIdx = findIndex(stationId); |
| | | if (stationIdx < 0) { |
| | | commandResponse.setMessage("未找到站点状态"); |
| | | return commandResponse; |
| | | } |
| | | |
| | | int slotBaseOffset = stationIdx * TASK_AREA_LENGTH + slotIdx * TASK_AREA_SLOT_SIZE; |
| | | CommandResponse clearTaskNo = sendOriginCommand("DB13." + slotBaseOffset, new short[]{0, 0}); |
| | | if (clearTaskNo == null || !Boolean.TRUE.equals(clearTaskNo.getResult())) { |
| | | commandResponse.setMessage(clearTaskNo == null ? "清空任务号失败" : clearTaskNo.getMessage()); |
| | | return commandResponse; |
| | | } |
| | | |
| | | CommandResponse clearTarget = sendOriginCommand("DB13." + (slotBaseOffset + 6), new short[]{0}); |
| | | if (clearTarget == null || !Boolean.TRUE.equals(clearTarget.getResult())) { |
| | | commandResponse.setMessage(clearTarget == null ? "清空目标站失败" : clearTarget.getMessage()); |
| | | return commandResponse; |
| | | } |
| | | |
| | | commandResponse.setResult(true); |
| | | commandResponse.setMessage("缓存区槽位清理成功"); |
| | | return commandResponse; |
| | | } |
| | | |
| | | @Override |
| | | public synchronized CommandResponse sendOriginCommand(String address, short[] data) { |
| | | CommandResponse commandResponse = new CommandResponse(false); |
| | | if (null == data || data.length == 0) { |
| | |
| | | return getCommand(StationCommandType.MOVE, taskNo, stationId, targetStationId, palletSize, pathLenFactor); |
| | | } |
| | | |
| | | boolean clearPath(Integer taskNo); |
| | | |
| | | CommandResponse sendCommand(StationCommand command); |
| | | |
| | | CommandResponse sendOriginCommand(String address, short[] data); |
| | |
| | | public byte[] readOriginCommand(String address, int length) { |
| | | return zyStationConnectDriver.readOriginCommand(address, length); |
| | | } |
| | | |
| | | @Override |
| | | public boolean clearPath(Integer taskNo) { |
| | | return false; |
| | | } |
| | | } |
| | |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean clearPath(Integer taskNo) { |
| | | return false; |
| | | } |
| | | } |
| | |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean clearPath(Integer taskNo) { |
| | | return false; |
| | | } |
| | | } |
| | |
| | | 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.DeviceConnectPool; |
| | | import com.zy.core.network.ZyStationConnectDriver; |
| | | import com.zy.core.network.entity.ZyStationStatusEntity; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public synchronized boolean clearPath(Integer taskNo) { |
| | | if (taskNo == null || taskNo <= 0) { |
| | | return false; |
| | | } |
| | | if (zyStationConnectDriver == null) { |
| | | return false; |
| | | } |
| | | List<StationProtocol> status = getStatus(); |
| | | if (status == null || status.isEmpty()) { |
| | | return false; |
| | | } |
| | | |
| | | boolean found = false; |
| | | boolean success = true; |
| | | for (StationProtocol stationProtocol : status) { |
| | | List<StationTaskBufferItem> taskBufferItems = stationProtocol == null ? null : stationProtocol.getTaskBufferItems(); |
| | | if (taskBufferItems == null || taskBufferItems.isEmpty()) { |
| | | continue; |
| | | } |
| | | Integer stationId = stationProtocol.getStationId(); |
| | | for (StationTaskBufferItem item : taskBufferItems) { |
| | | if (item == null || !Objects.equals(taskNo, item.getTaskNo())) { |
| | | continue; |
| | | } |
| | | found = true; |
| | | if (!zyStationConnectDriver.clearTaskBufferSlot(stationId, item.getSlotIdx())) { |
| | | success = false; |
| | | log.warn("输送站缓存区残留路径清理失败。stationId={}, slotIdx={}, taskNo={}", |
| | | stationId, item.getSlotIdx(), item.getTaskNo()); |
| | | continue; |
| | | } |
| | | item.setTaskNo(0); |
| | | item.setTargetStaNo(0); |
| | | } |
| | | } |
| | | return found && success; |
| | | } |
| | | |
| | | @Override |
| | | public CommandResponse sendCommand(StationCommand command) { |
| | | CommandResponse commandResponse = null; |
| | | try { |
| | |
| | | |
| | | 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 = (StationThread) SlaveConnection.get(SlaveType.Devp, 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 (complete) { |
| | | attemptClearTaskPath(stationThread, wrkNo); |
| | | completeStationRunTask(wrkMast, targetDeviceNo); |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | |
| | | </label> |
| | | <div class="mc-action-row"> |
| | | <button type="button" class="mc-btn" @click="controlCommand">下发</button> |
| | | <button type="button" class="mc-btn mc-btn-soft" @click="clearPathCommand">清路径</button> |
| | | <button type="button" class="mc-btn mc-btn-soft" @click="resetCommand">复位</button> |
| | | <button type="button" class="mc-btn mc-btn-ghost" @click="openStationTracePage">运行轨迹</button> |
| | | <button v-if="showFakeTraceEntry" type="button" class="mc-btn mc-btn-ghost" @click="openFakeTracePage">仿真轨迹</button> |
| | |
| | | controlCommand: function () { |
| | | this.postControl("/station/command/move", this.controlParam); |
| | | }, |
| | | clearPathCommand: function () { |
| | | this.postControl("/station/command/clearPath", this.controlParam); |
| | | }, |
| | | resetCommand: function () { |
| | | this.postControl("/station/command/reset", this.controlParam); |
| | | } |
| New file |
| | |
| | | package com.zy.asrs.controller; |
| | | |
| | | import com.core.common.R; |
| | | import com.zy.asrs.domain.param.StationCommandMoveParam; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.enums.SlaveType; |
| | | 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.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class StationControllerTest { |
| | | |
| | | @Test |
| | | void commandClearPath_callsThreadClearPath() { |
| | | StationController controller = new StationController(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | StationThread stationThread = mock(StationThread.class); |
| | | |
| | | ReflectionTestUtils.setField(controller, "basDevpService", basDevpService); |
| | | |
| | | 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.clearPath(10335)).thenReturn(true); |
| | | |
| | | StationCommandMoveParam param = new StationCommandMoveParam(); |
| | | param.setStationId(145); |
| | | param.setTaskNo(10335); |
| | | |
| | | SlaveConnection.put(SlaveType.Devp, 1, stationThread); |
| | | try { |
| | | R result = controller.commandClearPath(param); |
| | | |
| | | assertEquals(200, result.get("code")); |
| | | verify(stationThread).clearPath(10335); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.core.thread.impl; |
| | | |
| | | import com.zy.asrs.entity.DeviceConfig; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.model.protocol.StationTaskBufferItem; |
| | | import com.zy.core.network.ZyStationConnectDriver; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | 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.never; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class ZyStationV5ThreadTest { |
| | | |
| | | @Test |
| | | void clearPath_delegatesPureSlotClearingToDriver() { |
| | | DeviceConfig deviceConfig = new DeviceConfig(); |
| | | deviceConfig.setDeviceNo(1); |
| | | RedisUtil redisUtil = mock(RedisUtil.class); |
| | | ZyStationConnectDriver connectDriver = mock(ZyStationConnectDriver.class); |
| | | when(connectDriver.clearTaskBufferSlot(10, 2)).thenReturn(true); |
| | | |
| | | ZyStationV5Thread thread = new ZyStationV5Thread(deviceConfig, redisUtil); |
| | | ReflectionTestUtils.setField(thread, "zyStationConnectDriver", connectDriver); |
| | | |
| | | StationTaskBufferItem hitItem = new StationTaskBufferItem(); |
| | | hitItem.setSlotIdx(2); |
| | | hitItem.setTaskNo(100); |
| | | hitItem.setTargetStaNo(88); |
| | | |
| | | StationProtocol station20 = new StationProtocol(); |
| | | station20.setStationId(20); |
| | | station20.setTaskBufferItems(Collections.emptyList()); |
| | | |
| | | StationProtocol station10 = new StationProtocol(); |
| | | station10.setStationId(10); |
| | | station10.setTaskBufferItems(List.of(hitItem)); |
| | | |
| | | ReflectionTestUtils.setField(thread, "statusList", Arrays.asList(station20, station10)); |
| | | |
| | | boolean result = thread.clearPath(100); |
| | | |
| | | assertTrue(result); |
| | | verify(connectDriver).clearTaskBufferSlot(eq(10), eq(2)); |
| | | verify(redisUtil, never()).set(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.anyLong()); |
| | | verify(redisUtil, never()).del(org.mockito.ArgumentMatchers.any(String[].class)); |
| | | assertEquals(0, hitItem.getTaskNo()); |
| | | assertEquals(0, hitItem.getTargetStaNo()); |
| | | } |
| | | } |
| | |
| | | |
| | | import com.zy.asrs.entity.BasStationOpt; |
| | | 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.BasStationOptService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.asrs.service.WrkAnalysisService; |
| | |
| | | } |
| | | |
| | | @Test |
| | | void stationOutExecuteFinish_attemptsClearPathBeforeCompletingTask() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | 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(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); |
| | | |
| | | 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 { |
| | | utils.stationOutExecuteFinish(); |
| | | |
| | | verify(stationThread, times(1)).clearPath(10335); |
| | | verify(coordinator, times(1)).finishSession(10335); |
| | | verify(wrkMastService, times(1)).updateById(wrkMast); |
| | | assertEquals(WrkStsType.STATION_RUN_COMPLETE.sts, wrkMast.getWrkSts()); |
| | | } finally { |
| | | SlaveConnection.remove(SlaveType.Devp, 1); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void watchCircleStation_usesSessionArrivalStateWhenLegacyCommandMissing() { |
| | | StationOperateProcessUtils utils = new StationOperateProcessUtils(); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |