#
Junjie
2025-11-13 1b4fbdb92537036aed4d648967ef7e7ab8842aec
#
9个文件已添加
1个文件已删除
11个文件已修改
1352 ■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/BasMapController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/ConsoleController.java 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/StationController.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/domain/enums/StationStatusType.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/domain/param/StationCommandMoveParam.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/domain/vo/StationLatestDataVo.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/MapExcelUtils.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/model/NavigateNode.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/utils/NavigateSolution.java 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/utils/NavigateUtils.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/ServerBootstrap.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/protocol/StationProtocol.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java 148 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/real/ZyStationRealConnect.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/StationThread.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZyStationThread.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/DevpCard.js 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/WatchCrnCard.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/watch/console.html 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/watch/test.json 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/BasMapController.java
@@ -130,6 +130,16 @@
        return R.ok();
    }
    @GetMapping("/basMap/lev/{lev}/auth")
    @ManagerAuth
    public R getByLev(@PathVariable("lev") Integer lev) {
        BasMap basMap = basMapService.selectOne(new EntityWrapper<BasMap>().eq("lev", lev));
        if (basMap == null){
            return R.error("地图不存在");
        }
        return R.ok().add(basMap.getData());
    }
    @Autowired
    private MapExcelUtils mapExcelUtils;
@@ -165,7 +175,6 @@
                    }else if (nodeType.equals("RGB(0,112,192)")) {
                        //输送线
                        nodeData.put("type", "devp");
                        nodeData.put("value", (int) Double.parseDouble(map.get("value").toString()));
                    }else if (nodeType.equals("RGB(0,176,240)")) {
                        //RGV
                        nodeData.put("type", "rgv");
@@ -188,8 +197,17 @@
            }
        }
        return R.ok().add(JSON.toJSONString(dataList));
        BasMap basMap = basMapService.selectOne(new EntityWrapper<BasMap>().eq("lev", 1));
        if (basMap == null){
            basMap = new BasMap();
        }
        basMap.setLev(1);
        basMap.setData(JSON.toJSONString(dataList));
        basMap.setOriginData(JSON.toJSONString(dataList));
        basMap.setCreateTime(new Date());
        basMap.setUpdateTime(new Date());
        basMapService.insertOrUpdate(basMap);
        return R.ok();
    }
}
src/main/java/com/zy/asrs/controller/ConsoleController.java
@@ -1,15 +1,15 @@
package com.zy.asrs.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.annotations.ManagerAuth;
import com.core.common.Cools;
import com.core.common.R;
import com.core.exception.CoolException;
import com.zy.asrs.domain.enums.CrnStatusType;
import com.zy.asrs.domain.enums.StationStatusType;
import com.zy.asrs.domain.param.SystemSwitchParam;
import com.zy.asrs.domain.vo.CrnDetailVo;
import com.zy.asrs.domain.vo.CrnLatestDataVo;
import com.zy.asrs.domain.vo.StationLatestDataVo;
import com.zy.asrs.entity.BasCrnpErr;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.asrs.entity.WrkMast;
@@ -17,15 +17,15 @@
import com.zy.asrs.service.DeviceConfigService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.CodeRes;
import com.zy.core.Slave;
import com.zy.core.ThreadHandler;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.CrnModeType;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.properties.SystemProperties;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.impl.ZySiemensCrnThread;
import com.zy.core.thread.StationThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -50,54 +50,73 @@
    @PostMapping("/system/running/status")
    @ManagerAuth(memo = "系统运行状态")
    public R systemRunningStatus(){
    public R systemRunningStatus() {
        return R.ok().add(Cools.add("status", SystemProperties.WCS_RUNNING_STATUS.get()));
    }
    @PostMapping("/system/switch")
    @ManagerAuth(memo = "系统运行开关操作")
    public R systemSwitch(SystemSwitchParam param) throws InterruptedException {
        if (Cools.isEmpty(param.getOperatorType())){
        if (Cools.isEmpty(param.getOperatorType())) {
            return R.error();
        }
        if (param.getOperatorType() == 0) {
            if (Cools.isEmpty(param.getPassword())){
            if (Cools.isEmpty(param.getPassword())) {
                return R.error("请输入口令");
            }
            if (!param.getPassword().equals(SystemProperties.WCS_PASSWORD)){
            if (!param.getPassword().equals(SystemProperties.WCS_PASSWORD)) {
                return R.error("口令错误");
            }
        }
        Thread.sleep(200L);
        SystemProperties.WCS_RUNNING_STATUS.set(param.getOperatorType()==1?Boolean.TRUE:Boolean.FALSE);
        SystemProperties.WCS_RUNNING_STATUS.set(param.getOperatorType() == 1 ? Boolean.TRUE : Boolean.FALSE);
        return R.ok().add(Cools.add("status", SystemProperties.WCS_RUNNING_STATUS.get()));
    }
//    @PostMapping("/latest/data/site")
//    @ManagerAuth(memo = "站点实时数据")
//    public R siteLatestData(){
//        List<SiteLatestDataVo> vos = new ArrayList<>();
//        Map<Integer, StaProtocol> stations = new HashMap<>();
//        for (DevpSlave devp : slaveProperties.getDevp()) {
//            DevpThread devpThread = (DevpThread) SlaveConnection.get(SlaveType.Devp, devp.getId());
//            if (null != devpThread) {
//                stations.putAll(devpThread.getStation());
//            }
//        }
//        for (Map.Entry<Integer, StaProtocol> entry : stations.entrySet()) {
//            SiteLatestDataVo vo = new SiteLatestDataVo();
//            StaProtocol staProtocol = entry.getValue();
//            vo.setSiteId(String.valueOf(entry.getKey())); // 站点编号
//            vo.setWorkNo(staProtocol.getWorkNo()); // 工作号
//            vo.setSiteStatus(SiteStatusType.process(staProtocol));  // 状态
//            vos.add(vo);
//        }
//        return R.ok().add(vos);
//    }
    @PostMapping("/latest/data/station")
    @ManagerAuth(memo = "站点实时数据")
    public R stationLatestData() {
        List<StationLatestDataVo> vos = new ArrayList<>();
        List<DeviceConfig> devpList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
                .eq("device_type", String.valueOf(SlaveType.Devp)));
        for (DeviceConfig deviceConfig : devpList) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp,
                    deviceConfig.getDeviceNo());
            if (stationThread == null) {
                continue;
            }
            List<StationProtocol> statusList = stationThread.getStatus();
            if (statusList == null || statusList.isEmpty()) {
                continue;
            }
            for (StationProtocol stationProtocol : statusList) {
                StationLatestDataVo vo = new StationLatestDataVo();
                vo.setStationId(stationProtocol.getStationId()); // 站点编号
                vo.setTaskNo(stationProtocol.getTaskNo()); // 任务号
                vo.setTargetStaNo(stationProtocol.getTargetStaNo()); // 目标站点
                vo.setAutoing(stationProtocol.isAutoing()); // 是否自动
                vo.setLoading(stationProtocol.isLoading()); // 是否有物
                vo.setInEnable(stationProtocol.isInEnable()); // 是否可入
                vo.setOutEnable(stationProtocol.isOutEnable()); // 是否可出
                vo.setEmptyMk(stationProtocol.isEmptyMk()); // 是否空板
                vo.setFullPlt(stationProtocol.isFullPlt()); // 是否满板
                vo.setPalletHeight(stationProtocol.getPalletHeight()); // 托盘高度
                vo.setError(stationProtocol.getError()); // 错误码
                vo.setBarcode(stationProtocol.getBarcode()); // 条码
                String stationStatus = StationStatusType.process(stationProtocol).toString().toLowerCase().replaceAll("_", "-");
                vo.setStationStatus(stationStatus);
                vos.add(vo);
            }
        }
        return R.ok().add(vos);
    }
    @PostMapping("/latest/data/crn")
    @ManagerAuth(memo = "堆垛机实时数据")
    public R crnLatestData(){
    public R crnLatestData() {
        List<CrnLatestDataVo> vos = new ArrayList<>();
        List<DeviceConfig> crnList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
@@ -114,25 +133,27 @@
                continue;
            }
            CrnLatestDataVo vo = new CrnLatestDataVo();
            vo.setCrnId(crnProtocol.getCrnNo());   //  堆垛机编号
            vo.setCrnId(crnProtocol.getCrnNo()); // 堆垛机编号
            vo.setOffset((double) new Random().nextInt(560));     //  堆垛机偏移量
            vo.setBay(crnProtocol.getBay());    //  当前列
            vo.setOffset((double) new Random().nextInt(560)); // 堆垛机偏移量
            vo.setBay(crnProtocol.getBay()); // 当前列
            /**
             * 堆垛机状态判断
             */
            if (crnProtocol.getAlarm() > 0) {
                vo.setCrnStatus(CrnStatusType.MACHINE_ERROR);
            } else {
                if (crnProtocol.getTaskNo()>0) {
                if (crnProtocol.getTaskNo() > 0) {
                    WrkMast wrkMast = wrkMastService.selectById(crnProtocol.getTaskNo());
                    if (wrkMast != null) {
                        vo.setCrnStatus(CrnStatusType.process(wrkMast.getIoType()));
                    } else {
                        vo.setCrnStatus(crnProtocol.modeType.equals(CrnModeType.AUTO)? CrnStatusType.MACHINE_AUTO: CrnStatusType.MACHINE_UN_AUTO);
                        vo.setCrnStatus(crnProtocol.modeType.equals(CrnModeType.AUTO) ? CrnStatusType.MACHINE_AUTO
                                : CrnStatusType.MACHINE_UN_AUTO);
                    }
                } else {
                    vo.setCrnStatus(crnProtocol.modeType.equals(CrnModeType.AUTO)? CrnStatusType.MACHINE_AUTO: CrnStatusType.MACHINE_UN_AUTO);
                    vo.setCrnStatus(crnProtocol.modeType.equals(CrnModeType.AUTO) ? CrnStatusType.MACHINE_AUTO
                            : CrnStatusType.MACHINE_UN_AUTO);
                }
            }
            vos.add(vo);
@@ -140,61 +161,62 @@
        return R.ok().add(vos);
    }
//    @PostMapping("/latest/data/rgv")
//    @ManagerAuth(memo = "RGV实时数据")
//    public R rgvLatestData(){
//        List<RgvLatestDataVo> vos = new ArrayList<>();
//        for (RgvSlave rgvSlave : slaveProperties.getRgv()) {
//            // 获取堆垛机信息
//            RgvThread rgvThread = (RgvThread) SlaveConnection.get(SlaveType.Rgv, rgvSlave.getId());
//            if (rgvThread == null) {
//                continue;
//            }
//            RgvProtocol rgvProtocol = rgvThread.getRgvProtocol();
//            if (rgvProtocol == null) {
//                continue;
//            }
//            RgvLatestDataVo vo = new RgvLatestDataVo();
//            vo.setRgvId(rgvProtocol.getRgvNo());   //  RGV编号
//            vo.setTrackSiteNo(String.valueOf(rgvProtocol.getRgvPos()));
//            vo.setRgvStatus(rgvProtocol.getStatusType());
//            vos.add(vo);
//
//        }
//        Object object = redisUtil.get("rgvLatestData");
//        List<Object> siteLatestDataVos = JSON.parseArray(object.toString());
//        return R.ok().add(siteLatestDataVos);
//    }
    // @PostMapping("/latest/data/rgv")
    // @ManagerAuth(memo = "RGV实时数据")
    // public R rgvLatestData(){
    // List<RgvLatestDataVo> vos = new ArrayList<>();
    // for (RgvSlave rgvSlave : slaveProperties.getRgv()) {
    // // 获取堆垛机信息
    // RgvThread rgvThread = (RgvThread) SlaveConnection.get(SlaveType.Rgv,
    // rgvSlave.getId());
    // if (rgvThread == null) {
    // continue;
    // }
    // RgvProtocol rgvProtocol = rgvThread.getRgvProtocol();
    // if (rgvProtocol == null) {
    // continue;
    // }
    // RgvLatestDataVo vo = new RgvLatestDataVo();
    // vo.setRgvId(rgvProtocol.getRgvNo()); // RGV编号
    // vo.setTrackSiteNo(String.valueOf(rgvProtocol.getRgvPos()));
    // vo.setRgvStatus(rgvProtocol.getStatusType());
    // vos.add(vo);
    //
    // }
    // Object object = redisUtil.get("rgvLatestData");
    // List<Object> siteLatestDataVos = JSON.parseArray(object.toString());
    // return R.ok().add(siteLatestDataVos);
    // }
//    @PostMapping("/latest/data/barcode")
//    @ManagerAuth(memo = "条码扫描仪实时数据")
//    public R barcodeLatestData(){
//        List<BarcodeDataVo> list = new ArrayList<>();
//        for (Slave barcode : slaveProperties.getBarcode()) {
//            BarcodeThread barcodeThread = (BarcodeThread) SlaveConnection.get(SlaveType.Barcode, barcode.getId());
//            if (null == barcodeThread) {
//                continue;
//            }
//            BarcodeDataVo vo = new BarcodeDataVo();
//            vo.setBarcodeId(barcode.getId());
//            vo.setCodeValue(barcodeThread.getBarcode());
//            list.add(vo);
//        }
//        return R.ok().add(list);
//    }
    // @PostMapping("/latest/data/barcode")
    // @ManagerAuth(memo = "条码扫描仪实时数据")
    // public R barcodeLatestData(){
    // List<BarcodeDataVo> list = new ArrayList<>();
    // for (Slave barcode : slaveProperties.getBarcode()) {
    // BarcodeThread barcodeThread = (BarcodeThread)
    // SlaveConnection.get(SlaveType.Barcode, barcode.getId());
    // if (null == barcodeThread) {
    // continue;
    // }
    // BarcodeDataVo vo = new BarcodeDataVo();
    // vo.setBarcodeId(barcode.getId());
    // vo.setCodeValue(barcodeThread.getBarcode());
    // list.add(vo);
    // }
    // return R.ok().add(list);
    // }
    @PostMapping("/crn/detail")
    @ManagerAuth(memo = "堆垛机设备数据详情")
    public R crnDetail(@RequestParam Integer crnNo){
        if (Cools.isEmpty(crnNo)){
    public R crnDetail(@RequestParam Integer crnNo) {
        if (Cools.isEmpty(crnNo)) {
            return R.parse(CodeRes.EMPTY);
        }
        CrnDetailVo vo = new CrnDetailVo();
        DeviceConfig deviceConfig = deviceConfigService.selectOne(new EntityWrapper<DeviceConfig>()
                .eq("device_type", String.valueOf(SlaveType.Crn))
                .eq("device_no", crnNo)
        );
                .eq("device_no", crnNo));
        if (deviceConfig == null) {
            return R.error("设备不存在");
@@ -216,12 +238,12 @@
            if (wrkMast != null) {
                vo.setSourceStaNo(String.valueOf(wrkMast.getSourceStaNo()));
                vo.setStaNo(String.valueOf(wrkMast.getStaNo()));
                vo.setWrkSts(wrkMast.getWrkSts$());   // 工作状态
                vo.setIoType(wrkMast.getIoType$());   //  入出库类型
                vo.setWrkSts(wrkMast.getWrkSts$()); // 工作状态
                vo.setIoType(wrkMast.getIoType$()); // 入出库类型
                vo.setSourceLocNo(wrkMast.getSourceLocNo$());
                vo.setLocNo(wrkMast.getLocNo$());
                vo.setCrnStatus(crnProtocol.getStatusType().desc);
                vo.setError("");    // todo
                vo.setError(""); // todo
            }
        }
        return R.ok().add(vo);
src/main/java/com/zy/asrs/controller/StationController.java
New file
@@ -0,0 +1,75 @@
package com.zy.asrs.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.service.BasDevpService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.core.common.Cools;
import com.core.common.R;
import com.zy.asrs.domain.param.StationCommandMoveParam;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.Task;
import com.zy.core.model.command.StationCommand;
import com.zy.core.thread.StationThread;
import java.util.List;
@RestController
@RequestMapping("/station")
public class StationController {
    @Autowired
    private BasDevpService basDevpService;
    @PostMapping("/command/move")
    public R commandMove(@RequestBody StationCommandMoveParam param) {
        if (Cools.isEmpty(param)) {
            return R.error("缺少参数");
        }
        Integer stationId = param.getStationId();
        Integer taskNo = param.getTaskNo();
        Integer targetStationId = param.getTargetStationId();
        JSONObject finalStation = null;
        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
        for (BasDevp basDevp : basDevps) {
            List<JSONObject> list = JSON.parseArray(basDevp.getStationList(), JSONObject.class);
            for (JSONObject entity : list) {
                if(entity.getInteger("stationId").equals(stationId)){
                    finalStation = entity;
                    break;
                }
            }
            if(finalStation != null){
                break;
            }
        }
        if(finalStation == null){
            return R.error("站点不存在");
        }
        Integer devpNo = finalStation.getInteger("devpNo");
        StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, devpNo);
        if (stationThread == null) {
            return R.error("线程不存在");
        }
        StationCommand command = stationThread.getMoveCommand(taskNo, stationId, targetStationId, 0);
        MessageQueue.offer(SlaveType.Devp, devpNo, new Task(2, command));
        return R.ok();
    }
}
src/main/java/com/zy/asrs/domain/enums/StationStatusType.java
New file
@@ -0,0 +1,45 @@
package com.zy.asrs.domain.enums;
import com.zy.core.model.protocol.StationProtocol;
/**
 * 站点状态枚举
 */
public enum StationStatusType {
    // 自动
    SITE_AUTO,
    // 非自动
    SITE_UNAUTO,
    // 自动+有物+ID
    SITE_AUTO_RUN_ID,
    // 自动+有物
    SITE_AUTO_RUN,
    // 自动+ID
    SITE_AUTO_ID,
    ;
    public static StationStatusType process(StationProtocol stationProtocol){
        if (stationProtocol == null) {
            return null;
        }
        if (stationProtocol.isAutoing() && stationProtocol.isLoading() && stationProtocol.getTaskNo() > 0) {
            return SITE_AUTO_RUN_ID;
        }
        if (stationProtocol.isAutoing() && stationProtocol.isLoading()) {
            return SITE_AUTO_RUN;
        }
        if (stationProtocol.isAutoing() && stationProtocol.getTaskNo() > 0) {
            return SITE_AUTO_ID;
        }
        if (stationProtocol.isAutoing()) {
            return SITE_AUTO;
        }
        if (!stationProtocol.isAutoing()) {
            return SITE_UNAUTO;
        }
        return null;
    }
}
src/main/java/com/zy/asrs/domain/param/StationCommandMoveParam.java
New file
@@ -0,0 +1,17 @@
package com.zy.asrs.domain.param;
import lombok.Data;
@Data
public class StationCommandMoveParam {
    // 站点编号
    private Integer stationId;
    // 目标站点编号
    private Integer targetStationId;
    // 任务编号
    private Integer taskNo;
}
src/main/java/com/zy/asrs/domain/vo/StationLatestDataVo.java
New file
@@ -0,0 +1,46 @@
package com.zy.asrs.domain.vo;
import lombok.Data;
@Data
public class StationLatestDataVo {
    // 站点编号
    private Integer stationId;
    // 工作号
    private Integer taskNo;
    // 目标站
    private Integer targetStaNo;
    // 自动
    private boolean autoing;
    // 有物
    private boolean loading;
    // 可入
    private boolean inEnable;
    // 可出
    private boolean outEnable;
    // 空板信号
    private boolean emptyMk;
    // 满托盘
    private boolean fullPlt;
    // 托盘高度
    private Integer palletHeight;
    //报警
    private Integer error;
    //条码
    private String barcode;
    private String stationStatus;
}
src/main/java/com/zy/asrs/utils/MapExcelUtils.java
@@ -323,6 +323,7 @@
                            HashMap<String, Object> mapData = data.get(k - 2).get(l - 2);
                            mapData.put("bgColor", "merge");
                            mapData.put("value", map.get("value"));
                        }
                    }
src/main/java/com/zy/common/model/NavigateNode.java
New file
@@ -0,0 +1,64 @@
package com.zy.common.model;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.util.List;
/**
 * A*寻路算法Node节点
 */
@Data
public class NavigateNode implements Comparable<NavigateNode>, Cloneable, Serializable {
    private int x;//坐标x
    private int y;//坐标y
    private int F;//综合花费的步数
    private int G;//已经花费的步数
    private int H;//将要花费的步数
    private int value;
    @ToString.Exclude
    private NavigateNode Father;//父节点
    private List<String> directionList;//行走方向
    private String nodeValue;//节点数据
    private String nodeType;//节点类型
    public NavigateNode(int x, int y) {
        this.x = x;
        this.y = y;
    }
    //通过结点的坐标和目标结点的坐标可以计算出F, G, H三个属性
    //需要传入这个节点的上一个节点和最终的结点
    public void init_node(NavigateNode father, NavigateNode end) {
        this.Father = father;
        if (this.Father != null) {
            //走过的步数等于父节点走过的步数加一
            this.G = father.G + this.G;
        } else { //父节点为空代表它是第一个结点
            this.G = 0;
        }
        //以下计算方案为算法原始方案,没有去拐点方案。已被Solution计算时自动覆盖。
        //计算通过现在的结点的位置和最终结点的位置计算H值(曼哈顿法:坐标分别取差值相加)
        this.H = Math.abs(this.x - end.x) + Math.abs(this.y - end.y);
        this.F = this.G + this.H;
    }
    @Override
    public int compareTo(NavigateNode o) {
        return Integer.compare(this.F, o.F);
    }
    @Override
    public NavigateNode clone() {
        try {
            return (NavigateNode) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
src/main/java/com/zy/common/utils/NavigateSolution.java
New file
@@ -0,0 +1,218 @@
package com.zy.common.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.SpringUtils;
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasMap;
import com.zy.asrs.service.BasMapService;
import com.zy.common.model.NavigateNode;
import com.zy.core.enums.MapNodeType;
import java.util.*;
/**
 * A*算法实现类
 */
public class NavigateSolution {
    // Open表用优先队列
    public PriorityQueue<NavigateNode> Open = new PriorityQueue<NavigateNode>();
    //Close表用普通的数组
    public ArrayList<NavigateNode> Close = new ArrayList<NavigateNode>();
    //用来存放已经出现过的结点。
    Map<String, Integer> bestGMap = new HashMap<>();
    public List<List<NavigateNode>> getStationMap() {
        BasMapService basMapService = SpringUtils.getBean(BasMapService.class);
        BasMap basMap = basMapService.selectOne(new EntityWrapper<BasMap>().eq("lev", 1));
        if (basMap == null) {
            throw new CoolException("地图不存在");
        }
        List<List<JSONObject>> dataList = JSON.parseObject(basMap.getData(), List.class);
        List<List<NavigateNode>> navigateNodeList = new ArrayList<>();
        for (int i = 0; i < dataList.size(); i++) {
            List<JSONObject> row = dataList.get(i);
            List<NavigateNode> navigateNodeRow = new ArrayList<>();
            for (int j = 0; j < row.size(); j++) {
                JSONObject map = row.get(j);
                NavigateNode navigateNode = new NavigateNode(i, j);
                String nodeType = map.getString("type");
                if(nodeType == null) {
                    navigateNode.setValue(MapNodeType.DISABLE.id);
                }else if(nodeType.equals("devp") || nodeType.equals("merge")){
                    navigateNode.setValue(MapNodeType.NORMAL_PATH.id);
                    JSONObject valueObj = JSON.parseObject(map.getString("value"));
                    List<String> directionList = valueObj.getJSONArray("direction").toJavaList(String.class);
                    navigateNode.setDirectionList(directionList);
                }else {
                    navigateNode.setValue(MapNodeType.DISABLE.id);
                }
                navigateNode.setNodeType(nodeType);
                navigateNode.setNodeValue(map.getString("value"));
                navigateNodeRow.add(navigateNode);
            }
            navigateNodeList.add(navigateNodeRow);
        }
        return navigateNodeList;
    }
    public NavigateNode astarSearchJava(List<List<NavigateNode>> map, NavigateNode start, NavigateNode end) {
        // 清理上次搜索的状态,防止复用实例导致记录残留
        this.Open.clear();
        this.Close.clear();
        this.bestGMap.clear();
        //把第一个开始的结点加入到Open表中
        this.Open.add(start);
        //主循环
        while (Open.size() > 0) {
            //取优先队列顶部元素并且把这个元素从Open表中删除
            NavigateNode current_node = Open.poll();
            if (current_node.getX() == end.getX() && current_node.getY() == end.getY()) {//找到目标结点就返回
                return current_node;
            }
            //将这个结点加入到Close表中
            Close.add(current_node);
            //对当前结点进行扩展,得到一个四周结点的数组
            ArrayList<NavigateNode> neighbour_node = extend_current_node(map, current_node);
            //对这个结点遍历,看是否有目标结点出现
            for (NavigateNode node : neighbour_node) {
                // 已在关闭列表中的节点不再处理,避免父链反复改写形成环
                if (Close.contains(node)) {
                    continue;
                }
                // G + H + E (对启发函数增加去拐点方案calcNodeExtraCost)
                int gCost = calcNodeExtraCost(current_node, node, end);
                //进行计算对G, F, H 等值
                node.setG(1 + gCost);
                node.init_node(current_node, end);
                node.setH(calcNodeCost(node, end));
                node.setF(node.getG() + node.getH());
                String key = node.getX() + "_" + node.getY();
                Integer recordedG = bestGMap.get(key);
                // 仅当找到更小的代价时才更新与入Open,避免等价代价反复入队导致父链抖动
                if (recordedG == null || node.getG() < recordedG) {
                    bestGMap.put(key, node.getG());
                    Open.add(node);
                }
            }
        }
        //如果遍历完所有出现的结点都没有找到最终的结点,返回null
        return null;
    }
    public ArrayList<NavigateNode> extend_current_node(List<List<NavigateNode>> map, NavigateNode current_node) {
        //获取当前结点的x, y
        int x = current_node.getX();
        int y = current_node.getY();
        //如果当前结点的邻结点合法,就加入到neighbour_node
        ArrayList<NavigateNode> neighbour_node = new ArrayList<NavigateNode>();
        if(current_node.getValue() == MapNodeType.DISABLE.id) {
            return neighbour_node;
        }
        List<String> directionList = current_node.getDirectionList();
        if(directionList == null || directionList.size() == 0) {
            return neighbour_node;
        }
        for(String direction : directionList) {
            if(direction.equals("top")) {
                NavigateNode node = getValidNavigateNode(map, x - 1, y);
                if(node != null) {
                    neighbour_node.add(node);
                }
            }else if(direction.equals("bottom")) {
                NavigateNode node = getValidNavigateNode(map, x + 1, y);
                if(node != null) {
                    neighbour_node.add(node);
                }
            }else if(direction.equals("left")) {
                NavigateNode node = getValidNavigateNode(map, x, y - 1);
                if(node != null) {
                    neighbour_node.add(node);
                }
            }else if(direction.equals("right")) {
                NavigateNode node = getValidNavigateNode(map, x, y + 1);
                if(node != null) {
                    neighbour_node.add(node);
                }
            }
        }
        return neighbour_node;
    }
    public NavigateNode getValidNavigateNode(List<List<NavigateNode>> map, int x, int y) {
        if (x < 0 || x >= map.size()
                || y < 0 || y >= map.get(0).size()) {
            return null;
        }
        NavigateNode node = map.get(x).get(y);
        if(node.getValue() == MapNodeType.DISABLE.id) {
            return null;
        }
        return node;
    }
    public NavigateNode findStationNavigateNode(List<List<NavigateNode>> map, int stationId) {
        for(int x = 0; x < map.size(); x++) {
            for(int y = 0; y < map.get(0).size(); y++) {
                NavigateNode node = map.get(x).get(y);
                if("devp".equals(node.getNodeType())) {
                    JSONObject valueObj = JSON.parseObject(node.getNodeValue());
                    if(valueObj.getInteger("stationId") == stationId) {
                        return node;
                    }
                }
            }
        }
        return null;
    }
    //------------------A*启发函数------------------//
    //计算通过现在的结点的位置和最终结点的位置计算H值(曼哈顿法:坐标分别取差值相加)
    private int calcNodeCost(NavigateNode node1, NavigateNode node2) {
        return Math.abs(node2.getX() - node1.getX()) + Math.abs(node2.getY() - node1.getY());
    }
    //去除拐点算法,给直线增加优先级
    private int calcNodeExtraCost(NavigateNode currNode, NavigateNode nextNode, NavigateNode endNode) {
        // 第一个点或直线点
        if (currNode.getFather() == null || nextNode.getX() == currNode.getFather().getX()
                || nextNode.getY() == currNode.getFather().getY()) {
            return 0;
        }
        // 拐向终点的点
        if (nextNode.getX() == endNode.getX() || nextNode.getY() == endNode.getY()) {
            return 1;
        }
        // 普通拐点
        /*
        拐点判断逻辑
        拿到父节点和下一节点
        通过判断父节点和下一节点的x数据和y数据都不相同时,则表明当前坐标是一个拐点
         */
        return 1;
    }
    //------------------A*启发函数-end------------------//
}
src/main/java/com/zy/common/utils/NavigateUtils.java
New file
@@ -0,0 +1,69 @@
package com.zy.common.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.core.exception.CoolException;
import com.zy.common.model.NavigateNode;
@Component
public class NavigateUtils {
    public synchronized List<NavigateNode> calcByStationId(Integer startStationId, Integer endStationId) {
        NavigateSolution navigateSolution = new NavigateSolution();
        List<List<NavigateNode>> stationMap = navigateSolution.getStationMap();
        NavigateNode startNode = navigateSolution.findStationNavigateNode(stationMap, startStationId);
        if (startNode == null){
            throw new CoolException("未找到该 起点 对应的节点");
        }
        NavigateNode endNode = navigateSolution.findStationNavigateNode(stationMap, endStationId);
        if (endNode == null){
            throw new CoolException("未找到该 终点 对应的节点");
        }
        NavigateNode res_node = navigateSolution.astarSearchJava(stationMap, startNode, endNode);
        if (res_node == null) {
            throw new CoolException("未找到该路径");
        }
        ArrayList<NavigateNode> list = new ArrayList<>();
        // 使用 visited 集合防止父链出现环导致死循环,同时设置安全步数上限
        HashSet<NavigateNode> visited = new HashSet<>();
        int maxSteps = stationMap.size() * stationMap.get(0).size() + 5; // 安全上限
        int steps = 0;
        while (res_node != null && visited.add(res_node) && steps++ < maxSteps) {
            list.add(res_node);
            res_node = res_node.getFather();//迭代操作
        }
        if (steps >= maxSteps) {
            throw new CoolException("路径回溯超出安全上限,疑似存在父链循环");
        }
        Collections.reverse(list);
        //将每个节点里面的fatherNode至为null(方便后续计算时父节点过多导致显示的节点太多)
        for (NavigateNode navigateNode : list) {
            //父节点设置为null,不影响计算结果,不影响后续操作。
            //此操作仅为后续排查处理提供视觉方便。
            navigateNode.setFather(null);
        }
        //去重
        HashSet<Integer> set = new HashSet<>();
        List<NavigateNode> fitlerList = new ArrayList<>();
        for(NavigateNode navigateNode : list){
            JSONObject valuObject = JSON.parseObject(navigateNode.getNodeValue());
            if(set.add(valuObject.getInteger("stationId"))){
                fitlerList.add(navigateNode);
            }
        }
        return fitlerList;
    }
}
src/main/java/com/zy/core/ServerBootstrap.java
@@ -9,6 +9,8 @@
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.SlaveType;
import com.zy.core.thread.impl.ZySiemensCrnThread;
import com.zy.core.thread.impl.ZyStationThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
@@ -95,6 +97,21 @@
            SlaveConnection.put(SlaveType.Crn, deviceConfig.getDeviceNo(), thread);
        }
        News.info("初始化输送站........................................................");
        List<DeviceConfig> devpList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
                .eq("device_type", String.valueOf(SlaveType.Devp)));
        for (DeviceConfig deviceConfig : devpList) {
            ThreadHandler thread = null;
            if (deviceConfig.getThreadImpl().equals("ZyStationThread")) {
                thread = new ZyStationThread(deviceConfig, redisUtil);
            } else {
                throw new CoolException("未知的线程实现");
            }
            new Thread(thread).start();
            SlaveConnection.put(SlaveType.Devp, deviceConfig.getDeviceNo(), thread);
        }
    }
src/main/java/com/zy/core/model/protocol/StationProtocol.java
New file
@@ -0,0 +1,57 @@
package com.zy.core.model.protocol;
import lombok.Data;
@Data
public class StationProtocol {
 // 站点编号
    private Integer stationId;
    // ----------------------------------------------------------------
    // 工作号
    private Integer taskNo = 0;
    // ----------------------------------------------------------------
    // 目标站
    private Integer targetStaNo;
    // ----------------------------------------------------------------
    // 自动
    private boolean autoing;
    // 有物
    private boolean loading;
    // 可入
    private boolean inEnable;
    // 可出
    private boolean outEnable;
    // 空板信号
    private boolean emptyMk;
    // 满托盘
    private boolean fullPlt;
    // 托盘高度
    private Integer palletHeight;
    //报警
    private Integer error;
    //条码
    private String barcode;
    @Override
    public StationProtocol clone() {
        try {
            return (StationProtocol) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java
@@ -35,9 +35,6 @@
    // 满托盘
    private boolean fullPlt;
    // 锁定标记
    private boolean pakMk = true;
    // 托盘高度
    private Integer palletHeight;
src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
@@ -1,12 +1,17 @@
package com.zy.core.network.fake;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.core.common.SpringUtils;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.common.model.NavigateNode;
import com.zy.common.utils.NavigateUtils;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.command.StationCommand;
import com.zy.core.network.api.ZyStationConnectApi;
import com.zy.core.network.entity.ZyStationStatusEntity;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
@@ -17,17 +22,14 @@
 */
public class ZyStationFakeConnect implements ZyStationConnectApi {
    private ZyStationStatusEntity status;
    private List<ZyStationStatusEntity> statusList = new ArrayList<>();
    private final DeviceConfig deviceConfig;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    // 允许并行执行多个命令任务(固定线程池)。如需更高并发可调整大小。
    private final ExecutorService executor = Executors
            .newFixedThreadPool(Math.max(2, Runtime.getRuntime().availableProcessors()));
    public ZyStationFakeConnect(DeviceConfig deviceConfig) {
        this.deviceConfig = deviceConfig;
        this.status = JSON.parseObject(deviceConfig.getFakeInitStatus(), ZyStationStatusEntity.class);
        if (this.status == null) {
            this.status = new ZyStationStatusEntity();
            this.status.setStationId(deviceConfig.getDeviceNo());
        }
    }
    @Override
@@ -37,43 +39,129 @@
    @Override
    public boolean disconnect() {
        executor.shutdownNow();
        return true;
    }
    @Override
    public List<ZyStationStatusEntity> getStatus() {
        return Collections.singletonList(status);
        if (this.statusList.isEmpty()) {
            this.statusList = JSON.parseArray(deviceConfig.getFakeInitStatus(), ZyStationStatusEntity.class);
            for (ZyStationStatusEntity status : this.statusList) {
                status.setAutoing(true);// 模拟自动运行
                status.setLoading(false);// 模拟有物
                status.setInEnable(true);// 模拟可入
                status.setOutEnable(true);// 模拟可出
                status.setEmptyMk(false);// 模拟空板信号
                status.setFullPlt(false);// 模拟满托盘
                status.setPalletHeight(0);// 模拟托盘高度为0
                status.setError(0);// 模拟无报警
                status.setBarcode("");// 模拟无条码
            }
        }
        return this.statusList;
    }
    @Override
    public CommandResponse sendCommand(StationCommand command) {
        CommandResponse response = new CommandResponse(false);
        executor.submit(() -> handleCommand(command));
        response.setResult(true);
        return response;
        executor.submit(() -> {
            try {
                handleCommand(command);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return new CommandResponse(true, "命令已受理(异步执行)");
    }
    private void handleCommand(StationCommand command) {
        // 简单的模拟:设置工作号和目标站,并模拟有物/可入/可出状态切换
        this.status.setTaskNo(command.getTaskNo());
        this.status.setTargetStaNo(command.getTargetStaNo());
        NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
        if (navigateUtils == null) {
            return;
        }
        // 模拟到站过程
        this.status.setAutoing(true);
        this.status.setLoading(true);
        sleep(1000);
        Integer taskNo = command.getTaskNo();
        Integer stationId = command.getStationId();
        Integer targetStationId = command.getTargetStaNo();
        // 模拟放下托盘
        this.status.setInEnable(true);
        this.status.setOutEnable(false);
        sleep(1000);
        List<NavigateNode> navigateNodes = null;
        // 完成任务,复位状态
        this.status.setLoading(false);
        this.status.setAutoing(false);
        this.status.setTaskNo(0);
        this.status.setInEnable(false);
        this.status.setOutEnable(true);
        try {
            navigateNodes = navigateUtils.calcByStationId(stationId, targetStationId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (navigateNodes == null) {
            return;
        }
        Integer lastStationId = null;
        for (int i = 0; i < navigateNodes.size(); i++) {
            NavigateNode navigateNode = navigateNodes.get(i);
            JSONObject valueObject = JSON.parseObject(navigateNode.getNodeValue());
            Integer currentStationId = valueObject.getInteger("stationId");
            ZyStationStatusEntity status = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (status == null) {
                continue;
            }
            try {
                while (true) {
                    ZyStationStatusEntity nextStatus = statusList.stream()
                            .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
                    if (nextStatus == null) {
                        continue;
                    }
                    if (nextStatus.getTaskNo() == 0) {
                        break;
                    }
                    sleep(100);
                }
            } catch (Exception e) {
                continue;
            }
            synchronized (status) {
                status.setTaskNo(taskNo);
                status.setTargetStaNo(targetStationId);
                status.setLoading(true);
            }
            if (lastStationId != null) {
                Integer finalLastStationId = lastStationId;
                ZyStationStatusEntity lastStatus = statusList.stream()
                        .filter(item -> item.getStationId().equals(finalLastStationId)).findFirst().orElse(null);
                if (lastStatus != null) {
                    synchronized (lastStatus) {
                        lastStatus.setTaskNo(0);
                        lastStatus.setTargetStaNo(0);
                        lastStatus.setLoading(false);
                    }
                }
            }
            lastStationId = currentStationId;
            sleep(1000);
        }
        sleep(10000);
        if (lastStationId != null) {
            Integer finalLastStationId = lastStationId;
            ZyStationStatusEntity lastStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(finalLastStationId)).findFirst().orElse(null);
            if (lastStatus != null) {
                synchronized (lastStatus) {
                    lastStatus.setTaskNo(0);
                    lastStatus.setTargetStaNo(0);
                    lastStatus.setLoading(false);
                }
            }
        }
    }
    private void sleep(long ms) {
src/main/java/com/zy/core/network/real/ZyStationRealConnect.java
@@ -19,8 +19,6 @@
import com.zy.core.model.command.StationCommand;
import com.zy.core.network.api.ZyStationConnectApi;
import com.zy.core.network.entity.ZyStationStatusEntity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
src/main/java/com/zy/core/thread/StationThread.java
@@ -3,14 +3,14 @@
import com.zy.core.ThreadHandler;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.command.StationCommand;
import com.zy.core.network.entity.ZyStationStatusEntity;
import com.zy.core.model.protocol.StationProtocol;
import java.util.List;
public interface StationThread extends ThreadHandler{
public interface StationThread extends ThreadHandler {
    List<ZyStationStatusEntity> getStatus();
    List<StationProtocol> getStatus();
    StationCommand getMoveCommand();
    StationCommand getMoveCommand(Integer taskNo, Integer stationId, Integer targetStationId, Integer palletSize);
    CommandResponse sendCommand(StationCommand command);
src/main/java/com/zy/core/thread/impl/ZyStationThread.java
@@ -1,13 +1,34 @@
package com.zy.core.thread.impl;
import HslCommunication.Profinet.Siemens.SiemensPLCS;
import HslCommunication.Profinet.Siemens.SiemensS7Net;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.service.BasDevpService;
import com.zy.core.network.DeviceConnectPool;
import com.zy.core.thread.StationThread;
import com.alibaba.fastjson.JSON;
import com.core.common.DateUtils;
import com.core.common.SpringUtils;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.asrs.entity.DeviceDataLog;
import com.zy.asrs.service.DeviceDataLogService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.network.ZyStationConnectDriver;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.OutputQueue;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.Task;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.network.entity.ZyStationStatusEntity;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import lombok.Data;
@@ -20,9 +41,11 @@
@Slf4j
public class ZyStationThread implements Runnable, StationThread {
    private List<StationProtocol> statusList = new ArrayList<>();
    private DeviceConfig deviceConfig;
    private RedisUtil redisUtil;
    private ZyStationConnectDriver zyStationConnectDriver;
    private long deviceDataLogTime = System.currentTimeMillis();
    public ZyStationThread(DeviceConfig deviceConfig, RedisUtil redisUtil) {
        this.deviceConfig = deviceConfig;
@@ -30,7 +53,103 @@
    }
    @Override
    @SuppressWarnings("InfiniteLoopStatement")
    public void run() {
        this.connect();
        while (true) {
            try {
                int step = 1;
                Task task = MessageQueue.poll(SlaveType.Devp, deviceConfig.getDeviceNo());
                if (task != null) {
                    step = task.getStep();
                }
                switch (step) {
                    // 读数据
                    case 1:
                        readStatus();
                        break;
                    case 2:
                        sendCommand((StationCommand) task.getData());
                        break;
                    default:
                        break;
                }
                Thread.sleep(200);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private void readStatus() {
        if (zyStationConnectDriver == null) {
            return;
        }
        if(statusList.isEmpty()) {
            BasDevpService basDevpService = SpringUtils.getBean(BasDevpService.class);
            if (basDevpService == null) {
                return;
            }
            BasDevp basDevp = basDevpService
                    .selectOne(new EntityWrapper<BasDevp>().eq("devp_no", deviceConfig.getDeviceNo()));
            if (basDevp == null) {
                return;
            }
            List<ZyStationStatusEntity> list = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class);
            for (ZyStationStatusEntity entity : list) {
                StationProtocol stationProtocol = new StationProtocol();
                stationProtocol.setStationId(entity.getStationId());
                statusList.add(stationProtocol);
            }
        }
        List<ZyStationStatusEntity> zyStationStatusEntities = zyStationConnectDriver.getStatus();
        for (ZyStationStatusEntity statusEntity : zyStationStatusEntities) {
            for (StationProtocol stationProtocol : statusList) {
                if(stationProtocol.getStationId().equals(statusEntity.getStationId())) {
                    stationProtocol.setTaskNo(statusEntity.getTaskNo());
                    stationProtocol.setTargetStaNo(statusEntity.getTargetStaNo());
                    stationProtocol.setAutoing(statusEntity.isAutoing());
                    stationProtocol.setLoading(statusEntity.isLoading());
                    stationProtocol.setInEnable(statusEntity.isInEnable());
                    stationProtocol.setOutEnable(statusEntity.isOutEnable());
                    stationProtocol.setEmptyMk(statusEntity.isEmptyMk());
                    stationProtocol.setFullPlt(statusEntity.isFullPlt());
                    stationProtocol.setPalletHeight(statusEntity.getPalletHeight());
                    stationProtocol.setError(statusEntity.getError());
                    stationProtocol.setBarcode(statusEntity.getBarcode());
                }
            }
        }
        OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功",DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
        if (System.currentTimeMillis() - deviceDataLogTime > 1000 * 1) {
            //采集时间超过1s,保存一次数据记录
            //保存数据记录
            DeviceDataLogService deviceDataLogService = SpringUtils.getBean(DeviceDataLogService.class);
            DeviceDataLog deviceDataLog = new DeviceDataLog();
            deviceDataLog.setOriginData(JSON.toJSONString(zyStationStatusEntities));
            deviceDataLog.setWcsData(JSON.toJSONString(statusList));
            deviceDataLog.setType(String.valueOf(SlaveType.Devp));
            deviceDataLog.setDeviceNo(deviceConfig.getDeviceNo());
            deviceDataLog.setCreateTime(new Date());
            deviceDataLogService.insert(deviceDataLog);
            //更新采集时间
            deviceDataLogTime = System.currentTimeMillis();
        }
    }
    @Override
    public boolean connect() {
        SiemensS7Net siemensS7Net = new SiemensS7Net(SiemensPLCS.S1200, deviceConfig.getIp());
        zyStationConnectDriver = new ZyStationConnectDriver(siemensS7Net, deviceConfig);
        new Thread(zyStationConnectDriver).start();
        DeviceConnectPool.put(SlaveType.Devp, deviceConfig.getDeviceNo(), zyStationConnectDriver);
        return true;
    }
@@ -39,24 +158,24 @@
    }
    @Override
    public List<ZyStationStatusEntity> getStatus() {
        if (zyStationConnectDriver == null) {
            return Collections.emptyList();
        }
        return zyStationConnectDriver.getStatus();
    public List<StationProtocol> getStatus() {
        return statusList;
    }
    @Override
    public StationCommand getMoveCommand() {
        return null;
    public StationCommand getMoveCommand(Integer taskNo, Integer stationId, Integer targetStationId, Integer palletSize) {
        StationCommand stationCommand = new StationCommand();
        stationCommand.setTaskNo(taskNo);
        stationCommand.setStationId(stationId);
        stationCommand.setTargetStaNo(targetStationId);
        stationCommand.setPalletSize(palletSize);
        return stationCommand;
    }
    @Override
    public CommandResponse sendCommand(StationCommand command) {
        return new CommandResponse(true);
       CommandResponse commandResponse = zyStationConnectDriver.sendCommand(command);
       return commandResponse;
    }
    @Override
    public void run() {
    }
}
src/main/webapp/components/DevpCard.js
New file
@@ -0,0 +1,139 @@
Vue.component("devp-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="searchStationId" placeholder="请输入站号"></el-input><el-button @click="getDevpStateInfo" 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.stationId" placeholder="站号"></el-input></div>
                <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.taskNo" placeholder="工作号"></el-input></div>
                <div style="margin-bottom: 10px;width: 33%;"><el-input size="mini" v-model="controlParam.targetStationId" placeholder="目标站"></el-input></div>
                <div style="margin-bottom: 10px;"><el-button @click="controlCommand()" size="mini">下发</el-button></div>
            </div>
        </div>
        <el-collapse v-model="activeNames">
          <el-collapse-item v-for="(item) in stationList" :name="item.stationId">
            <template slot="title">
                <div style="width: 100%;display: flex;">
                   <div style="width: 50%;">{{ item.stationId }}站</div>
                   <div style="width: 50%;text-align: right;">
                      <el-tag v-if="item.autoing" type="success" 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.stationId }}</el-descriptions-item>
                <el-descriptions-item label="工作号">{{ item.taskNo }}</el-descriptions-item>
                <el-descriptions-item label="目标站">{{ item.targetStaNo }}</el-descriptions-item>
                <el-descriptions-item label="模式">{{ item.autoing ? '自动' : '手动' }}</el-descriptions-item>
                <el-descriptions-item label="有物">{{ item.loading ? '有' : '无' }}</el-descriptions-item>
                <el-descriptions-item label="可入">{{ item.inEnable ? 'Y' : 'N' }}</el-descriptions-item>
                <el-descriptions-item label="可出">{{ item.outEnable ? 'Y' : 'N' }}</el-descriptions-item>
                <el-descriptions-item label="空板信号">{{ item.emptyMk ? 'Y' : 'N' }}</el-descriptions-item>
                <el-descriptions-item label="满板信号">{{ item.fullPlt ? 'Y' : 'N' }}</el-descriptions-item>
                <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>
          </el-collapse-item>
        </el-collapse>
    </div>
    `,
  props: ["param"],
  data() {
    return {
      stationList: [],
      activeNames: "",
      searchStationId: "",
      showControl: true,
      controlParam: {
        stationId: "",
        taskNo: "",
        targetStationId: "",
      },
    };
  },
  created() {
    setInterval(() => {
      this.getDevpStateInfo();
    }, 1000);
  },
  watch: {
    param: {
      handler(newVal, oldVal) {
        if (newVal.stationId != 0) {
          this.activeNames = newVal.stationId;
          this.searchStationId = newVal.stationId;
        }
      },
      deep: true, // 深度监听嵌套属性
      immediate: true, // 立即触发一次(可选)
    },
  },
  methods: {
    getDevpStateInfo() {
      let that = this;
      $.ajax({
        url: baseUrl + "/console/latest/data/station",
        headers: {
          token: localStorage.getItem("token"),
        },
        method: "post",
        success: (res) => {
          // 堆垛机信息表获取
          if (res.code == 200) {
            let list = res.data;
            if (that.searchStationId == "") {
              that.stationList = list;
            } else {
              let tmp = [];
              list.forEach((item) => {
                if (item.stationId == that.searchStationId) {
                  tmp.push(item);
                }
              });
              that.stationList = tmp;
            }
          }
        },
      });
    },
    openControl() {
      this.showControl = !this.showControl;
    },
    controlCommand() {
      let that = this;
      //下发命令
      $.ajax({
        url: baseUrl + "/station/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",
            });
          }
        },
      });
    },
  },
});
src/main/webapp/components/WatchCrnCard.js
@@ -7,7 +7,8 @@
        </div>
        <div style="margin-bottom: 10px;">
            <div style="margin-bottom: 5px;">
               <el-button @click="openControl" size="mini">控制中心</el-button>
               <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>
@@ -64,7 +65,7 @@
      crnList: [],
      activeNames: "",
      searchCrnNo: "",
      showControl: true,
      showControl: false,
      controlParam: {
        crnNo: "",
        sourceLocNo: "",
src/main/webapp/views/watch/console.html
@@ -21,7 +21,9 @@
                        <el-tab-pane label="堆垛机" name="crn">
                            <watch-crn-card :param="crnParam"></watch-crn-card>
                        </el-tab-pane>
                        <el-tab-pane label="输送线" name="devp">输送线</el-tab-pane>
                        <el-tab-pane label="输送站" name="devp">
                            <devp-card :param="devpParam"></devp-card>
                        </el-tab-pane>
                        <el-tab-pane label="RGV" name="rgv">RGV</el-tab-pane>
                        <el-tab-pane label="地图配置" name="mapSetting">
                            <map-setting-card :param="mapSettingParam"></map-setting-card>
@@ -45,7 +47,7 @@
                                    <div class="shelf">{{col.shelfIdx}}</div>
                                </div>
                                <div v-else-if="col.type == 'devp'">
                                    <div class="site" :style="{height: col.rowPx}" :id="'site-' + col.value" @click="openSite(col.value)">{{col.value}}</div>
                                    <div class="site" :style="{height: col.rowPx}" :id="'site-' + getStationId(col.value)" @click="openSite(getStationId(col.value))">{{getStationId(col.value)}}</div>
                                </div>
                                <div v-else-if="col.type == 'rgv'" style="position: relative;">
                                    <div class="rgv-item" v-if="getDeviceNo(col.value) != -1" :style="{width: col.width}" :id="'rgv-' + getDeviceNo(col.value)" @click="openRgv(getDeviceNo(col.value))">{{getDeviceNo(col.value)}}</div>
@@ -71,6 +73,7 @@
        </div>
        <script src="../../components/WatchCrnCard.js"></script>
        <script src="../../components/DevpCard.js"></script>
        <script src="../../components/MapSettingCard.js"></script>
        <script>
            var app = new Vue({
@@ -88,6 +91,9 @@
                    },
                    mapSettingParam: {
                        zoom: 70
                    },
                    devpParam: {
                        stationId: 0
                    }
                },
                created() {
@@ -101,11 +107,11 @@
                        this.getMap()
                        this.getSystemRunningStatus() //获取系统运行状态
                        // this.consoleInterval = setInterval(() => {
                        //     this.getCrnInfo() //获取堆垛机数据
                        //     this.getSiteInfo() //获取输送站点数据
                        //     this.getRgvInfo() //获取RGV数据
                        // }, 1000)
                        this.consoleInterval = setInterval(() => {
                            this.getCrnInfo() //获取堆垛机数据
                            this.getSiteInfo() //获取输送站点数据
                            this.getRgvInfo() //获取RGV数据
                        }, 1000)
                    },
                    //获取地图数据
                    getMap() {
@@ -113,7 +119,10 @@
                        let colPx = 35;
                        $.ajax({
                            url: "./test.json",
                            url: baseUrl + "/basMap/lev/1/auth",
                            headers: {
                                'token': localStorage.getItem('token')
                            },
                            method: "get",
                            success: (res) => {
                                let data = res.data;
@@ -166,47 +175,25 @@
                        })
                    },
                    openSite(id) {
                        this.siteWindow = true; //打开站点信息弹窗
                        $(".detailed").empty();
                        $('.detailed').append(id + '站点详细信息');
                        $.ajax({
                            url: baseUrl + "/console/site/detail",
                            headers: {
                                'token': localStorage.getItem('token')
                            },
                            data: {
                                siteId: id
                            },
                            method: 'post',
                            success: function(res) {
                                for (var val in res.data) {
                                    var find = $("#siteWindow").find(":input[name='" + val + "']");
                                    if (find[0].type === 'text') {
                                        find.val(res.data[val]);
                                    } else if (find[0].type === 'checkbox') {
                                        find.attr("checked", res.data[val] === 'Y');
                                    }
                                }
                            }
                        })
                        this.devpParam.stationId = id;
                    },
                    getSiteInfo() {
                        //获取输送站点数据
                        $.ajax({
                            url: baseUrl + "/console/latest/data/site",
                            url: baseUrl + "/console/latest/data/station",
                            headers: {'token': localStorage.getItem('token')},
                            method: 'POST',
                            success: function (res) {
                                console.log(res)
                                if (res.code === 200) {
                                    var sites = res.data;
                                    for (var i = 0; i < sites.length; i++){
                                        var siteEl = $("#site-"+sites[i].siteId);
                                        siteEl.attr("class", "site " + sites[i].siteStatus);
                                        if (sites[i].workNo != null && sites[i].workNo>0) {
                                            siteEl.html(sites[i].siteId + "[" + sites[i].workNo + "]");
                                        var siteEl = $("#site-"+sites[i].stationId);
                                        siteEl.attr("class", "site " + sites[i].stationStatus);
                                        if (sites[i].taskNo != null && sites[i].taskNo>0) {
                                            siteEl.html(sites[i].stationId + "[" + sites[i].taskNo + "]");
                                        } else {
                                            siteEl.html(sites[i].siteId);
                                            siteEl.html(sites[i].stationId);
                                        }
                                    }
                                } else if (res.code === 403) {
@@ -454,6 +441,17 @@
                            return -1;
                        }
                    },
                    getStationId(obj) {
                        if (this.isJson(obj)) {
                            let data = JSON.parse(obj)
                            if (data.stationId == null || data.stationId == undefined) {
                                return -1;
                            }
                            return data.stationId;
                        }else {
                            return -1;
                        }
                    },
                    getTrackSiteNo(obj) {
                        if (this.isJson(obj)) {
                            let data = JSON.parse(obj)
src/main/webapp/views/watch/test.json
File was deleted