#
Junjie
3 小时以前 26784989e73fc36c6315e54939d1b13a50eb5020
#
2个文件已添加
13个文件已修改
343 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/StationController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/ZyStationConnectDriver.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/api/ZyStationConnectApi.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/real/ZyStationV4RealConnect.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/StationThread.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationThread.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationV3Thread.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationV4Thread.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationV5Thread.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/DevpCard.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/asrs/controller/StationControllerTest.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/core/thread/impl/ZyStationV5ThreadTest.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/StationController.java
@@ -133,6 +133,29 @@
        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;
src/main/java/com/zy/core/network/ZyStationConnectDriver.java
@@ -186,6 +186,15 @@
        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) {
src/main/java/com/zy/core/network/api/ZyStationConnectApi.java
@@ -16,6 +16,10 @@
    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);//读取原始数据
src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
@@ -10,6 +10,7 @@
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;
@@ -139,6 +140,35 @@
    }
    @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];
    }
src/main/java/com/zy/core/network/real/ZyStationV4RealConnect.java
@@ -274,6 +274,44 @@
    }
    @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) {
src/main/java/com/zy/core/thread/StationThread.java
@@ -40,6 +40,8 @@
        return getCommand(StationCommandType.MOVE, taskNo, stationId, targetStationId, palletSize, pathLenFactor);
    }
    boolean clearPath(Integer taskNo);
    CommandResponse sendCommand(StationCommand command);
    CommandResponse sendOriginCommand(String address, short[] data);
src/main/java/com/zy/core/thread/impl/ZyStationThread.java
@@ -259,4 +259,9 @@
    public byte[] readOriginCommand(String address, int length) {
        return zyStationConnectDriver.readOriginCommand(address, length);
    }
    @Override
    public boolean clearPath(Integer taskNo) {
        return false;
    }
}
src/main/java/com/zy/core/thread/impl/ZyStationV3Thread.java
@@ -480,4 +480,9 @@
        } catch (Exception ignore) {
        }
    }
    @Override
    public boolean clearPath(Integer taskNo) {
        return false;
    }
}
src/main/java/com/zy/core/thread/impl/ZyStationV4Thread.java
@@ -563,4 +563,9 @@
        } catch (Exception ignore) {
        }
    }
    @Override
    public boolean clearPath(Integer taskNo) {
        return false;
    }
}
src/main/java/com/zy/core/thread/impl/ZyStationV5Thread.java
@@ -28,6 +28,7 @@
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;
@@ -354,6 +355,45 @@
    }
    @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 {
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -382,10 +382,11 @@
                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());
@@ -396,6 +397,7 @@
                }
                if (complete) {
                    attemptClearTaskPath(stationThread, wrkNo);
                    completeStationRunTask(wrkMast, targetDeviceNo);
                }
            }
@@ -404,6 +406,20 @@
        }
    }
    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;
src/main/webapp/components/DevpCard.js
@@ -33,6 +33,7 @@
          </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>
@@ -484,6 +485,9 @@
    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);
    }
src/test/java/com/zy/asrs/controller/StationControllerTest.java
New file
@@ -0,0 +1,51 @@
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);
        }
    }
}
src/test/java/com/zy/core/thread/impl/ZyStationV5ThreadTest.java
New file
@@ -0,0 +1,60 @@
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());
    }
}
src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java
@@ -2,8 +2,10 @@
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;
@@ -766,6 +768,53 @@
    }
    @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);