8f444535d40bf13ce7ca2fb0585e1f9b8d089f1a..b74b172daac9b2b21af1149bfcfb935bc879e5ef
2025-12-25 Junjie
#
b74b17 对比 | 目录
2025-12-25 Junjie
#
05bd24 对比 | 目录
2025-12-25 Junjie
#
08cd84 对比 | 目录
2025-12-25 Junjie
#
d8e68f 对比 | 目录
2025-12-25 Junjie
#
a3d444 对比 | 目录
2025-12-25 Junjie
#
ad43be 对比 | 目录
2025-12-25 Junjie
#
2f9849 对比 | 目录
9个文件已添加
31个文件已修改
1741 ■■■■ 已修改文件
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/BasDualCrnpController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/BasDualCrnpErrController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/BasDualCrnpErrLogController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/BasDualCrnpOptController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/BasMapController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/ConsoleController.java 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/DualCrnController.java 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/domain/param/DualCrnCommandParam.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/BasDualCrnp.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/cache/MessageQueue.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/DualCrnForkPosType.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/DualCrnLiftPosType.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/DualCrnModeType.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/DualCrnStatusType.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/DualCrnTaskModeType.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/command/DualCrnCommand.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/protocol/CrnProtocol.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/protocol/RgvProtocol.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/protocol/StationProtocol.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/ZyStationConnectDriver.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/api/ZyStationConnectApi.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/entity/ZyDualCrnStatusEntity.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/real/ZyDualCrnRealConnect.java 259 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/real/ZyStationRealConnect.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/DualCrnThread.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/StationThread.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationThread.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/DevpCard.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/MapCanvas.js 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/WatchCrnCard.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/WatchDualCrnCard.js 299 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/WatchRgvCard.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/watch/console.html 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
@@ -38,7 +38,7 @@
        new Thread(() -> {
            try {
                WcsDiagnosisRequest request = aiUtils.makeAiRequest(1000, "系统当前不执行任务,但具体原因不明,请根据以下信息帮助判断。\n\n");
                WcsDiagnosisRequest request = aiUtils.makeAiRequest(1000, "对当前系统进行巡检,如果有异常情况就进行详细的分析,如果没有异常情况则当成一次检查\n\n");
                wcsDiagnosisService.diagnoseStream(request, emitter);
            } catch (Exception e) {
                emitter.completeWithError(e);
src/main/java/com/zy/asrs/controller/BasDualCrnpController.java
@@ -1,6 +1,5 @@
package com.zy.asrs.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
@@ -78,8 +77,8 @@
    @RequestMapping(value = "/basDualCrnp/delete/auth")
    @ManagerAuth
    public R delete(@RequestParam(value="ids[]") Long[] ids){
         for (Long id : ids){
    public R delete(@RequestParam(value="ids[]") Integer[] ids){
         for (Integer id : ids){
            basDualCrnpService.deleteById(id);
        }
        return R.ok();
src/main/java/com/zy/asrs/controller/BasDualCrnpErrController.java
@@ -78,8 +78,8 @@
    @RequestMapping(value = "/basDualCrnpErr/delete/auth")
    @ManagerAuth
    public R delete(@RequestParam(value="ids[]") Long[] ids){
         for (Long id : ids){
    public R delete(@RequestParam(value="ids[]") Integer[] ids){
         for (Integer id : ids){
            basDualCrnpErrService.deleteById(id);
        }
        return R.ok();
src/main/java/com/zy/asrs/controller/BasDualCrnpErrLogController.java
@@ -78,8 +78,8 @@
    @RequestMapping(value = "/basDualCrnpErrLog/delete/auth")
    @ManagerAuth
    public R delete(@RequestParam(value="ids[]") Long[] ids){
         for (Long id : ids){
    public R delete(@RequestParam(value="ids[]") Integer[] ids){
         for (Integer id : ids){
            basDualCrnpErrLogService.deleteById(id);
        }
        return R.ok();
src/main/java/com/zy/asrs/controller/BasDualCrnpOptController.java
@@ -78,8 +78,8 @@
    @RequestMapping(value = "/basDualCrnpOpt/delete/auth")
    @ManagerAuth
    public R delete(@RequestParam(value="ids[]") Long[] ids){
         for (Long id : ids){
    public R delete(@RequestParam(value="ids[]") Integer[] ids){
         for (Integer id : ids){
            basDualCrnpOptService.deleteById(id);
        }
        return R.ok();
src/main/java/com/zy/asrs/controller/BasMapController.java
@@ -198,6 +198,9 @@
                    }else if (nodeType.equals("RGB(255,192,0)")) {
                        //堆垛机
                        nodeData.put("type", "crn");
                    }else if (nodeType.equals("RGB(255,255,0)")) {
                        //双工位堆垛机
                        nodeData.put("type", "dualCrn");
                    }else if (nodeType.equals("RGB(0,112,192)")) {
                        //输送线
                        nodeData.put("type", "devp");
src/main/java/com/zy/asrs/controller/ConsoleController.java
@@ -20,15 +20,13 @@
import com.zy.common.CodeRes;
import com.zy.common.utils.RedisUtil;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.CrnModeType;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.RgvStatusType;
import com.zy.core.enums.SlaveType;
import com.zy.core.enums.WrkIoType;
import com.zy.core.enums.*;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.properties.SystemProperties;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.DualCrnThread;
import com.zy.core.thread.StationThread;
import com.zy.core.thread.RgvThread;
import com.zy.core.model.protocol.RgvProtocol;
@@ -205,6 +203,39 @@
        return R.ok().add(vos);
    }
    @PostMapping("/latest/data/dualcrn")
    @ManagerAuth(memo = "双工位堆垛机实时数据")
    public R dualCrnLatestData() {
        List<CrnLatestDataVo> vos = new ArrayList<>();
        List<DeviceConfig> dualCrnList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
                .eq("device_type", String.valueOf(SlaveType.DualCrn)));
        for (DeviceConfig deviceConfig : dualCrnList) {
            DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, deviceConfig.getDeviceNo());
            if (crnThread == null) { continue; }
            DualCrnProtocol p = crnThread.getStatus();
            if (p == null) { continue; }
            CrnLatestDataVo vo = new CrnLatestDataVo();
            vo.setCrnId(p.getCrnNo());
            Integer bay = p.getBay() != null ? p.getBay() : p.getBayTwo();
            vo.setOffset(bay == null ? 0.0 : Double.valueOf(bay));
            vo.setBay(bay);
            Integer taskNo = (p.getTaskNo() != null && p.getTaskNo() > 0) ? p.getTaskNo()
                    : ((p.getTaskNoTwo() != null && p.getTaskNoTwo() > 0) ? p.getTaskNoTwo() : 0);
            vo.setTaskNo(taskNo);
            if (p.getAlarm() != null && p.getAlarm() > 0) {
                vo.setCrnStatus(CrnStatusType.MACHINE_ERROR);
            } else {
                if (taskNo != null && taskNo > 0) {
                    vo.setCrnStatus(p.getModeType() == DualCrnModeType.AUTO ? CrnStatusType.MACHINE_AUTO : CrnStatusType.MACHINE_UN_AUTO);
                } else {
                    vo.setCrnStatus(p.getModeType() == DualCrnModeType.AUTO ? CrnStatusType.MACHINE_AUTO : CrnStatusType.MACHINE_UN_AUTO);
                }
            }
            vos.add(vo);
        }
        return R.ok().add(vos);
    }
    @PostMapping("/latest/data/rgv")
    @ManagerAuth(memo = "RGV实时数据")
    public R rgvLatestData(){
src/main/java/com/zy/asrs/controller/DualCrnController.java
New file
@@ -0,0 +1,184 @@
package com.zy.asrs.controller;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.annotations.ManagerAuth;
import com.core.common.Cools;
import com.core.common.R;
import com.zy.asrs.domain.param.DualCrnCommandParam;
import com.zy.asrs.domain.vo.DualCrnStateTableVo;
import com.zy.asrs.entity.BasDualCrnp;
import com.zy.asrs.service.BasDualCrnpService;
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.DualCrnCommand;
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.thread.DualCrnThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
public class DualCrnController {
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    @PostMapping("/dualcrn/table/crn/state")
    @ManagerAuth(memo = "双工位堆垛机信息表")
    public R dualCrnStateTable() {
        List<DualCrnStateTableVo> list = new ArrayList<>();
        List<BasDualCrnp> crnps = basDualCrnpService.selectList(new EntityWrapper<BasDualCrnp>().orderBy("crn_no"));
        for (BasDualCrnp basDualCrnp : crnps) {
            DualCrnStateTableVo vo = new DualCrnStateTableVo();
            vo.setCrnNo(basDualCrnp.getCrnNo());
            list.add(vo);
            DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, basDualCrnp.getCrnNo());
            if (crnThread == null) {
                continue;
            }
            DualCrnProtocol p = crnThread.getStatus();
            if (p == null) {
                continue;
            }
            vo.setTaskNo(p.getTaskNo());
            vo.setTaskNoTwo(p.getTaskNoTwo());
            vo.setMode(p.getModeType() == null ? "-" : p.getModeType().desc);
            vo.setStatus(p.getStatusType() == null ? "-" : p.getStatusType().desc);
            vo.setStatusTwo(p.getStatusTypeTwo() == null ? "-" : p.getStatusTypeTwo().desc);
            vo.setLoading(p.getLoaded() != null && p.getLoaded() == 1 ? "有物" : "无物");
            vo.setLoadingTwo(p.getLoadedTwo() != null && p.getLoadedTwo() == 1 ? "有物" : "无物");
            vo.setBay(p.getBay());
            vo.setBayTwo(p.getBayTwo());
            vo.setLev(p.getLevel());
            vo.setLevTwo(p.getLevelTwo());
            vo.setForkOffset(p.getForkPosType() == null ? "-" : p.getForkPosType().desc);
            vo.setForkOffsetTwo(p.getForkPosTypeTwo() == null ? "-" : p.getForkPosTypeTwo().desc);
            vo.setLiftPos(p.getLiftPosType() == null ? "-" : p.getLiftPosType().desc);
            vo.setLiftPosTwo(p.getLiftPosTypeTwo() == null ? "-" : p.getLiftPosTypeTwo().desc);
            vo.setWalkPos(p.getWalkPos() != null && p.getWalkPos() == 0 ? "在定位" : "不在定位");
            vo.setWalkPosTwo(p.getWalkPosTwo() != null && p.getWalkPosTwo() == 0 ? "在定位" : "不在定位");
            vo.setTaskReceive(p.getTaskReceive() != null && p.getTaskReceive() == 1 ? "接收" : "无任务");
            vo.setTaskReceiveTwo(p.getTaskReceiveTwo() != null && p.getTaskReceiveTwo() == 1 ? "接收" : "无任务");
            vo.setXspeed(p.getXSpeed());
            vo.setYspeed(p.getYSpeed());
            vo.setZspeed(p.getZSpeed());
            vo.setXdistance(p.getXDistance());
            vo.setYdistance(p.getYDistance());
            vo.setXduration(p.getXDuration());
            vo.setYduration(p.getYDuration());
            vo.setWarnCode(p.getAlarm() == null ? "-" : String.valueOf(p.getAlarm()));
            if (p.getAlarm() != null && p.getAlarm() > 0) {
                vo.setDeviceStatus("ERROR");
            } else if (p.getTaskNo() != null && p.getTaskNo() > 0 || p.getTaskNoTwo() != null && p.getTaskNoTwo() > 0) {
                vo.setDeviceStatus("WORKING");
            } else if (p.getModeType() != null && "AUTO".equals(p.getModeType().name())) {
                vo.setDeviceStatus("AUTO");
            } else {
                vo.setDeviceStatus("OFFLINE");
            }
        }
        return R.ok().add(list);
    }
    @PostMapping("/dualcrn/command/take")
    @ManagerAuth(memo = "双工位堆垛机取放货命令")
    public R dualCrnCommandTake(@RequestBody DualCrnCommandParam param) {
        if (Cools.isEmpty(param)) {
            return R.error("缺少参数");
        }
        Integer crnNo = param.getCrnNo();
        String sourceLocNo = param.getSourceLocNo();
        String targetLocNo = param.getTargetLocNo();
        Integer station = param.getStation();
        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
        if (crnThread == null) {
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getPickAndPutCommand(sourceLocNo, targetLocNo, 9999, crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        return R.ok();
    }
    @PostMapping("/dualcrn/command/pick")
    @ManagerAuth(memo = "双工位堆垛机取货命令")
    public R dualCrnCommandPick(@RequestBody DualCrnCommandParam param) {
        if (Cools.isEmpty(param)) {
            return R.error("缺少参数");
        }
        Integer crnNo = param.getCrnNo();
        String targetLocNo = param.getTargetLocNo();
        Integer station = param.getStation();
        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
        if (crnThread == null) {
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getPickCommand(targetLocNo, 9999, crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        return R.ok();
    }
    @PostMapping("/dualcrn/command/put")
    @ManagerAuth(memo = "双工位堆垛机放货命令")
    public R dualCrnCommandPut(@RequestBody DualCrnCommandParam param) {
        if (Cools.isEmpty(param)) {
            return R.error("缺少参数");
        }
        Integer crnNo = param.getCrnNo();
        String targetLocNo = param.getTargetLocNo();
        Integer station = param.getStation();
        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
        if (crnThread == null) {
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getPutCommand(targetLocNo, 9999, crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        return R.ok();
    }
    @PostMapping("/dualcrn/command/move")
    @ManagerAuth(memo = "双工位堆垛机移动命令")
    public R dualCrnCommandMove(@RequestBody DualCrnCommandParam param) {
        if (Cools.isEmpty(param)) {
            return R.error("缺少参数");
        }
        Integer crnNo = param.getCrnNo();
        String targetLocNo = param.getTargetLocNo();
        Integer station = param.getStation();
        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
        if (crnThread == null) {
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getMoveCommand(targetLocNo, 9999, crnNo);
        if (station != null) {
            command.setStation(station.shortValue());
        }
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        return R.ok();
    }
    @PostMapping("/dualcrn/command/taskComplete")
    @ManagerAuth(memo = "双工位堆垛机任务完成确认")
    public R dualCrnCommandTaskComplete(@RequestBody DualCrnCommandParam param) {
        if (Cools.isEmpty(param)) {
            return R.error("缺少参数");
        }
        Integer crnNo = param.getCrnNo();
        Integer station = param.getStation();
        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
        if (crnThread == null) {
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getResetCommand(crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        return R.ok();
    }
}
src/main/java/com/zy/asrs/domain/param/DualCrnCommandParam.java
New file
@@ -0,0 +1,11 @@
package com.zy.asrs.domain.param;
import lombok.Data;
@Data
public class DualCrnCommandParam {
    private Integer crnNo;
    private String sourceLocNo;
    private String targetLocNo;
    private Integer station;
}
src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java
New file
@@ -0,0 +1,65 @@
package com.zy.asrs.domain.vo;
import lombok.Data;
@Data
public class DualCrnStateTableVo {
    private Integer crnNo;
    private String mode = "-";
    private String warnCode = "-";
    private Integer taskNo = 0;
    private Integer taskNoTwo = 0;
    private String status = "-";
    private String statusTwo = "-";
    private String loading = "-";
    private String loadingTwo = "-";
    private Integer bay;
    private Integer bayTwo;
    private Integer lev;
    private Integer levTwo;
    private String forkOffset = "-";
    private String forkOffsetTwo = "-";
    private String liftPos = "-";
    private String liftPosTwo = "-";
    private String walkPos = "-";
    private String walkPosTwo = "-";
    private String taskReceive = "-";
    private String taskReceiveTwo = "-";
    private Integer xspeed = 0;
    private Integer yspeed = 0;
    private Integer zspeed = 0;
    private Integer xdistance = 0;
    private Integer ydistance = 0;
    private Integer xduration = 0;
    private Integer yduration = 0;
    private String deviceStatus = "OFFLINE";
}
src/main/java/com/zy/asrs/entity/BasDualCrnp.java
@@ -6,8 +6,6 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
@@ -103,10 +103,15 @@
            } else if ("/console/latest/data/rgv".equals(url)) {
                ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
                resObj = consoleController.rgvLatestData();
            } else if ("/console/latest/data/dualcrn".equals(url)) {
                ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
                resObj = consoleController.dualCrnLatestData();
            } else if ("/crn/table/crn/state".equals(url)) {
                resObj = SpringUtils.getBean(CrnController.class).crnStateTable();
            } else if ("/rgv/table/rgv/state".equals(url)) {
                resObj = SpringUtils.getBean(RgvController.class).rgvStateTable();
            } else if ("/dualcrn/table/crn/state".equals(url)) {
                resObj = SpringUtils.getBean(com.zy.asrs.controller.DualCrnController.class).dualCrnStateTable();
            }
        } catch (Exception e) {
            R err = R.error(e.getMessage());
src/main/java/com/zy/core/cache/MessageQueue.java
@@ -16,6 +16,8 @@
    // 堆垛机mq交换机
    private static final Map<Integer, LinkedBlockingQueue<Task>> CRN_EXCHANGE = new ConcurrentHashMap<>();
    // 双工位堆垛机mq交换机
    private static final Map<Integer, LinkedBlockingQueue<Task>> DUAL_CRN_EXCHANGE = new ConcurrentHashMap<>();
    // 输送线mq交换机
    private static final Map<Integer, ConcurrentLinkedQueue<Task>> DEVP_EXCHANGE = new ConcurrentHashMap<>();
    // 条码扫描仪mq交换机
@@ -34,6 +36,9 @@
        switch (type) {
            case Crn:
                CRN_EXCHANGE.put(id, new LinkedBlockingQueue<>(1));
                break;
            case DualCrn:
                DUAL_CRN_EXCHANGE.put(id, new LinkedBlockingQueue<>(1));
                break;
            case Rgv:
                RGV_EXCHANGE.put(id, new LinkedBlockingQueue<>(1));
@@ -63,6 +68,8 @@
        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:
@@ -86,6 +93,8 @@
        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:
@@ -108,6 +117,8 @@
        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:
@@ -128,6 +139,9 @@
            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;
src/main/java/com/zy/core/enums/DualCrnForkPosType.java
New file
@@ -0,0 +1,43 @@
package com.zy.core.enums;
public enum DualCrnForkPosType {
    NONE(-1, "不在定位"),   // 货叉原位
    HOME(0, "货叉原位"),   // 货叉原位
    LEFT(1, "货叉在左侧"),  // 货叉在左侧
    RIGHT(2, "货叉在右侧"),   // 货叉在右侧
    _LEFT(3, "货叉在左侧远"),   // 货叉在右侧远
    _RIGHT(4, "货叉在右侧远"),   // 货叉在右侧远
    ;
    public Integer id;
    public String desc;
    DualCrnForkPosType(Integer id, String desc) {
        this.id = id;
        this.desc = desc;
    }
    public static DualCrnForkPosType get(Integer id) {
        if (null == id) {
            return null;
        }
        for (DualCrnForkPosType type : DualCrnForkPosType.values()) {
            if (type.id.equals(id)) {
                return type;
            }
        }
        return null;
    }
    public static DualCrnForkPosType get(DualCrnForkPosType type) {
        if (null == type) {
            return null;
        }
        for (DualCrnForkPosType crnForkPosType : DualCrnForkPosType.values()) {
            if (crnForkPosType == type) {
                return crnForkPosType;
            }
        }
        return null;
    }
}
src/main/java/com/zy/core/enums/DualCrnLiftPosType.java
New file
@@ -0,0 +1,44 @@
package com.zy.core.enums;
public enum DualCrnLiftPosType {
    ERROR(-1, "未知"),   // 不在定位
    NONE(0, "不在定位"),   // 不在定位
    _DOWN(1, "双深低位"),  //
    DOWN(2, "单深低位"),  //
    _UP(3, "单深高位"),   //
    UP(4, "双深高位"),   //
    ;
    public Integer id;
    public String desc;
    DualCrnLiftPosType(Integer id, String desc) {
        this.id = id;
        this.desc = desc;
    }
    public static DualCrnLiftPosType get(Integer id) {
        if (null == id) {
            return null;
        }
        for (DualCrnLiftPosType type : DualCrnLiftPosType.values()) {
            if (type.id.equals(id)) {
                return type;
            }
        }
        return null;
    }
    public static DualCrnLiftPosType get(DualCrnLiftPosType type) {
        if (null == type) {
            return null;
        }
        for (DualCrnLiftPosType crnLiftPosType : DualCrnLiftPosType.values()) {
            if (crnLiftPosType == type) {
                return crnLiftPosType;
            }
        }
        return null;
    }
}
src/main/java/com/zy/core/enums/DualCrnModeType.java
New file
@@ -0,0 +1,42 @@
package com.zy.core.enums;
public enum DualCrnModeType {
    NONE(-1, "离线"),
    STOP(0, "维修"),
    HAND(1, "手动"),
    HALF_AUTO(2, "半自动"),
    AUTO(3, "自动"),
    ;
    public Integer id;
    public String desc;
    DualCrnModeType(Integer id, String desc) {
        this.id = id;
        this.desc = desc;
    }
    public static DualCrnModeType get(Integer id) {
        if (null == id) {
            return null;
        }
        for (DualCrnModeType type : DualCrnModeType.values()) {
            if (type.id.equals(id)) {
                return type;
            }
        }
        return null;
    }
    public static DualCrnModeType get(DualCrnModeType type) {
        if (null == type) {
            return null;
        }
        for (DualCrnModeType crnModeType : DualCrnModeType.values()) {
            if (crnModeType == type) {
                return crnModeType;
            }
        }
        return null;
    }
}
src/main/java/com/zy/core/enums/DualCrnStatusType.java
New file
@@ -0,0 +1,52 @@
package com.zy.core.enums;
public enum DualCrnStatusType {
    NONE(-1, "离线"),
    IDLE(0, "空闲"),
    FETCH_MOVING(1, "取货行走"),
    FETCH_WAITING(2, "取货等待"),
    FETCHING(3, "取货中"),
    PUT_MOVING(4, "放货走行"),
    PUT_WAITING(5, "放货等待"),
    PUTTING(6, "放货中"),
    ORIGIN_GO(7, "回原点"),
    ORIGIN_BACK(8, "回反原点"),
    MOVING(9, "走行中"),
    WAITING(90, "任务完成等待WCS确认"),
    PAUSE(11, "任务暂停"),
    SOS(99, "报警"),
    UNKNOW(100, "其他"),
    ;
    public Integer id;
    public String desc;
    DualCrnStatusType(Integer id, String desc) {
        this.id = id;
        this.desc = desc;
    }
    public static DualCrnStatusType get(Integer id) {
        if (null == id) {
            return null;
        }
        for (DualCrnStatusType type : DualCrnStatusType.values()) {
            if (type.id.equals(id)) {
                return type;
            }
        }
        return NONE;
    }
    public static DualCrnStatusType get(DualCrnStatusType type) {
        if (null == type) {
            return null;
        }
        for (DualCrnStatusType crnStatusType : DualCrnStatusType.values()) {
            if (crnStatusType == type) {
                return crnStatusType;
            }
        }
        return null;
    }
}
src/main/java/com/zy/core/enums/DualCrnTaskModeType.java
New file
@@ -0,0 +1,42 @@
package com.zy.core.enums;
public enum DualCrnTaskModeType {
    NONE(0),    // 无
    PICK(1),    // 取货
    PUT(2),    // 放货
    TRANSFER(3),    // 取放货
    MOVE(4),    // 移动
    CONFIRM(5),    // 确认
    ;
    public Integer id;
    DualCrnTaskModeType(Integer id) {
        this.id = id;
    }
    public static DualCrnTaskModeType get(Integer id) {
        if (null == id) {
            return null;
        }
        for (DualCrnTaskModeType type : DualCrnTaskModeType.values()) {
            if (type.id.equals(id)) {
                return type;
            }
        }
        return null;
    }
    public static DualCrnTaskModeType get(DualCrnTaskModeType type) {
        if (null == type) {
            return null;
        }
        for (DualCrnTaskModeType crnTaskModeType : DualCrnTaskModeType.values()) {
            if (crnTaskModeType == type) {
                return crnTaskModeType;
            }
        }
        return null;
    }
}
src/main/java/com/zy/core/model/command/DualCrnCommand.java
@@ -11,24 +11,11 @@
    // 堆垛机号
    private Integer crnNo = 0;
    // 任务完成确认位
    private Short ackFinish = 0;
    // 任务号
    private Short taskNo = 0;
    /**
     * 任务模式:
     * 0 = 无
     * 1 = 入库   源和目标都发
     * 2 = 出库   源和目标都发
     * 3 = 库位移转 源和目标都发
     * 4 = 站位移转 源和目标都发
     * 5 = 回原点  不用发
     * 6 = 去反原点 目标发
     * 7 = 坐标移行 取货发
     * 90 = 设置时间
     * 99 = 取消当前任务
     */
    private Short taskMode = 0;
src/main/java/com/zy/core/model/protocol/CrnProtocol.java
@@ -6,6 +6,8 @@
import com.zy.core.enums.CrnStatusType;
import lombok.Data;
import java.util.Map;
@Data
public class CrnProtocol {
@@ -147,6 +149,16 @@
    private Integer crnLane = 1;
    /**
     * 称重数据
     */
    private Double weight;
    /**
     * 条码数据
     */
    private String barcode;
    /**
     * 日志采集时间
     */
    private Long deviceDataLog = System.currentTimeMillis();
@@ -156,6 +168,11 @@
     */
    private Long lastCommandTime = System.currentTimeMillis();
    /**
     * 扩展数据
     */
    private Map<String, Object> extend;
    public void setMode(Integer mode) {
        this.mode = mode;
        this.modeType = CrnModeType.get(mode);
src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java
@@ -1,10 +1,13 @@
package com.zy.core.model.protocol;
import com.zy.core.enums.CrnForkPosType;
import com.zy.core.enums.CrnLiftPosType;
import com.zy.core.enums.CrnModeType;
import com.zy.core.enums.CrnStatusType;
import com.zy.core.enums.DualCrnForkPosType;
import com.zy.core.enums.DualCrnLiftPosType;
import com.zy.core.enums.DualCrnModeType;
import com.zy.core.enums.DualCrnStatusType;
import lombok.Data;
import java.util.Map;
@Data
public class DualCrnProtocol {
@@ -18,7 +21,7 @@
     */
    public Integer mode;
    public CrnModeType modeType;
    public DualCrnModeType modeType;
    /**
     * 异常码
@@ -58,12 +61,12 @@
    /**
     * 工位1状态枚举
     */
    public CrnStatusType statusType;
    public DualCrnStatusType statusType;
    /**
     * 工位2状态枚举
     */
    public CrnStatusType statusTypeTwo;
    public DualCrnStatusType statusTypeTwo;
    /**
     * 工位1堆垛机当前列号
@@ -101,9 +104,9 @@
     */
    public Integer forkPosTwo;
    public CrnForkPosType forkPosType;
    public DualCrnForkPosType forkPosType;
    public CrnForkPosType forkPosTypeTwo;
    public DualCrnForkPosType forkPosTypeTwo;
    /**
     * 当前载货台位置
@@ -114,9 +117,9 @@
    public Integer liftPosTwo;
    public CrnLiftPosType liftPosType;
    public DualCrnLiftPosType liftPosType;
    public CrnLiftPosType liftPosTypeTwo;
    public DualCrnLiftPosType liftPosTypeTwo;
    /**
     * 走行在定位
@@ -133,6 +136,15 @@
    public Integer loaded;
    public Integer loadedTwo;
    /**
     * 任务接收状态
     * 0 = 未接收
     * 1 = 已接收
     */
    public Integer taskReceive;
    public Integer taskReceiveTwo;
    private Integer temp1;
@@ -199,44 +211,79 @@
     */
    private Long lastCommandTime = System.currentTimeMillis();
    /**
     * 扩展数据
     */
    private Map<String, Object> extend;
    public void setMode(Integer mode) {
        this.mode = mode;
        this.modeType = CrnModeType.get(mode);
        this.modeType = DualCrnModeType.get(mode);
    }
    public void setMode(CrnModeType type) {
    public void setMode(DualCrnModeType type) {
        this.modeType = type;
        this.mode = CrnModeType.get(type).id;
        this.mode = DualCrnModeType.get(type).id;
    }
    public void setForkPos(Integer forkPos) {
        this.forkPos = forkPos;
        this.forkPosType = CrnForkPosType.get(forkPos);
        this.forkPosType = DualCrnForkPosType.get(forkPos);
    }
    public void setForkPos(CrnForkPosType type) {
    public void setForkPosTwo(Integer forkPosTwo) {
        this.forkPosTwo = forkPosTwo;
        this.forkPosTypeTwo = DualCrnForkPosType.get(forkPosTwo);
    }
    public void setForkPos(DualCrnForkPosType type) {
        this.forkPosType = type;
        this.forkPos = CrnForkPosType.get(type).id;
        this.forkPos = DualCrnForkPosType.get(type).id;
    }
    public void setForkPosTwo(DualCrnForkPosType type) {
        this.forkPosTypeTwo = type;
        this.forkPosTwo = DualCrnForkPosType.get(type).id;
    }
    public void setLiftPos(Integer liftPos) {
        this.liftPos = liftPos;
        this.liftPosType = CrnLiftPosType.get(liftPos);
        this.liftPosType = DualCrnLiftPosType.get(liftPos);
    }
    public void setLiftPos(CrnLiftPosType type) {
    public void setLiftPosTwo(Integer liftPosTwo) {
        this.liftPosTwo = liftPosTwo;
        this.liftPosTypeTwo = DualCrnLiftPosType.get(liftPosTwo);
    }
    public void setLiftPos(DualCrnLiftPosType type) {
        this.liftPosType = type;
        this.liftPos = CrnLiftPosType.get(type).id;
        this.liftPos = DualCrnLiftPosType.get(type).id;
    }
    public void setLiftPosTwo(DualCrnLiftPosType type) {
        this.liftPosTypeTwo = type;
        this.liftPosTwo = DualCrnLiftPosType.get(type).id;
    }
    public void setStatus(Integer status){
        this.status = status;
        this.statusType = CrnStatusType.get(status);
        this.statusType = DualCrnStatusType.get(status);
    }
    public void setStatus(CrnStatusType type){
    public void setStatus(DualCrnStatusType type){
        this.statusType = type;
        this.status = CrnStatusType.get(type).id;
        this.status = DualCrnStatusType.get(type).id;
    }
    public void setStatusTwo(Integer statusTwo){
        this.statusTwo = statusTwo;
        this.statusTypeTwo = DualCrnStatusType.get(statusTwo);
    }
    public void setStatusTwo(DualCrnStatusType type){
        this.statusTypeTwo = type;
        this.statusTwo = DualCrnStatusType.get(type).id;
    }
}
src/main/java/com/zy/core/model/protocol/RgvProtocol.java
@@ -2,6 +2,8 @@
import lombok.Data;
import java.util.Map;
@Data
public class RgvProtocol {
@@ -22,4 +24,9 @@
    private Long deviceDataLog = System.currentTimeMillis();
    
    private Long lastCommandTime = System.currentTimeMillis();
    /**
     * 扩展数据
     */
    private Map<String, Object> extend;
}
src/main/java/com/zy/core/model/protocol/StationProtocol.java
@@ -2,6 +2,8 @@
import lombok.Data;
import java.util.Map;
@Data
public class StationProtocol {
@@ -50,6 +52,11 @@
    //重量
    private Double weight;
   /**
    * 扩展数据
    */
   private Map<String, Object> extend;
    @Override
    public StationProtocol clone() {
        try {
src/main/java/com/zy/core/network/ZyStationConnectDriver.java
@@ -104,4 +104,12 @@
    public CommandResponse sendCommand(StationCommand command) {
        return zyStationConnectApi.sendCommand(deviceConfig.getDeviceNo(), command);
    }
    public CommandResponse sendOriginCommand(String address, short[] data) {
        return zyStationConnectApi.sendOriginCommand(address, data);
    }
    public byte[] readOriginCommand(String address, int length) {
        return zyStationConnectApi.readOriginCommand(address, length);
    }
}
src/main/java/com/zy/core/network/api/ZyStationConnectApi.java
@@ -16,4 +16,7 @@
    CommandResponse sendCommand(Integer deviceNo, StationCommand command);//下发命令
    CommandResponse sendOriginCommand(String address, short[] data);//原始命令
    byte[] readOriginCommand(String address, int length);//读取原始数据
}
src/main/java/com/zy/core/network/entity/ZyDualCrnStatusEntity.java
@@ -96,6 +96,15 @@
    public Integer loadedTwo;
    /**
     * 任务接收状态
     * 0 = 未接收
     * 1 = 已接收
     */
    public Integer taskReceive;
    public Integer taskReceiveTwo;
    /**
     * X行走线速度m/min
     */
    private Integer xSpeed;
src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
@@ -3,7 +3,7 @@
import com.alibaba.fastjson.JSON;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.core.enums.CrnStatusType;
import com.zy.core.enums.CrnTaskModeType;
import com.zy.core.enums.DualCrnTaskModeType;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.command.DualCrnCommand;
import com.zy.core.network.api.ZyDualCrnConnectApi;
@@ -42,13 +42,19 @@
    @Override
    public CommandResponse sendCommand(DualCrnCommand command) {
        CommandResponse response = new CommandResponse(false);
        if (command.getTaskMode().intValue() == CrnTaskModeType.LOC_MOVE.id) {
        if (command.getTaskMode().intValue() == DualCrnTaskModeType.TRANSFER.id) {
            //取放货
            executor.submit(() -> commandTake(command));
        } else if (command.getTaskMode().intValue() == CrnTaskModeType.CRN_MOVE.id) {
        } else if (command.getTaskMode().intValue() == DualCrnTaskModeType.PICK.id) {
            //取货
            executor.submit(() -> commandPick(command));
        } else if (command.getTaskMode().intValue() == DualCrnTaskModeType.PUT.id) {
            //放货
            executor.submit(() -> commandPut(command));
        } else if (command.getTaskMode().intValue() == DualCrnTaskModeType.MOVE.id) {
            //移动
            executor.submit(() -> commandMove(command));
        } else if (command.getTaskMode().intValue() == CrnTaskModeType.NONE.id) {
        } else if (command.getTaskMode().intValue() == DualCrnTaskModeType.CONFIRM.id) {
            //复位
            executor.submit(() -> commandTaskComplete(command));
        }
@@ -57,8 +63,15 @@
    }
    private void commandTaskComplete(DualCrnCommand command) {
        this.crnStatus.setTaskNo(0);
        this.crnStatus.setStatus(CrnStatusType.IDLE.id);
        if(command.getStation() == 1) {
            this.crnStatus.setTaskNo(0);
            this.crnStatus.setTaskReceive(0);
            this.crnStatus.setStatus(CrnStatusType.IDLE.id);
        }else {
            this.crnStatus.setTaskNoTwo(0);
            this.crnStatus.setTaskReceiveTwo(0);
            this.crnStatus.setStatusTwo(CrnStatusType.IDLE.id);
        }
    }
    private void commandMove(DualCrnCommand command) {
@@ -71,12 +84,14 @@
        if(command.getStation() == 1) {
            this.crnStatus.setTaskNo(taskNo);
            this.crnStatus.setStatus(CrnStatusType.MOVING.id);
            this.crnStatus.setTaskReceive(1);
            moveY(this.crnStatus.getBay(), destinationPosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevel(), destinationPosZ, command.getStation().intValue());
            this.crnStatus.setStatus(CrnStatusType.WAITING.id);
        }else {
            this.crnStatus.setTaskNoTwo(taskNo);
            this.crnStatus.setStatusTwo(CrnStatusType.MOVING.id);
            this.crnStatus.setTaskReceive(1);
            moveY(this.crnStatus.getBayTwo(), destinationPosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevelTwo(), destinationPosZ, command.getStation().intValue());
            this.crnStatus.setStatusTwo(CrnStatusType.WAITING.id);
@@ -93,10 +108,10 @@
        int taskMode = command.getTaskMode().intValue();
        int taskNo = command.getTaskNo().intValue();
        this.crnStatus.setMode(taskMode);
        if(command.getStation() == 1) {
            this.crnStatus.setTaskNo(taskNo);
            this.crnStatus.setStatus(CrnStatusType.FETCH_MOVING.id);
            this.crnStatus.setTaskReceive(1);
            moveY(this.crnStatus.getBay(), sourcePosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevel(), sourcePosZ, command.getStation().intValue());
@@ -120,6 +135,7 @@
        }else {
            this.crnStatus.setTaskNoTwo(taskNo);
            this.crnStatus.setStatusTwo(CrnStatusType.FETCH_MOVING.id);
            this.crnStatus.setTaskReceiveTwo(1);
            moveY(this.crnStatus.getBayTwo(), sourcePosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevelTwo(), sourcePosZ, command.getStation().intValue());
@@ -143,6 +159,82 @@
        }
    }
    private void commandPick(DualCrnCommand command) {
        int destinationPosX = command.getDestinationPosX().intValue();
        int destinationPosY = command.getDestinationPosY().intValue();
        int destinationPosZ = command.getDestinationPosZ().intValue();
        int taskMode = command.getTaskMode().intValue();
        int taskNo = command.getTaskNo().intValue();
        if(command.getStation() == 1) {
            this.crnStatus.setTaskNo(taskNo);
            this.crnStatus.setStatus(CrnStatusType.FETCH_MOVING.id);
            this.crnStatus.setTaskReceive(1);
            moveY(this.crnStatus.getBay(), destinationPosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevel(), destinationPosZ, command.getStation().intValue());
            this.crnStatus.setStatus(CrnStatusType.FETCHING.id);
            sleep(2000);
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
            this.crnStatus.setLoaded(1);
            this.crnStatus.setStatus(CrnStatusType.WAITING.id);
        }else {
            this.crnStatus.setTaskNoTwo(taskNo);
            this.crnStatus.setStatusTwo(CrnStatusType.FETCH_MOVING.id);
            this.crnStatus.setTaskReceiveTwo(1);
            moveY(this.crnStatus.getBayTwo(), destinationPosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevelTwo(), destinationPosZ, command.getStation().intValue());
            this.crnStatus.setStatusTwo(CrnStatusType.FETCHING.id);
            sleep(2000);
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
            this.crnStatus.setLoadedTwo(1);
            this.crnStatus.setStatusTwo(CrnStatusType.WAITING.id);
        }
    }
    private void commandPut(DualCrnCommand command) {
        int destinationPosX = command.getDestinationPosX().intValue();
        int destinationPosY = command.getDestinationPosY().intValue();
        int destinationPosZ = command.getDestinationPosZ().intValue();
        int taskMode = command.getTaskMode().intValue();
        int taskNo = command.getTaskNo().intValue();
        if(command.getStation() == 1) {
            this.crnStatus.setTaskNo(taskNo);
            this.crnStatus.setStatus(CrnStatusType.PUT_MOVING.id);
            this.crnStatus.setTaskReceive(1);
            moveY(this.crnStatus.getBay(), destinationPosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevel(), destinationPosZ, command.getStation().intValue());
            this.crnStatus.setStatus(CrnStatusType.PUTTING.id);
            sleep(2000);
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
            this.crnStatus.setLoaded(0);
            this.crnStatus.setStatus(CrnStatusType.WAITING.id);
        }else {
            this.crnStatus.setTaskNoTwo(taskNo);
            this.crnStatus.setStatusTwo(CrnStatusType.PUT_MOVING.id);
            this.crnStatus.setTaskReceiveTwo(1);
            moveY(this.crnStatus.getBayTwo(), destinationPosY, command.getStation().intValue());
            moveZ(this.crnStatus.getLevelTwo(), destinationPosZ, command.getStation().intValue());
            this.crnStatus.setStatusTwo(CrnStatusType.PUTTING.id);
            sleep(2000);
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
            this.crnStatus.setLoadedTwo(0);
            this.crnStatus.setStatusTwo(CrnStatusType.WAITING.id);
        }
    }
    private void moveZ(int sourcePosZ, int destinationPosZ, int station) {
        if(destinationPosZ - sourcePosZ > 0) {
            int moveLength = destinationPosZ - sourcePosZ;
src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
@@ -101,6 +101,16 @@
        return new CommandResponse(true, "命令已受理(异步执行)");
    }
    @Override
    public CommandResponse sendOriginCommand(String address, short[] data) {
        return new CommandResponse(true, "原始命令已受理(异步执行)");
    }
    @Override
    public byte[] readOriginCommand(String address, int length) {
        return new byte[0];
    }
    private void handleCommand(Integer deviceNo, StationCommand command) {
        News.info("[WCS Debug] 站点仿真模拟已启动,命令数据={}", JSON.toJSONString(command));
        Integer taskNo = command.getTaskNo();
src/main/java/com/zy/core/network/real/ZyDualCrnRealConnect.java
@@ -108,145 +108,144 @@
    @Override
    public CommandResponse sendCommand(DualCrnCommand command) {
        CommandResponse response = new CommandResponse(false);
        try {
            if (null == command) {
                News.error("双工位堆垛机写入命令为空");
                response.setMessage("双工位堆垛机写入命令为空");
                return response;
            }
        // try {
        //     if (null == command) {
        //         News.error("双工位堆垛机写入命令为空");
        //         response.setMessage("双工位堆垛机写入命令为空");
        //         return response;
        //     }
            String address = "DB100.0";
            if (command.getStation() == 1) {
                //工位1
                address = "DB100.0";
            }else {
                //工位2
                address = "DB100.20";
            }
        //     String address = "DB100.0";
        //     if (command.getStation() == 1) {
        //         //工位1
        //         address = "DB100.0";
        //     }else {
        //         //工位2
        //         address = "DB100.20";
        //     }
            int writeAck = 0;
            boolean ackResult = false;
            do {
                OperateResult resultAck = siemensNet.Write(address, (short) 0);
                if (resultAck.IsSuccess) {
                    Thread.sleep(200);
                    OperateResultExOne<byte[]> resultRead = siemensNet.Read(address, (short) 2);
                    short ack = siemensNet.getByteTransform().TransInt16(resultRead.Content, 0);
                    if (ack != 0) {
                        writeAck++;
                    } else {
                        News.info("双工位堆垛机命令下发[id:{}] >>>>> {}", command.getCrnNo(), "ack复位完成");
                        ackResult = true;
                        break;
                    }
                }
            } while (writeAck < 5);
        //     int writeAck = 0;
        //     boolean ackResult = false;
        //     do {
        //         OperateResult resultAck = siemensNet.Write(address, (short) 0);
        //         if (resultAck.IsSuccess) {
        //             Thread.sleep(200);
        //             OperateResultExOne<byte[]> resultRead = siemensNet.Read(address, (short) 2);
        //             short ack = siemensNet.getByteTransform().TransInt16(resultRead.Content, 0);
        //             if (ack != 0) {
        //                 writeAck++;
        //             } else {
        //                 News.info("双工位堆垛机命令下发[id:{}] >>>>> {}", command.getCrnNo(), "ack复位完成");
        //                 ackResult = true;
        //                 break;
        //             }
        //         }
        //     } while (writeAck < 5);
            if (!ackResult) {
                response.setMessage("双工位堆垛机命令下发[id:{}] >>>>> {}" + command.getCrnNo() + "ack复位失败");
                return response;
            }
        //     if (!ackResult) {
        //         response.setMessage("双工位堆垛机命令下发[id:{}] >>>>> {}" + command.getCrnNo() + "ack复位失败");
        //         return response;
        //     }
            short[] array = new short[10];
            array[0] = command.getAckFinish();
            array[1] = command.getTaskNo();
            array[2] = command.getTaskMode();
            array[3] = command.getSourcePosX();
            array[4] = command.getSourcePosY();
            array[5] = command.getSourcePosZ();
            array[6] = command.getDestinationPosX();
            array[7] = command.getDestinationPosY();
            array[8] = command.getDestinationPosZ();
            array[9] = command.getCommand();
        //     short[] array = new short[10];
        //     array[1] = command.getTaskNo();
        //     array[2] = command.getTaskMode();
        //     array[3] = command.getSourcePosX();
        //     array[4] = command.getSourcePosY();
        //     array[5] = command.getSourcePosZ();
        //     array[6] = command.getDestinationPosX();
        //     array[7] = command.getDestinationPosY();
        //     array[8] = command.getDestinationPosZ();
        //     array[9] = command.getCommand();
            OperateResult result = null;
            int idx = 0;
            do {
                OperateResultExOne<byte[]> resultRead = siemensNet.Read(address, (short) 20);
                if (resultRead.IsSuccess) {
                    if (command.getAckFinish() == 0) {
                        short taskNo = siemensNet.getByteTransform().TransInt16(resultRead.Content, 2);
                        short taskMode = siemensNet.getByteTransform().TransInt16(resultRead.Content, 4);
                        short sourcePosX = siemensNet.getByteTransform().TransInt16(resultRead.Content, 6);
                        short sourcePosY = siemensNet.getByteTransform().TransInt16(resultRead.Content, 8);
                        short sourcePosZ = siemensNet.getByteTransform().TransInt16(resultRead.Content, 10);
                        short destinationPosX = siemensNet.getByteTransform().TransInt16(resultRead.Content, 12);
                        short destinationPosY = siemensNet.getByteTransform().TransInt16(resultRead.Content, 14);
                        short destinationPosZ = siemensNet.getByteTransform().TransInt16(resultRead.Content, 16);
                        if (taskNo == 0 || taskMode == 0 || sourcePosX == 0 || sourcePosY == 0 || sourcePosZ == 0 || destinationPosX == 0 || destinationPosY == 0 || destinationPosZ == 0) {
                            result = siemensNet.Write(address, array);
                        } else {
                            break;
                        }
                    } else {
                        short ackFinish = siemensNet.getByteTransform().TransInt16(resultRead.Content, 0);
                        if (ackFinish != command.getAckFinish()) {
                            result = siemensNet.Write(address, array);
                        } else {
                            break;
                        }
                    }
                }
                idx++;
                Thread.sleep(500);
            } while (idx < 5);
        //     OperateResult result = null;
        //     int idx = 0;
        //     do {
        //         OperateResultExOne<byte[]> resultRead = siemensNet.Read(address, (short) 20);
        //         if (resultRead.IsSuccess) {
        //             if (command.getAckFinish() == 0) {
        //                 short taskNo = siemensNet.getByteTransform().TransInt16(resultRead.Content, 2);
        //                 short taskMode = siemensNet.getByteTransform().TransInt16(resultRead.Content, 4);
        //                 short sourcePosX = siemensNet.getByteTransform().TransInt16(resultRead.Content, 6);
        //                 short sourcePosY = siemensNet.getByteTransform().TransInt16(resultRead.Content, 8);
        //                 short sourcePosZ = siemensNet.getByteTransform().TransInt16(resultRead.Content, 10);
        //                 short destinationPosX = siemensNet.getByteTransform().TransInt16(resultRead.Content, 12);
        //                 short destinationPosY = siemensNet.getByteTransform().TransInt16(resultRead.Content, 14);
        //                 short destinationPosZ = siemensNet.getByteTransform().TransInt16(resultRead.Content, 16);
        //                 if (taskNo == 0 || taskMode == 0 || sourcePosX == 0 || sourcePosY == 0 || sourcePosZ == 0 || destinationPosX == 0 || destinationPosY == 0 || destinationPosZ == 0) {
        //                     result = siemensNet.Write(address, array);
        //                 } else {
        //                     break;
        //                 }
        //             } else {
        //                 short ackFinish = siemensNet.getByteTransform().TransInt16(resultRead.Content, 0);
        //                 if (ackFinish != command.getAckFinish()) {
        //                     result = siemensNet.Write(address, array);
        //                 } else {
        //                     break;
        //                 }
        //             }
        //         }
        //         idx++;
        //         Thread.sleep(500);
        //     } while (idx < 5);
            if (command.getAckFinish() == 0) {
                short commandFinish = 1;
                int i = 0;
                do {
                    OperateResultExOne<byte[]> resultRead = siemensNet.Read(address, (short) 4);
                    OperateResultExOne<byte[]> resultReadConfirm = siemensNet.Read(address + 18, (short) 2);
                    if (resultRead.IsSuccess && resultReadConfirm.IsSuccess) {
                        short taskNo = siemensNet.getByteTransform().TransInt16(resultRead.Content, 2);
                        short confirm = siemensNet.getByteTransform().TransInt16(resultReadConfirm.Content, 0);
                        if (taskNo != 0 && confirm == 0) {
                            result = siemensNet.Write(address + 18, commandFinish);
                        }
                    }
                    i++;
                    Thread.sleep(500);
                } while (i < 5);
            }
        //     if (command.getAckFinish() == 0) {
        //         short commandFinish = 1;
        //         int i = 0;
        //         do {
        //             OperateResultExOne<byte[]> resultRead = siemensNet.Read(address, (short) 4);
        //             OperateResultExOne<byte[]> resultReadConfirm = siemensNet.Read(address + 18, (short) 2);
        //             if (resultRead.IsSuccess && resultReadConfirm.IsSuccess) {
        //                 short taskNo = siemensNet.getByteTransform().TransInt16(resultRead.Content, 2);
        //                 short confirm = siemensNet.getByteTransform().TransInt16(resultReadConfirm.Content, 0);
        //                 if (taskNo != 0 && confirm == 0) {
        //                     result = siemensNet.Write(address + 18, commandFinish);
        //                 }
        //             }
        //             i++;
        //             Thread.sleep(500);
        //         } while (i < 5);
        //     }
            if (result != null && result.IsSuccess) {
                News.info("SiemensDualCrn 双工位堆垛机命令下发[id:{}] >>>>> {}", command.getCrnNo(), JSON.toJSON(command));
                OutputQueue.CRN.offer(MessageFormat.format("【{0}】[id:{1}] >>>>> 命令下发: {2}", DateUtils.convert(new Date()), command.getCrnNo(), JSON.toJSON(command)));
                response.setResult(true);
                response.setMessage("命令下发成功");
            } else {
                News.error("SiemensDualCrn 双工位堆垛机写入堆垛机plc数据失败 ===>> [id:{}]", command.getCrnNo());
                OutputQueue.CRN.offer(MessageFormat.format("【{0}】写入堆垛机plc数据失败 ===>> [id:{1}]", DateUtils.convert(new Date()), command.getCrnNo()));
                response.setResult(false);
                response.setMessage("命令下发失败");
            }
        //     if (result != null && result.IsSuccess) {
        //         News.info("SiemensDualCrn 双工位堆垛机命令下发[id:{}] >>>>> {}", command.getCrnNo(), JSON.toJSON(command));
        //         OutputQueue.CRN.offer(MessageFormat.format("【{0}】[id:{1}] >>>>> 命令下发: {2}", DateUtils.convert(new Date()), command.getCrnNo(), JSON.toJSON(command)));
        //         response.setResult(true);
        //         response.setMessage("命令下发成功");
        //     } else {
        //         News.error("SiemensDualCrn 双工位堆垛机写入堆垛机plc数据失败 ===>> [id:{}]", command.getCrnNo());
        //         OutputQueue.CRN.offer(MessageFormat.format("【{0}】写入堆垛机plc数据失败 ===>> [id:{1}]", DateUtils.convert(new Date()), command.getCrnNo()));
        //         response.setResult(false);
        //         response.setMessage("命令下发失败");
        //     }
            return response;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            String sourceLocNo = Utils.getLocNo(command.getSourcePosX(), command.getSourcePosY(), command.getSourcePosZ());
            String targetLocNo = Utils.getLocNo(command.getDestinationPosX(), command.getDestinationPosY(), command.getDestinationPosZ());
        //     return response;
        // } catch (Exception e) {
        //     e.printStackTrace();
        // }finally {
        //     String sourceLocNo = Utils.getLocNo(command.getSourcePosX(), command.getSourcePosY(), command.getSourcePosZ());
        //     String targetLocNo = Utils.getLocNo(command.getDestinationPosX(), command.getDestinationPosY(), command.getDestinationPosZ());
            // 日志记录
            BasCrnpOptService bean = SpringUtils.getBean(BasCrnpOptService.class);
            BasCrnpOpt basCrnpOpt = new BasCrnpOpt(
                    command.getTaskNo().intValue(),    // 任务号
                    command.getCrnNo(),    // 堆垛机[非空]
                    new Date(),    // 下发时间
                    String.valueOf(command.getTaskMode()),    // 模式
                    sourceLocNo, //源库位
                    targetLocNo, //目标库位
                    null,    // 修改时间
                    null,    // 修改人员
                    null,    // 备注
                    JSON.toJSONString(command),    // 指令
                    JSON.toJSONString(getStatus()),    // 系统状态
                    1,    // 下发状态{0:未下发,1:已下发}
                    JSON.toJSONString(response)    // 响应
            );
            bean.insert(basCrnpOpt);
        }
        //     // 日志记录
        //     BasCrnpOptService bean = SpringUtils.getBean(BasCrnpOptService.class);
        //     BasCrnpOpt basCrnpOpt = new BasCrnpOpt(
        //             command.getTaskNo().intValue(),    // 任务号
        //             command.getCrnNo(),    // 堆垛机[非空]
        //             new Date(),    // 下发时间
        //             String.valueOf(command.getTaskMode()),    // 模式
        //             sourceLocNo, //源库位
        //             targetLocNo, //目标库位
        //             null,    // 修改时间
        //             null,    // 修改人员
        //             null,    // 备注
        //             JSON.toJSONString(command),    // 指令
        //             JSON.toJSONString(getStatus()),    // 系统状态
        //             1,    // 下发状态{0:未下发,1:已下发}
        //             JSON.toJSONString(response)    // 响应
        //     );
        //     bean.insert(basCrnpOpt);
        // }
        return response;
    }
}
src/main/java/com/zy/core/network/real/ZyStationRealConnect.java
@@ -186,6 +186,34 @@
        return commandResponse;
    }
    @Override
    public CommandResponse sendOriginCommand(String address, short[] data) {
        CommandResponse commandResponse = new CommandResponse(false);
        if (null == data || data.length == 0) {
            commandResponse.setMessage("数据为空");
            return commandResponse;
        }
        OperateResult write = siemensNet.Write(address, data);
        if (write.IsSuccess) {
            log.info("写入原始命令成功。地址={},数据={}", address, JSON.toJSON(data));
            commandResponse.setResult(true);
        } else {
            log.error("写入原始命令失败。地址={},数据={}", address, JSON.toJSON(data));
            commandResponse.setResult(false);
        }
        return commandResponse;
    }
    @Override
    public byte[] readOriginCommand(String address, int length) {
        OperateResultExOne<byte[]> result = siemensNet.Read(address, (short) length);
        if (result.IsSuccess) {
            return result.Content;
        }
        return new byte[0];
    }
    private ZyStationStatusEntity findStatusEntity(Integer stationId) {
        for (ZyStationStatusEntity statusEntity : statusList) {
            if (statusEntity.getStationId().equals(stationId)) {
src/main/java/com/zy/core/thread/DualCrnThread.java
@@ -11,6 +11,10 @@
    DualCrnCommand getPickAndPutCommand(String sourceLocNo, String targetLocNo, Integer taskNo, Integer crnNo, Integer station);//取放货
    DualCrnCommand getPickCommand(String targetLocNo, Integer taskNo, Integer crnNo, Integer station);//取货
    DualCrnCommand getPutCommand(String targetLocNo, Integer taskNo, Integer crnNo, Integer station);//放货
    DualCrnCommand getMoveCommand(String targetLocNo, Integer taskNo, Integer crnNo);//移动
    DualCrnCommand getResetCommand(Integer crnNo, Integer station);//复位
src/main/java/com/zy/core/thread/StationThread.java
@@ -17,4 +17,8 @@
    CommandResponse sendCommand(StationCommand command);
    CommandResponse sendOriginCommand(String address, short[] data);
    byte[] readOriginCommand(String address, int length);
}
src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
@@ -14,7 +14,7 @@
import com.zy.common.utils.RedisUtil;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.OutputQueue;
import com.zy.core.enums.CrnTaskModeType;
import com.zy.core.enums.DualCrnTaskModeType;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.CommandResponse;
@@ -129,6 +129,7 @@
        crnProtocol.setForkPos(-1);
        crnProtocol.setLoaded(0);
        crnProtocol.setWalkPos(0);
        crnProtocol.setLiftPos(0);
        //工位2
        crnProtocol.setTaskNoTwo(0);
@@ -138,6 +139,7 @@
        crnProtocol.setForkPosTwo(-1);
        crnProtocol.setLoadedTwo(0);
        crnProtocol.setWalkPosTwo(0);
        crnProtocol.setLiftPosTwo(0);
        crnProtocol.setAlarm(0);
        crnProtocol.setXSpeed(0);
@@ -166,7 +168,6 @@
            OutputQueue.DUAL_CRN.offer(MessageFormat.format("【{0}】读取双工位堆垛机plc状态信息失败 ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort()));
            return;
        }
        crnProtocol.setMode(crnStatus.getMode());
        //工位1
@@ -177,6 +178,8 @@
        crnProtocol.setForkPos(crnStatus.getForkPos());
        crnProtocol.setLoaded(crnStatus.getLoaded());
        crnProtocol.setWalkPos(crnStatus.getWalkPos());
        crnProtocol.setLiftPos(crnStatus.getLiftPos());
        crnProtocol.setTaskReceive(crnStatus.getTaskReceive());
        //工位2
        crnProtocol.setTaskNoTwo(crnStatus.getTaskNoTwo());
@@ -186,6 +189,8 @@
        crnProtocol.setForkPosTwo(crnStatus.getForkPosTwo());
        crnProtocol.setLoadedTwo(crnStatus.getLoadedTwo());
        crnProtocol.setWalkPosTwo(crnStatus.getWalkPosTwo());
        crnProtocol.setLiftPosTwo(crnStatus.getLiftPosTwo());
        crnProtocol.setTaskReceiveTwo(crnStatus.getTaskReceiveTwo());
        crnProtocol.setAlarm(crnStatus.getAlarm());
        crnProtocol.setTemp1(crnStatus.getTemp1());
@@ -272,10 +277,38 @@
        DualCrnCommand crnCommand = new DualCrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo(taskNo.shortValue()); // 工作号
        crnCommand.setTaskMode(CrnTaskModeType.LOC_MOVE.id.shortValue()); // 任务模式:  库位移转
        crnCommand.setTaskMode(DualCrnTaskModeType.TRANSFER.id.shortValue()); // 任务模式:  取放货
        crnCommand.setSourcePosX((short) Utils.getRow(sourceLocNo));     // 源库位排
        crnCommand.setSourcePosY((short) Utils.getBay(sourceLocNo));     // 源库位列
        crnCommand.setSourcePosZ((short) Utils.getLev(sourceLocNo));     // 源库位层
        crnCommand.setDestinationPosX((short) Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY((short) Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ((short) Utils.getLev(targetLocNo));     // 目标库位层
        crnCommand.setStation(station.shortValue());//工位
        crnCommand.setCommand((short) 1);     // 任务确认
        return crnCommand;
    }
    @Override
    public DualCrnCommand getPickCommand(String targetLocNo, Integer taskNo, Integer crnNo, Integer station) {
        DualCrnCommand crnCommand = new DualCrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo(taskNo.shortValue()); // 工作号
        crnCommand.setTaskMode(DualCrnTaskModeType.PICK.id.shortValue()); // 任务模式:  取货
        crnCommand.setDestinationPosX((short) Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY((short) Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ((short) Utils.getLev(targetLocNo));     // 目标库位层
        crnCommand.setStation(station.shortValue());//工位
        crnCommand.setCommand((short) 1);     // 任务确认
        return crnCommand;
    }
    @Override
    public DualCrnCommand getPutCommand(String targetLocNo, Integer taskNo, Integer crnNo, Integer station) {
        DualCrnCommand crnCommand = new DualCrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo(taskNo.shortValue()); // 工作号
        crnCommand.setTaskMode(DualCrnTaskModeType.PUT.id.shortValue()); // 任务模式:  放货
        crnCommand.setDestinationPosX((short) Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY((short) Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ((short) Utils.getLev(targetLocNo));     // 目标库位层
@@ -289,8 +322,7 @@
        DualCrnCommand crnCommand = new DualCrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo(taskNo.shortValue()); // 工作号
        crnCommand.setAckFinish((short) 0);  // 任务完成确认位
        crnCommand.setTaskMode(CrnTaskModeType.CRN_MOVE.id.shortValue()); // 任务模式:  堆垛机移动
        crnCommand.setTaskMode(DualCrnTaskModeType.MOVE.id.shortValue()); // 任务模式:  堆垛机移动
        crnCommand.setDestinationPosX((short) Utils.getRow(targetLocNo));     // 目标库位排
        crnCommand.setDestinationPosY((short) Utils.getBay(targetLocNo));     // 目标库位列
        crnCommand.setDestinationPosZ((short) Utils.getLev(targetLocNo));     // 目标库位层
@@ -303,8 +335,7 @@
        DualCrnCommand crnCommand = new DualCrnCommand();
        crnCommand.setCrnNo(crnNo); // 堆垛机编号
        crnCommand.setTaskNo((short) 0); // 工作号
        crnCommand.setAckFinish((short) 1);  // 任务完成确认位
        crnCommand.setTaskMode(CrnTaskModeType.NONE.id.shortValue()); // 任务模式
        crnCommand.setTaskMode(DualCrnTaskModeType.CONFIRM.id.shortValue()); // 任务模式:  确认
        crnCommand.setSourcePosX((short)0);     // 源库位排
        crnCommand.setSourcePosY((short)0);     // 源库位列
        crnCommand.setSourcePosZ((short)0);     // 源库位层
src/main/java/com/zy/core/thread/impl/ZyStationThread.java
@@ -266,4 +266,14 @@
        }
        return map;
    }
    @Override
    public CommandResponse sendOriginCommand(String address, short[] data) {
        return zyStationConnectDriver.sendOriginCommand(address, data);
    }
    @Override
    public byte[] readOriginCommand(String address, int length) {
        return zyStationConnectDriver.readOriginCommand(address, length);
    }
}
src/main/webapp/components/DevpCard.js
@@ -43,6 +43,7 @@
                <el-descriptions-item label="托盘高度">{{ item.palletHeight }}</el-descriptions-item>
                <el-descriptions-item label="条码">{{ item.barcode }}</el-descriptions-item>
                <el-descriptions-item label="故障代码">{{ item.error }}</el-descriptions-item>
                <el-descriptions-item label="扩展数据">{{ item.extend }}</el-descriptions-item>
            </el-descriptions>
            </el-collapse-item>
          </el-collapse>
src/main/webapp/components/MapCanvas.js
@@ -14,10 +14,15 @@
      currentLev: 1,
      mapFps: 0,
      ws: null,
      wsReconnectTimer: null,
      wsReconnectAttempts: 0,
      wsReconnectBaseDelay: 1000,
      wsReconnectMaxDelay: 15000,
      pixiApp: null,
      pixiStageList: [],
      pixiStaMap: new Map(),
      pixiCrnMap: new Map(),
      pixiDualCrnMap: new Map(),
      pixiRgvMap: new Map(),
      pixiShelfMap: new Map(),
      pixiTrackMap: new Map(),
@@ -27,6 +32,7 @@
      pixiCrnColorTextureMap: new Map(),
      pixiRgvColorTextureMap: new Map(),
      crnList: [],
      dualCrnList: [],
      rgvList: [],
      objectsContainer: null,
      objectsContainer2: null,
@@ -41,14 +47,10 @@
      isSwitchingFloor: false
    }
  },
  mounted() {
    mounted() {
    this.currentLev = this.lev || 1;
    this.createMap();
    this.ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
    this.ws.onopen = this.webSocketOnOpen;
    this.ws.onerror = this.webSocketOnError;
    this.ws.onmessage = this.webSocketOnMessage;
    this.ws.onclose = this.webSocketClose;
    this.connectWs();
    
    setTimeout(() => {
      this.getMap(this.currentLev);
@@ -56,6 +58,7 @@
    this.timer = setInterval(() => {
      this.getCrnInfo();
      this.getDualCrnInfo();
      this.getSiteInfo();
      this.getRgvInfo();
    }, 1000);
@@ -64,6 +67,8 @@
    if (this.timer) { clearInterval(this.timer); }
    if (this.pixiApp) { this.pixiApp.destroy(true, { children: true }); }
    window.removeEventListener('resize', this.resizeToContainer);
    if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
    if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { try { this.ws.close(); } catch (e) {} }
  },
  watch: {
    lev(newLev) {
@@ -180,12 +185,23 @@
      //*******************FPS*******************
      let g_Time = 0;
      let fpsLastUpdateTs = 0;
      let fpsDeltaSumMs = 0;
      let fpsFrameCount = 0;
      const fpsUpdateInterval = 200;
      this.pixiApp.ticker.add((delta) => {
        const timeNow = (new Date()).getTime();
        const timeDiff = timeNow - g_Time;
        g_Time = timeNow;
        const fps = 1000 / timeDiff;
        this.mapFps = parseInt(fps);
        fpsDeltaSumMs += timeDiff;
        fpsFrameCount += 1;
        if (timeNow - fpsLastUpdateTs >= fpsUpdateInterval) {
          const avgFps = fpsDeltaSumMs > 0 ? (fpsFrameCount * 1000 / fpsDeltaSumMs) : 0;
          this.mapFps = Math.round(avgFps);
          fpsDeltaSumMs = 0;
          fpsFrameCount = 0;
          fpsLastUpdateTs = timeNow;
        }
      });
      //*******************FPS*******************
    },
@@ -208,17 +224,35 @@
      if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
      if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); }
      this.crnList = [];
      this.dualCrnList = [];
      this.rgvList = [];
      this.pixiCrnMap = new Map();
      this.pixiDualCrnMap = new Map();
      this.pixiRgvMap = new Map();
      this.pixiStaMap = new Map();
      this.pixiStageList = [];
      this.getMap();
    },
    createMapData(map) {
      this.pixiStageList = [map.length];
      this.pixiStaMap = new Map();
      if (window.gsap) {
        this.pixiStaMap && this.pixiStaMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
        this.pixiCrnMap && this.pixiCrnMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
        this.pixiDualCrnMap && this.pixiDualCrnMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
        this.pixiRgvMap && this.pixiRgvMap.forEach((s) => { try { window.gsap.killTweensOf(s); } catch (e) {} });
      }
      this.objectsContainer.removeChildren();
      this.objectsContainer2.removeChildren();
      if (this.tracksContainer) { this.tracksContainer.removeChildren(); }
      if (this.shelvesContainer) { this.shelvesContainer.removeChildren(); }
      this.crnList = [];
      this.dualCrnList = [];
      this.rgvList = [];
      this.pixiCrnMap = new Map();
      this.pixiDualCrnMap = new Map();
      this.pixiRgvMap = new Map();
      this.pixiStaMap = new Map();
      this.pixiStageList = [];
      this.pixiStageList = [map.length];
      const bayHeightList = this.initHeight(map);
      const bayWidthList = this.initWidth(map);
      map.forEach((item, index) => {
@@ -362,6 +396,39 @@
        this.objectsContainer2.addChild(sprite);
      });
      
      this.dualCrnList.forEach((item) => {
        if (this.graphicsCrn == null) { this.graphicsCrn = this.createCrnTexture(item.width * 0.9, item.height * 0.9); }
        let sprite = new PIXI.Sprite(this.graphicsCrn);
        const deviceNo = this.getDeviceNo(item.value);
        const taskNo = this.getTaskNo(item.value);
        const style = new PIXI.TextStyle({ fontFamily: 'Arial', fontSize: 12, fill: '#000000', stroke: '#ffffff', strokeThickness: 1 });
        const txt = taskNo > 0 ? (deviceNo + "(" + taskNo + ")") : String(deviceNo);
        const text = new PIXI.Text(txt, style);
        text.anchor.set(0.5);
        text.position.set(sprite.width / 2, sprite.height / 2);
        sprite.addChild(text);
        sprite.textObj = text;
        sprite.position.set(item.posX, item.posY);
        sprite.interactive = true;
        sprite.buttonMode = true;
        sprite.on('pointerdown', () => {
          if (window.gsap) { window.gsap.killTweensOf(sprite); }
          sprite.alpha = 1;
          const id = parseInt(deviceNo, 10);
          this.$emit('dual-crn-click', id);
        });
        let rowIndexForCrn = 0;
        for (let r = 0; r < map.length; r++) {
          if (map[r].length > 0) {
            const rowY = map[r][0].posY;
            if (Math.abs(rowY - item.posY) < 0.5) { rowIndexForCrn = r; break; }
          }
        }
        sprite.rowIndex = rowIndexForCrn;
        this.pixiDualCrnMap.set(parseInt(deviceNo), sprite);
        this.objectsContainer2.addChild(sprite);
      });
      this.rgvList.forEach((item) => {
        if (this.graphicsRgv == null) { this.graphicsRgv = this.createRgvTexture(item.width * 0.9, item.height * 0.9); }
        let sprite = new PIXI.Sprite(this.graphicsRgv);
@@ -491,6 +558,10 @@
      if (this.isSwitchingFloor) { return; }
      this.sendWs(JSON.stringify({ url: "/console/latest/data/crn", data: {} }));
    },
    getDualCrnInfo() {
      if (this.isSwitchingFloor) { return; }
      this.sendWs(JSON.stringify({ url: "/console/latest/data/dualcrn", data: {} }));
    },
    getSiteInfo() {
      if (this.isSwitchingFloor) { return; }
      this.sendWs(JSON.stringify({ url: "/console/latest/data/station", data: {} }));
@@ -533,6 +604,60 @@
          for (let c = this.map[rowIndex].length - 1; c >= 0; c--) {
            const cell = this.map[rowIndex][c];
            if (cell && cell.type === 'crn') { targetCell = cell; break; }
          }
        }
        if (!targetCell) { continue; }
        const targetX = targetCell.posX + (targetCell.width - sprite.width) / 2;
        const dx = Math.abs(targetX - sprite.x);
        if (dx < 1) {
        } else if (dx < 5) {
          sprite.x = targetX;
        } else if (window.gsap) {
          window.gsap.killTweensOf(sprite);
          window.gsap.to(sprite, { x: targetX, duration: 0.3, ease: "power1.inOut" });
        } else {
          sprite.x = targetX;
        }
      }
      this.scheduleAdjustLabels();
    },
    setDualCrnInfo(res) {
      let crns = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
      if (!crns) { return; }
      for (var i = 0; i < crns.length; i++) {
        const id = parseInt(crns[i].crnId);
        const sprite = this.pixiDualCrnMap.get(id);
        if (!sprite) { continue; }
        const taskNo = crns[i].taskNo;
        if (taskNo != null && taskNo > 0) { sprite.textObj.text = id + "(" + taskNo + ")"; } else { sprite.textObj.text = String(id); }
        const status = crns[i].crnStatus;
        const statusColor = this.getCrnStatusColor(status);
        this.updateCrnTextureColor(sprite, statusColor);
        let bay = parseInt(crns[i].bay, 10);
        if (isNaN(bay) || bay < 1 || bay === -2) { bay = 1; }
        let rowIndex = (sprite.rowIndex != null) ? sprite.rowIndex : -1;
        if (rowIndex === -1) {
          for (let r = 0; r < this.map.length; r++) {
            if (this.map[r].length > 0) {
              const rowY = this.map[r][0].posY;
              if (Math.abs(rowY - sprite.y) < 0.5) { rowIndex = r; break; }
            }
          }
          if (rowIndex === -1) { rowIndex = 0; }
        }
        let targetCell = null;
        let crnCount = 0;
        for (let c = 0; c < this.map[rowIndex].length; c++) {
          const cell = this.map[rowIndex][c];
          if (cell && (cell.type === 'dualCrn' || cell.type === 'dualcrn')) {
            crnCount++;
            if (crnCount === bay) { targetCell = cell; break; }
          }
        }
        if (!targetCell) {
          for (let c = this.map[rowIndex].length - 1; c >= 0; c--) {
            const cell = this.map[rowIndex][c];
            if (cell && (cell.type === 'dualCrn' || cell.type === 'dualcrn')) { targetCell = cell; break; }
          }
        }
        if (!targetCell) { continue; }
@@ -596,8 +721,12 @@
      this.createMapData(JSON.parse(res.data));
    },
    webSocketOnOpen(e) {
      if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
      this.wsReconnectAttempts = 0;
      this.getMap(this.currentLev);
    },
    webSocketOnError(e) {
      this.scheduleReconnect();
    },
    webSocketOnMessage(e) {
      const result = JSON.parse(e.data);
@@ -605,6 +734,8 @@
        this.setSiteInfo(JSON.parse(result.data));
      } else if (result.url === "/console/latest/data/crn") {
        this.setCrnInfo(JSON.parse(result.data));
      } else if (result.url === "/console/latest/data/dualcrn") {
        this.setDualCrnInfo(JSON.parse(result.data));
      } else if (result.url === "/console/latest/data/rgv") {
        this.setRgvInfo(JSON.parse(result.data));
      } else if (typeof result.url === "string" && result.url.indexOf("/basMap/lev/") === 0) {
@@ -612,11 +743,31 @@
      }
    },
    webSocketClose(e) {
      this.scheduleReconnect();
    },
    sendWs(message) {
      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(message);
      }
    },
    connectWs() {
      if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { return; }
      this.ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
      this.ws.onopen = this.webSocketOnOpen;
      this.ws.onerror = this.webSocketOnError;
      this.ws.onmessage = this.webSocketOnMessage;
      this.ws.onclose = this.webSocketClose;
    },
    scheduleReconnect() {
      if (this.wsReconnectTimer) { return; }
      const attempt = this.wsReconnectAttempts + 1;
      const jitter = Math.floor(Math.random() * 300);
      const delay = Math.min(this.wsReconnectMaxDelay, this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts)) + jitter;
      this.wsReconnectTimer = setTimeout(() => {
        this.wsReconnectTimer = null;
        this.wsReconnectAttempts = attempt;
        this.connectWs();
      }, delay);
    },
    createShelfSprite(width, height) {
      let idx = width + "-" + height;
@@ -901,6 +1052,10 @@
        sprite = this.createTrackSprite(item.width, item.height);
        sprite._kind = 'crn-track';
        if (this.getDeviceNo(value) > 0) { this.crnList.push(item); }
      } else if (item.type == 'dualCrn') {
        sprite = this.createTrackSprite(item.width, item.height);
        sprite._kind = 'crn-track';
        if (this.getDeviceNo(value) > 0) { this.dualCrnList.push(item); }
      } else if (item.type == 'rgv') {
        sprite = this.createTrackSprite(item.width, item.height);
        sprite._kind = 'rgv-track';
@@ -983,6 +1138,20 @@
        const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
        textObj.visible = (s >= 0.25) && on;
      });
      this.pixiDualCrnMap && this.pixiDualCrnMap.forEach((sprite) => {
        const textObj = sprite && sprite.textObj;
        if (!textObj) { return; }
        const base = (textObj.style && textObj.style.fontSize) ? textObj.style.fontSize : 12;
        let scale = minPx / (base * s);
        if (!isFinite(scale)) { scale = 1; }
        scale = Math.max(0.8, Math.min(scale, 3));
        textObj.scale.set(scale);
        textObj.position.set(sprite.width / 2, sprite.height / 2);
        const sx = pos.x + sprite.x * s;
        const sy = pos.y + sprite.y * s;
        const on = sx >= -margin && sy >= -margin && sx <= vw + margin && sy <= vh + margin;
        textObj.visible = (s >= 0.25) && on;
      });
      this.pixiRgvMap && this.pixiRgvMap.forEach((sprite) => {
        const textObj = sprite && sprite.textObj;
        if (!textObj) { return; }
src/main/webapp/components/WatchCrnCard.js
@@ -53,8 +53,11 @@
                <el-descriptions-item label="升降距离(Km)">{{ item.ydistance }}</el-descriptions-item>
                <el-descriptions-item label="走行时长(H)">{{ item.xDuration }}</el-descriptions-item>
                <el-descriptions-item label="升降时长(H)">{{ item.yduration }}</el-descriptions-item>
                <el-descriptions-item label="称重数据">{{ item.weight }}</el-descriptions-item>
                <el-descriptions-item label="条码数据">{{ item.barcode }}</el-descriptions-item>
                <el-descriptions-item label="故障代码">{{ item.warnCode }}</el-descriptions-item>
                <el-descriptions-item label="故障描述">{{ item.alarm }}</el-descriptions-item>
                <el-descriptions-item label="扩展数据">{{ item.extend }}</el-descriptions-item>
            </el-descriptions>
            </el-collapse-item>
          </el-collapse>
src/main/webapp/components/WatchDualCrnCard.js
New file
@@ -0,0 +1,299 @@
Vue.component("watch-dual-crn-card", {
  template: `
    <div>
        <div style="display: flex;margin-bottom: 10px;">
            <div style="width: 100%;">双工位堆垛机监控</div>
            <div style="width: 100%;text-align: right;display: flex;">
              <el-input size="mini" v-model="searchCrnNo" placeholder="请输入堆垛机号"></el-input>
              <el-button @click="getDualCrnStateInfo" size="mini">查询</el-button>
            </div>
        </div>
        <div style="margin-bottom: 10px;">
          <div style="margin-bottom: 5px;">
            <el-button v-if="showControl" @click="openControl" size="mini">关闭控制中心</el-button>
            <el-button v-else @click="openControl" size="mini">打开控制中心</el-button>
          </div>
          <div v-if="showControl" style="display: flex;justify-content: space-between;flex-wrap: wrap;">
            <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.crnNo" placeholder="堆垛机号"></el-input></div>
            <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.sourceLocNo" placeholder="源点"></el-input></div>
            <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.targetLocNo" placeholder="目标点"></el-input></div>
            <div style="margin-bottom: 10px;width: 33%;">
              <el-select size="mini" v-model="controlParam.station" placeholder="工位">
                <el-option :label="'工位1'" :value="1"></el-option>
                <el-option :label="'工位2'" :value="2"></el-option>
              </el-select>
            </div>
            <div style="margin-bottom: 10px;"><el-button @click="controlCommandTransport()" size="mini">取放货</el-button></div>
            <div style="margin-bottom: 10px;"><el-button @click="controlCommandPickup()" size="mini">取货</el-button></div>
            <div style="margin-bottom: 10px;"><el-button @click="controlCommandPutdown()" size="mini">放货</el-button></div>
            <div style="margin-bottom: 10px;"><el-button @click="controlCommandMove()" size="mini">移动</el-button></div>
            <div style="margin-bottom: 10px;"><el-button @click="controlCommandTaskComplete()" size="mini">任务完成</el-button></div>
          </div>
        </div>
        <div style="max-height: 55vh; overflow:auto;">
          <el-collapse v-model="activeNames" accordion>
            <el-collapse-item v-for="(item) in displayCrnList" :name="item.crnNo">
              <template slot="title">
                <div style="width: 100%;display: flex;">
                  <div style="width: 50%;">{{ item.crnNo }}号双工位堆垛机</div>
                  <div style="width: 50%;text-align: right;">
                    <el-tag v-if="item.deviceStatus == 'AUTO'" type="success" size="small">自动</el-tag>
                    <el-tag v-else-if="item.deviceStatus == 'WORKING'" size="small">作业中</el-tag>
                    <el-tag v-else-if="item.deviceStatus == 'ERROR'" type="danger" size="small">故障</el-tag>
                    <el-tag v-else type="warning" size="small">离线</el-tag>
                  </div>
                </div>
              </template>
              <el-descriptions border direction="vertical">
                <el-descriptions-item label="模式">{{ item.mode }}</el-descriptions-item>
                <el-descriptions-item label="异常码">{{ item.warnCode }}</el-descriptions-item>
                <el-descriptions-item label="工位1任务号">{{ item.taskNo }}</el-descriptions-item>
                <el-descriptions-item label="工位2任务号">{{ item.taskNoTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1状态">{{ item.status }}</el-descriptions-item>
                <el-descriptions-item label="工位2状态">{{ item.statusTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1是否有物">{{ item.loading }}</el-descriptions-item>
                <el-descriptions-item label="工位2是否有物">{{ item.loadingTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1列">{{ item.bay }}</el-descriptions-item>
                <el-descriptions-item label="工位2列">{{ item.bayTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1层">{{ item.lev }}</el-descriptions-item>
                <el-descriptions-item label="工位2层">{{ item.levTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1货叉定位">{{ item.forkOffset }}</el-descriptions-item>
                <el-descriptions-item label="工位2货叉定位">{{ item.forkOffsetTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1载货台定位">{{ item.liftPos }}</el-descriptions-item>
                <el-descriptions-item label="工位2载货台定位">{{ item.liftPosTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1走行在定位">{{ item.walkPos }}</el-descriptions-item>
                <el-descriptions-item label="工位2走行在定位">{{ item.walkPosTwo }}</el-descriptions-item>
                <el-descriptions-item label="工位1任务接收">{{ item.taskReceive }}</el-descriptions-item>
                <el-descriptions-item label="工位2任务接收">{{ item.taskReceiveTwo }}</el-descriptions-item>
                <el-descriptions-item label="走行速度(m/min)">{{ item.xspeed }}</el-descriptions-item>
                <el-descriptions-item label="升降速度(m/min)">{{ item.yspeed }}</el-descriptions-item>
                <el-descriptions-item label="叉牙速度(m/min)">{{ item.zspeed }}</el-descriptions-item>
                <el-descriptions-item label="走行距离(Km)">{{ item.xdistance }}</el-descriptions-item>
                <el-descriptions-item label="升降距离(Km)">{{ item.ydistance }}</el-descriptions-item>
                <el-descriptions-item label="走行时长(H)">{{ item.xduration }}</el-descriptions-item>
                <el-descriptions-item label="升降时长(H)">{{ item.yduration }}</el-descriptions-item>
                <el-descriptions-item label="扩展数据">{{ item.extend }}</el-descriptions-item>
              </el-descriptions>
            </el-collapse-item>
          </el-collapse>
        </div>
        <div style="display:flex; justify-content:flex-end; margin-top:8px;">
          <el-pagination
            @current-change="handlePageChange"
            @size-change="handleSizeChange"
            :current-page="currentPage"
            :page-size="pageSize"
            :page-sizes="[10,20,50,100]"
            layout="total, prev, pager, next"
            :total="crnList.length">
          </el-pagination>
        </div>
    </div>
  `,
  props: ["param"],
  data() {
    return {
      crnList: [],
      activeNames: "",
      searchCrnNo: "",
      showControl: false,
      controlParam: {
        crnNo: "",
        sourceLocNo: "",
        targetLocNo: "",
        station: 1,
      },
      pageSize: 25,
      currentPage: 1,
    };
  },
  created() {
    setInterval(() => {
      this.getDualCrnStateInfo();
    }, 1000);
  },
  computed: {
    displayCrnList() {
      const start = (this.currentPage - 1) * this.pageSize;
      const end = start + this.pageSize;
      return this.crnList.slice(start, end);
    }
  },
  watch: {
    param: {
      handler(newVal) {
        if (newVal.crnNo != 0) {
          this.activeNames = newVal.crnNo;
          this.searchCrnNo = newVal.crnNo;
          const idx = this.crnList.findIndex(i => i.crnNo == newVal.crnNo);
          if (idx >= 0) { this.currentPage = Math.floor(idx / this.pageSize) + 1; }
        }
      },
      deep: true,
      immediate: true,
    },
  },
  methods: {
    handlePageChange(page) {
      this.currentPage = page;
    },
    handleSizeChange(size) {
      this.pageSize = size;
      this.currentPage = 1;
    },
    openControl() {
      this.showControl = !this.showControl;
    },
    getDualCrnStateInfo() {
      if (this.$root.sendWs) {
        this.$root.sendWs(JSON.stringify({
          "url": "/dualcrn/table/crn/state",
          "data": {}
        }));
      }
    },
    controlCommandTransport() {
      let that = this;
      $.ajax({
        url: baseUrl + "/dualcrn/command/take",
        headers: {
          token: localStorage.getItem("token"),
        },
        contentType: "application/json",
        method: "post",
        data: JSON.stringify(that.controlParam),
        success: (res) => {
          if (res.code == 200) {
            that.$message({
              message: res.msg,
              type: "success",
            });
          } else {
            that.$message({
              message: res.msg,
              type: "warning",
            });
          }
        },
      });
    },
    controlCommandPickup() {
      let that = this;
      $.ajax({
        url: baseUrl + "/dualcrn/command/pick",
        headers: {
          token: localStorage.getItem("token"),
        },
        contentType: "application/json",
        method: "post",
        data: JSON.stringify(that.controlParam),
        success: (res) => {
          if (res.code == 200) {
            that.$message({
              message: res.msg,
              type: "success",
            });
          } else {
            that.$message({
              message: res.msg,
              type: "warning",
            });
          }
        },
      });
    },
    controlCommandPutdown() {
      let that = this;
      $.ajax({
        url: baseUrl + "/dualcrn/command/put",
        headers: {
          token: localStorage.getItem("token"),
        },
        contentType: "application/json",
        method: "post",
        data: JSON.stringify(that.controlParam),
        success: (res) => {
          if (res.code == 200) {
            that.$message({
              message: res.msg,
              type: "success",
            });
          } else {
            that.$message({
              message: res.msg,
              type: "warning",
            });
          }
        },
      });
    },
    controlCommandMove() {
      let that = this;
      $.ajax({
        url: baseUrl + "/dualcrn/command/move",
        headers: {
          token: localStorage.getItem("token"),
        },
        contentType: "application/json",
        method: "post",
        data: JSON.stringify(that.controlParam),
        success: (res) => {
          if (res.code == 200) {
            that.$message({
              message: res.msg,
              type: "success",
            });
          } else {
            that.$message({
              message: res.msg,
              type: "warning",
            });
          }
        },
      });
    },
    controlCommandTaskComplete() {
      let that = this;
      $.ajax({
        url: baseUrl + "/dualcrn/command/taskComplete",
        headers: {
          token: localStorage.getItem("token"),
        },
        contentType: "application/json",
        method: "post",
        data: JSON.stringify(that.controlParam),
        success: (res) => {
          if (res.code == 200) {
            that.$message({
              message: res.msg,
              type: "success",
            });
          } else {
            that.$message({
              message: res.msg,
              type: "warning",
            });
          }
        },
      });
    },
    setDualCrnList(res) {
      let that = this;
      if (res.code == 200) {
        let list = res.data;
        if (that.searchCrnNo == "") {
          that.crnList = list;
        } else {
          let tmp = [];
          list.forEach((item) => {
            if (item.crnNo == that.searchCrnNo) {
              tmp.push(item);
            }
          });
          that.crnList = tmp;
          that.currentPage = 1;
        }
      }
    },
  },
});
src/main/webapp/components/WatchRgvCard.js
@@ -45,6 +45,7 @@
                <el-descriptions-item label="是否有物">{{ item.loading }}</el-descriptions-item>
                <el-descriptions-item label="故障代码">{{ item.warnCode }}</el-descriptions-item>
                <el-descriptions-item label="故障描述">{{ item.alarm }}</el-descriptions-item>
                <el-descriptions-item label="扩展数据">{{ item.extend }}</el-descriptions-item>
            </el-descriptions>
            </el-collapse-item>
          </el-collapse>
src/main/webapp/views/watch/console.html
@@ -23,6 +23,9 @@
                        <el-tab-pane label="堆垛机" name="crn">
                            <watch-crn-card ref="watchCrnCard" :param="crnParam"></watch-crn-card>
                        </el-tab-pane>
                        <el-tab-pane label="双工位堆垛机" name="dualCrn">
                            <watch-dual-crn-card ref="watchDualCrnCard" :param="dualCrnParam"></watch-dual-crn-card>
                        </el-tab-pane>
                        <el-tab-pane label="输送站" name="devp">
                            <devp-card ref="devpCard" :param="devpParam"></devp-card>
                        </el-tab-pane>
@@ -35,7 +38,7 @@
                    </el-tabs>
                </div>
                <map-canvas :lev="currentLev" :crn-param="crnParam" :rgv-param="rgvParam" :devp-param="devpParam" @crn-click="openCrn" @station-click="openSite" style="width: 80%; height: 100vh;"></map-canvas>
                <map-canvas :lev="currentLev" :crn-param="crnParam" :rgv-param="rgvParam" :devp-param="devpParam" @crn-click="openCrn" @dual-crn-click="openDualCrn" @station-click="openSite" style="width: 80%; height: 100vh;"></map-canvas>
                <div style="position: absolute;top: 15px;left: 50%;display: flex;">
                    <div v-if="levList.length > 1" v-for="(lev,index) in levList" :key="index" style="margin-right: 10px;">
@@ -47,6 +50,7 @@
        </div>
        <script src="../../components/WatchCrnCard.js"></script>
        <script src="../../components/WatchDualCrnCard.js"></script>
        <script src="../../components/DevpCard.js"></script>
        <script src="../../components/MapSettingCard.js"></script>
        <script src="../../components/WatchRgvCard.js"></script>
@@ -66,6 +70,9 @@
                    crnParam: {
                        crnNo: 0
                    },
                    dualCrnParam: {
                        crnNo: 0
                    },
                    mapSettingParam: {
                        zoom: 70
                    },
@@ -75,12 +82,20 @@
                    rgvParam: {
                        rgvNo: 0
                    },
                    locMastData: []//库位数据
                    locMastData: [],//库位数据
                    wsReconnectTimer: null,
                    wsReconnectAttempts: 0,
                    wsReconnectBaseDelay: 1000,
                    wsReconnectMaxDelay: 15000
                },
                created() {
                    this.init()
                },
                mounted() {
                },
                beforeDestroy() {
                    if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
                    if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { try { ws.close(); } catch (e) {} }
                },
                watch: {
@@ -93,18 +108,27 @@
                    },
                    webSocketOnOpen() {
                        console.log("WebSocket连接成功");
                        if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
                        this.wsReconnectAttempts = 0;
                        this.getMap();
                    },
                    webSocketOnError() {
                        console.log("WebSocket连接发生错误");
                        this.scheduleWsReconnect();
                    },
                    webSocketClose() {
                        console.log("WebSocket连接关闭");
                        this.scheduleWsReconnect();
                    },
                    webSocketOnMessage(e) {
                        const result = JSON.parse(e.data);
                        if (result.url == "/crn/table/crn/state") {
                             if(this.$refs.watchCrnCard) {
                                 this.$refs.watchCrnCard.setCrnList(JSON.parse(result.data));
                             }
                        } else if (result.url == "/dualcrn/table/crn/state") {
                             if(this.$refs.watchDualCrnCard) {
                                 this.$refs.watchDualCrnCard.setDualCrnList(JSON.parse(result.data));
                             }
                        } else if (result.url == "/console/latest/data/station") {
                             if(this.$refs.devpCard) {
@@ -129,15 +153,30 @@
                        }))
                    },
                    init() {
                        this.connectWs();
                        this.getSystemRunningStatus() //获取系统运行状态
                        this.getLevList() //获取地图层级列表
                        this.getLocMastData() //获取库位数据
                    },
                    connectWs() {
                        if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { return; }
                        ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
                        ws.onopen = this.webSocketOnOpen;
                        ws.onerror = this.webSocketOnError;
                        ws.onmessage = this.webSocketOnMessage;
                        ws.onclose = this.webSocketClose;
                        this.getSystemRunningStatus() //获取系统运行状态
                        this.getLevList() //获取地图层级列表
                        this.getLocMastData() //获取库位数据
                    },
                    scheduleWsReconnect() {
                        if (this.wsReconnectTimer) { return; }
                        const attempt = this.wsReconnectAttempts + 1;
                        const jitter = Math.floor(Math.random() * 300);
                        const delay = Math.min(this.wsReconnectMaxDelay, this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts)) + jitter;
                        this.wsReconnectTimer = setTimeout(() => {
                            this.wsReconnectTimer = null;
                            this.wsReconnectAttempts = attempt;
                            this.connectWs();
                        }, delay);
                    },
                    getLevList() {
                        let that = this;
@@ -161,6 +200,13 @@
                    openCrn(id) {
                        this.crnParam.crnNo = id;
                        this.activateCard = 'crn';
                        console.log(id);
                    },
                    openDualCrn(id) {
                        this.dualCrnParam.crnNo = id;
                        this.activateCard = 'dualCrn';
                        console.log(id);
                    },
                    openRgv(id) {
                        this.rgvParam.rgvNo = id;