自动化立体仓库 - WCS系统
1
zhangc
2025-04-12 4ec87f4d9d873487adda85686a1bfd7fa620f857
src/main/java/com/zy/core/thread/ShuttleThread.java
@@ -1,1065 +1,80 @@
package com.zy.core.thread;
import HslCommunication.Core.Transfer.DataFormat;
import HslCommunication.Core.Types.OperateResult;
import HslCommunication.Core.Types.OperateResultExOne;
import HslCommunication.ModBus.ModbusTcpNet;
import com.alibaba.fastjson.JSON;
import com.core.common.DateUtils;
import com.core.common.SpringUtils;
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.BasShuttle;
import com.zy.asrs.entity.BasShuttleOpt;
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.BasShuttleOptService;
import com.zy.asrs.service.BasShuttleService;
import com.zy.asrs.service.LocMastService;
import com.zy.common.model.NavigateNode;
import com.zy.common.utils.CommonUtils;
import com.zy.common.utils.NavigatePositionConvert;
import com.zy.common.utils.NavigateUtils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.ThreadHandler;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.OutputQueue;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.*;
import com.zy.core.model.ShuttleSlave;
import com.zy.core.model.Task;
import com.zy.core.model.command.*;
import com.zy.core.model.protocol.LiftProtocol;
import com.zy.core.model.protocol.ShuttleProtocol;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import com.zy.common.ExecuteSupport;
import com.zy.common.model.NavigateNode;
import com.zy.core.ThreadHandler;
import com.zy.core.enums.ShuttleProtocolStatusType;
import com.zy.core.enums.ShuttleTaskNoType;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.command.ShuttleCommand;
import com.zy.core.model.param.ShuttleMoveLocParam;
import com.zy.core.model.protocol.ShuttleProtocol;
import java.util.List;
/**
 * 四向穿梭车线程
 */
@Data
@Slf4j
public class ShuttleThread implements  Runnable, ThreadHandler {
public interface ShuttleThread extends ThreadHandler {
    private ModbusTcpNet modbusTcpNet;
    private ShuttleSlave slave;
    private ShuttleProtocol shuttleProtocol;
    private RedisUtil redisUtil;
    ShuttleProtocol getStatus(boolean clone);//获取四向穿梭车状态
    public ShuttleThread(ShuttleSlave slave,RedisUtil redisUtil) {
        this.slave = slave;
        this.redisUtil = redisUtil;
    }
    ShuttleProtocol getStatus();//获取四向穿梭车状态-默认clone
    @Override
    public void run() {
        this.connect();
        while (true) {
            try {
                int step = 1;
                Task task = MessageQueue.poll(SlaveType.Shuttle, slave.getId());
                if (task != null) {
                    step = task.getStep();
                }
                switch (step) {
                    // 读数据
                    case 1:
                        readStatus();
                        break;
                    // 写入数据
                    case 2:
                        write((ShuttleCommand) task.getData());
                        break;
                    //下发任务
                    case 3:
                        assignWork((ShuttleAssignCommand) task.getData());
                        break;
                    default:
                        break;
                }
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    CommandResponse movePath(List<NavigateNode> nodes, Integer taskNo);//路径下发
    @Override
    public boolean connect() {
        boolean result = false;
        //-------------------------四向穿梭车连接方法------------------------//
        modbusTcpNet = new ModbusTcpNet(slave.getIp(), slave.getPort(), (byte) 0x01);
        // 当你需要指定格式的数据解析时,就需要设置下面的这个信息
        modbusTcpNet.setDataFormat(DataFormat.ABCD);
        OperateResult connect = modbusTcpNet.ConnectServer();
        if(connect.IsSuccess){
            result = true;
            OutputQueue.CRN.offer(MessageFormat.format( "【{0}】四向穿梭车plc连接成功 ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), slave.getId(), slave.getIp(), slave.getPort()));
            log.info("四向穿梭车plc连接成功 ===>> [id:{}] [ip:{}] [port:{}] ", slave.getId(), slave.getIp(), slave.getPort());
        } else {
            OutputQueue.CRN.offer(MessageFormat.format("【{0}】四向穿梭车plc连接失败!!! ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), slave.getId(), slave.getIp(), slave.getPort()));
            log.error("四向穿梭车plc连接失败!!! ===>> [id:{}] [ip:{}] [port:{}] ", slave.getId(), slave.getIp(), slave.getPort());
        }
        modbusTcpNet.ConnectClose();
        //-------------------------四向穿梭车连接方法------------------------//
        return result;
    }
    CommandResponse move(ShuttleCommand command);//移动
    @Override
    public void close() {
        modbusTcpNet.ConnectClose();
    }
    CommandResponse lift(ShuttleCommand command);//顶升
    private void readStatus() {
        try {
            OperateResultExOne<byte[]> result = modbusTcpNet.Read("200", (short) 17);
            if (result.IsSuccess) {
                if (null == shuttleProtocol) {
                    shuttleProtocol = new ShuttleProtocol();
                    shuttleProtocol.setShuttleNo(slave.getId().shortValue());
                    shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.IDLE);
                }
    CommandResponse charge(ShuttleCommand command);//充电开关
                //----------读取四向穿梭车状态-----------
                //获取数据
                byte[] content = result.Content;
                //小车忙状态位
                shuttleProtocol.setBusyStatus(modbusTcpNet.getByteTransform().TransInt16(content,0));
                //当前二维码
                shuttleProtocol.setCurrentCode(modbusTcpNet.getByteTransform().TransInt16(content,2));
                //电池电量百分比
                shuttleProtocol.setBatteryPower(modbusTcpNet.getByteTransform().TransInt16(content,4));
                //电池温度
                shuttleProtocol.setBatteryTemp(modbusTcpNet.getByteTransform().TransInt16(content,6));
                //错误编号
                shuttleProtocol.setErrorCode(modbusTcpNet.getByteTransform().TransInt16(content,8));
                //Plc输出状态IO
                boolean[] booleans = modbusTcpNet.getByteTransform().TransBool(content, 10, 2);
                shuttleProtocol.setPlcOutputLift(booleans[1]);
                shuttleProtocol.setPlcOutputTransfer(booleans[2]);
                shuttleProtocol.setPlcOutputBrake(booleans[3]);
                shuttleProtocol.setPlcOutputCharge(booleans[4]);
                shuttleProtocol.setPlcOutputStatusIO(modbusTcpNet.getByteTransform().TransInt16(content, 10));
                //错误信息码
                shuttleProtocol.setStatusErrorCode(modbusTcpNet.getByteTransform().TransInt16(content,12));
                //PLC输入状态
                shuttleProtocol.setPlcInputStatus(modbusTcpNet.getByteTransform().TransInt16(content,14));
                //当前或者之前读到的二维码值
                shuttleProtocol.setCurrentOrBeforeCode(modbusTcpNet.getByteTransform().TransInt16(content,16));
                //读到的二维码X方向偏移量
                shuttleProtocol.setCodeOffsetX(modbusTcpNet.getByteTransform().TransInt16(content,18));
                //读到的二维码Y方向偏移量
                shuttleProtocol.setCodeOffsetY(modbusTcpNet.getByteTransform().TransInt16(content,20));
                //当前的电压值
                shuttleProtocol.setCurrentVoltage(modbusTcpNet.getByteTransform().TransInt16(content,22));
                //当前的模拟量值
                shuttleProtocol.setCurrentAnalogValue(modbusTcpNet.getByteTransform().TransInt16(content,24));
                //当前的升降伺服速度
                shuttleProtocol.setCurrentLiftServoSpeed(modbusTcpNet.getByteTransform().TransInt16(content,26));
                //当前的行走伺服速度
                shuttleProtocol.setCurrentMoveServoSpeed(modbusTcpNet.getByteTransform().TransInt16(content,28));
                //当前的升降伺服负载率
                shuttleProtocol.setCurrentLiftServoLoad(modbusTcpNet.getByteTransform().TransInt16(content,30));
                //当前的行走伺服负载率
                shuttleProtocol.setCurrentMoveServoLoad(modbusTcpNet.getByteTransform().TransInt16(content,32));
    CommandResponse reset(ShuttleCommand command);//复位开关
                ///读取四向穿梭车状态-end
    CommandResponse updateLocation(ShuttleCommand command);//更新坐标
                //小车处于忙碌状态,将标记置为true
                if (shuttleProtocol.getBusyStatusType() == ShuttleStatusType.BUSY) {
                    shuttleProtocol.setPakMk(true);
                }
    boolean isIdle();
                if (shuttleProtocol.getErrorCode() != 0 && shuttleProtocol.getProtocolStatusType() == ShuttleProtocolStatusType.WORKING) {
                    //出现错误
                    resetAndTryFix(shuttleProtocol.getTaskNo());
                }
    boolean isIdle(ExecuteSupport support);//是否空闲
                if(shuttleProtocol.getProtocolStatusType() == ShuttleProtocolStatusType.FIXING
                        && shuttleProtocol.getTaskNo() != 0
                        && shuttleProtocol.getBusyStatusType() == ShuttleStatusType.IDLE){
                    //处于故障修复状态
                    //执行下一步指令
                    executeWork(shuttleProtocol.getTaskNo());
                }
    boolean isDeviceIdle();//设备是否空闲
                //四向穿梭车空闲、有任务、标记为true、存在任务指令,需要执行任务的下一条指令
                if (shuttleProtocol.getBusyStatusType() == ShuttleStatusType.IDLE
                        && shuttleProtocol.getTaskNo() != 0
                        && shuttleProtocol.getPakMk()) {
                    //执行下一步指令
                    executeWork(shuttleProtocol.getTaskNo());
                }
    boolean isDeviceIdle(ExecuteSupport support);//设备是否空闲
                //检测是否有提升机锁定标记,有则检测提升机是否到位,是否能走下一步命令
                if (shuttleProtocol.getBusyStatusType() == ShuttleStatusType.IDLE
                        && shuttleProtocol.getTaskNo() != 0) {
                    Object o = redisUtil.get("wrk_no_" + shuttleProtocol.getTaskNo());
                    if (o != null) {
                        ShuttleRedisCommand redisCommand = JSON.parseObject(o.toString(), ShuttleRedisCommand.class);
                        if (redisCommand.getLiftSecurityMk()) {
                            //执行下一步指令
                            executeWork(shuttleProtocol.getTaskNo());
                        }
                    }
                }
    boolean isRequireCharge();//是否满足充电状态
                //将四向穿梭车状态保存至数据库
                BasShuttleService shuttleService = SpringUtils.getBean(BasShuttleService.class);
                BasShuttle basShuttle = shuttleService.selectById(shuttleProtocol.getShuttleNo());
                if (basShuttle == null) {
                    basShuttle = new BasShuttle();
                    //四向穿梭车号
                    basShuttle.setShuttleNo(slave.getId());
                    shuttleService.insert(basShuttle);
                }
                //小车忙状态位
                basShuttle.setBusyStatus(shuttleProtocol.getBusyStatus().intValue());
                //当前二维码
                basShuttle.setCurrentCode(shuttleProtocol.getCurrentCode().intValue());
                //电池电量百分比
                basShuttle.setBatteryPower(shuttleProtocol.getBatteryPower().intValue());
                //电池温度
                basShuttle.setBatteryTemp(shuttleProtocol.getBatteryTemp().intValue());
                //错误编号
                basShuttle.setErrorCode(shuttleProtocol.getErrorCode().intValue());
                //Plc输出状态IO
                basShuttle.setPlcOutputStatusIo(shuttleProtocol.getPlcOutputStatusIO().intValue());
                //错误信息码
                basShuttle.setStatusErrorCode(shuttleProtocol.getStatusErrorCode().intValue());
                //PLC输入状态
                basShuttle.setPlcInputStatus(shuttleProtocol.getPlcInputStatus().intValue());
                //当前或者之前读到的二维码值
                basShuttle.setCurrentOrBeforeCode(shuttleProtocol.getCurrentOrBeforeCode().intValue());
                //读到的二维码X方向偏移量
                basShuttle.setCodeOffsetX(shuttleProtocol.getCodeOffsetX().intValue());
                //读到的二维码Y方向偏移量
                basShuttle.setCodeOffsetY(shuttleProtocol.getCodeOffsetY().intValue());
                //当前的电压值
                basShuttle.setCurrentVoltage(shuttleProtocol.getCurrentVoltage().intValue());
                //当前的模拟量值
                basShuttle.setCurrentAnalogValue(shuttleProtocol.getCurrentAnalogValue().intValue());
                //当前的升降伺服速度
                basShuttle.setCurrentLiftServoSpeed(shuttleProtocol.getCurrentLiftServoSpeed().intValue());
                //当前的行走伺服速度
                basShuttle.setCurrentMoveServoSpeed(shuttleProtocol.getCurrentMoveServoSpeed().intValue());
                //当前的升降伺服负载率
                basShuttle.setCurrentLiftServoLoad(shuttleProtocol.getCurrentLiftServoLoad().intValue());
                //当前的行走伺服负载率
                basShuttle.setCurrentMoveServoLoad(shuttleProtocol.getCurrentMoveServoLoad().intValue());
                //当前小车状态(内部自我维护)
                basShuttle.setShuttleStatus(shuttleProtocol.getProtocolStatus());
                //任务号
                basShuttle.setWrkNo(shuttleProtocol.getTaskNo().intValue());
                //修改时间
                basShuttle.setUpdateTime(new Date());
                //作业标记
                basShuttle.setPakMk(shuttleProtocol.getPakMk());
                if (shuttleService.updateById(basShuttle)) {
                    OutputQueue.SHUTTLE.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功",DateUtils.convert(new Date()), slave.getId()));
//                    log.info(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功",DateUtils.convert(new Date()), slave.getId()));
                }
    boolean isCharging();//是否充电中
            }else {
                OutputQueue.SHUTTLE.offer(MessageFormat.format("【{0}】{1}四向穿梭车plc状态信息失败", DateUtils.convert(new Date()), slave.getId()));
                throw new CoolException(MessageFormat.format( "四向穿梭车plc状态信息失败 ===>> [id:{0}] [ip:{1}] [port:{2}]", slave.getId(), slave.getIp(), slave.getPort()));
            }
        } catch (Exception e) {
            e.printStackTrace();
            OutputQueue.SHUTTLE.offer(MessageFormat.format("【{0}】四向穿梭车plc状态信息失败 ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), slave.getId(), slave.getIp(), slave.getPort()));
            initShuttle();
        }
    }
    boolean isChargingCompleted();//是否充电完成
    private boolean write(ShuttleCommand command){
        if (null == command) {
            News.error("四向穿梭车写入命令为空");
            return false;
        }
    boolean isFault();//是否故障
        BasShuttleService shuttleService = SpringUtils.getBean(BasShuttleService.class);
        if (shuttleService == null) {
            News.error("系统错误");
            return false;
        }
    List<NavigateNode> getMoveAdvancePath();//获取穿梭车任务路径
        BasShuttle basShuttle = shuttleService.selectById(slave.getId().shortValue());
        if (basShuttle == null) {
            News.error("四向穿梭车不存在");
            return false;
        }
    int generateDeviceTaskNo(int taskNo, ShuttleTaskNoType type);//生成硬件设备工作号
        command.setShuttleNo(slave.getId().shortValue());
        // 开始任务
        short[] array = new short[17];
        //控制指令字
        array[0] = command.getCommandWord();
        if (command.getStartCodeNum() != null) {
            //启始二维编号
            array[1] = command.getStartCodeNum();
        }
    boolean setProtocolStatus(ShuttleProtocolStatusType status);//设置工作状态
        if (command.getMiddleCodeNum() != null) {
            //中间二维编号
            array[2] = command.getMiddleCodeNum();
        }
    boolean setTaskNo(Integer taskNo);//设置工作号
        if (command.getDistCodeNum() != null) {
            //目标二维编号
            array[3] = command.getDistCodeNum();
        }
    boolean setSyncTaskNo(Integer taskNo);//设置工作号
        if (command.getStartToDistDistance() != null) {
            //起点到目标点的距离长度,先将int转为byte数组,再将byte数组转成两个short,分别占用4和5空间
            short[] startToDistDistances = CommonUtils.intToShorts(command.getStartToDistDistance());
            array[4] = startToDistDistances[0];
            array[5] = startToDistDistances[1];
        }
    boolean setPakMk(boolean pakMk);//设置标记
        if (command.getMiddleToDistDistance() != null) {
            //中间点到目标点的距离长度,先将int转为byte数组,再将byte数组转成两个short,分别占用4和5空间
            short[] middleToDistDistances = CommonUtils.intToShorts(command.getMiddleToDistDistance());
            array[6] = middleToDistDistances[0];
            array[7] = middleToDistDistances[1];
        }
    boolean enableMoveLoc(ShuttleMoveLocParam param, boolean enable);
        array[8] = basShuttle.getRunSpeed().shortValue();//四向穿梭车运行速度,从系统数据库读出
        if (command.getRunDirection() != null) {
            //小车运行方向
            array[8] = command.getRunDirection();
        }
    boolean requestWaiting();
        if (command.getPalletLift() != null) {
            //托盘顶升
            array[9] = command.getPalletLift();
        }
    boolean enableDemo(boolean enable);//演示模式
        if (command.getForceMoveDistance() != null) {
            //小车强制移动距离,先将int转为byte数组,再将byte数组转成两个short,分别占用4和5空间
            short[] forceMoveDistances = CommonUtils.intToShorts(command.getForceMoveDistance());
            array[10] = forceMoveDistances[0];
            array[11] = forceMoveDistances[1];
        }
    boolean offerSystemMsg(String format, Object... arguments);
        if (command.getChargeSwitch() != null) {
            //充电开关
            array[12] = command.getChargeSwitch();
        }
    //***************获取命令*****************
        if (command.getIOControl() != null) {
            //小车IO控制
            array[13] = command.getIOControl();
        }
    ShuttleCommand getMoveCommand(Integer taskNo, String startCodeNum, String distCodeNum, Integer allDistance, Integer runDirection, Integer runSpeed, List<NavigateNode> nodes);//获取移动命令
        if (command.getRunSpeed() != null) {
            //小车运行速度
            array[14] = command.getRunSpeed();
        }
    ShuttleCommand getLiftCommand(Integer taskNo, Boolean lift);//顶升命令 true=>顶升 false=>下降
        if (command.getRadarTmp() != null) {
            //小车雷达备用
            array[15] = command.getRadarTmp();
        }
    ShuttleCommand getChargeCommand(Integer taskNo, Boolean charge);//充电开关命令 true=>开 false=>关
        //指令结束位
        array[16] = command.getCommandEnd();
        OperateResult result = modbusTcpNet.Write("0", array);;
        if (result != null && result.IsSuccess) {
            News.info("四向穿梭车命令下发[id:{}] >>>>> {}", slave.getId(), JSON.toJSON(command));
            OutputQueue.SHUTTLE.offer(MessageFormat.format("【{0}】[id:{1}] >>>>> 命令下发: {2}", DateUtils.convert(new Date()), slave.getId(), JSON.toJSON(command)));
            return true;
        } else {
            OutputQueue.SHUTTLE.offer(MessageFormat.format("【{0}】写入四向穿梭车plc数据失败 ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), slave.getId(), slave.getIp(), slave.getPort()));
            News.error("写入四向穿梭车plc数据失败 ===>> [id:{}] [ip:{}] [port:{}]", slave.getId(), slave.getIp(), slave.getPort());
            return false;
        }
    }
    /**
     * 初始化四向穿梭车
     */
    private void initShuttle() {
        if (null == shuttleProtocol) {
            shuttleProtocol = new ShuttleProtocol();
        }
    }
    //分配任务
    private void assignWork(ShuttleAssignCommand assignCommand) {
        if (!assignCommand.getAuto()) {
            List<ShuttleCommand> commands = new ArrayList<>();
            ShuttleCommand command = new ShuttleCommand();
            LocMastService locMastService = SpringUtils.getBean(LocMastService.class);
            switch (assignCommand.getTaskMode()) {
                case 1://入库
                case 2://出库
                    //小车移动到提升机口,计算路径
                    //计算小车起点到中点所需命令
                    LocMast currentLocMast = locMastService.queryByQrCode(shuttleProtocol.getCurrentCode().toString());
                    List<NavigateNode> firstMastResult = NavigateUtils.calc(currentLocMast.getLocNo(), assignCommand.getSourceLocNo(), ShuttleTaskModeType.PAK_IN.id);
                    if (firstMastResult != null) {
                        //获取分段路径
                        ArrayList<ArrayList<NavigateNode>> data = NavigateUtils.getSectionPath(firstMastResult);
                        //将每一段路径分成command指令
                        for (ArrayList<NavigateNode> nodes : data) {
                            //开始路径
                            NavigateNode startPath = nodes.get(0);
                            //目标路径
                            NavigateNode endPath = nodes.get(nodes.size() - 1);
                            Integer allDistance = NavigateUtils.getCurrentPathAllDistance(nodes);//计算当前路径行走总距离
                            ShuttleCommand command1 = new ShuttleCommand();
                            command1.setCommandWord((short) 1);
                            command1.setStartCodeNum(NavigatePositionConvert.xyToPosition(startPath.getX(), startPath.getY(), 1));
                            command1.setMiddleCodeNum((short) 1);
                            command1.setDistCodeNum(NavigatePositionConvert.xyToPosition(endPath.getX(), endPath.getY(), 1));
                            command1.setStartToDistDistance(allDistance);
                            command1.setRunDirection(ShuttleRunDirection.get(startPath.getDirection()).id);
                            command1.setForceMoveDistance(0);
                            command1.setIOControl((short) 0);
                            command1.setRunSpeed((short) 1000);
                            command1.setCommandEnd((short) 1);
                            commands.add(command1);
                        }
                        //托盘顶升
                        ShuttleCommand command2 = new ShuttleCommand();
                        command2.setCommandWord((short) 2);
                        command2.setStartCodeNum((short) 0);
                        command2.setMiddleCodeNum((short) 0);
                        command2.setDistCodeNum((short) 0);
                        command2.setStartToDistDistance(0);
                        command2.setMiddleToDistDistance(0);
                        command2.setRunDirection((short) 0);
                        command2.setForceMoveDistance(0);
                        command2.setPalletLift((short) 1);
                        command2.setRunSpeed((short) 0);
                        command2.setCommandEnd((short) 1);
                        commands.add(command2);
                    }else {
                        //没有计算到路径,可能存在小车位置就是起点位置
                        if (currentLocMast.getLocNo().equals(assignCommand.getSourceLocNo())) {
                            //小车位置就是起点位置,无需移动,直接顶升
                            //托盘顶升
                            ShuttleCommand command2 = new ShuttleCommand();
                            command2.setCommandWord((short) 2);
                            command2.setStartCodeNum((short) 0);
                            command2.setMiddleCodeNum((short) 0);
                            command2.setDistCodeNum((short) 0);
                            command2.setStartToDistDistance(0);
                            command2.setMiddleToDistDistance(0);
                            command2.setRunDirection((short) 0);
                            command2.setForceMoveDistance(0);
                            command2.setPalletLift((short) 1);
                            command2.setRunSpeed((short) 0);
                            command2.setCommandEnd((short) 1);
                            commands.add(command2);
                        }
                    }
                    //计算中点到终点路径
                    List<NavigateNode> secMastResult = NavigateUtils.calc(assignCommand.getSourceLocNo(), assignCommand.getLocNo(), ShuttleTaskModeType.PAK_IN.id);
                    if (secMastResult != null) {
                        //获取分段路径
                        ArrayList<ArrayList<NavigateNode>> data = NavigateUtils.getSectionPath(secMastResult);
                        //将每一段路径分成command指令
                        for (ArrayList<NavigateNode> nodes : data) {
                            //开始路径
                            NavigateNode startPath = nodes.get(0);
                            //目标路径
                            NavigateNode endPath = nodes.get(nodes.size() - 1);
                            Integer allDistance = NavigateUtils.getCurrentPathAllDistance(nodes);//计算当前路径行走总距离
                            ShuttleCommand command1 = new ShuttleCommand();
                            command1.setCommandWord((short) 1);
                            command1.setStartCodeNum(NavigatePositionConvert.xyToPosition(startPath.getX(), startPath.getY(), 1));
                            command1.setMiddleCodeNum((short) 1);
                            command1.setDistCodeNum(NavigatePositionConvert.xyToPosition(endPath.getX(), endPath.getY(), 1));
                            command1.setStartToDistDistance(allDistance);
                            command1.setRunDirection(ShuttleRunDirection.get(startPath.getDirection()).id);
                            command1.setForceMoveDistance(0);
                            command1.setIOControl((short) 0);
                            command1.setRunSpeed((short) 1000);
                            command1.setCommandEnd((short) 1);
                            commands.add(command1);
                        }
                        //托盘下降
                        ShuttleCommand command2 = new ShuttleCommand();
                        command2.setCommandWord((short) 2);
                        command2.setStartCodeNum((short) 0);
                        command2.setMiddleCodeNum((short) 0);
                        command2.setDistCodeNum((short) 0);
                        command2.setStartToDistDistance(0);
                        command2.setMiddleToDistDistance(0);
                        command2.setRunDirection((short) 0);
                        command2.setForceMoveDistance(0);
                        command2.setPalletLift((short) 2);
                        command2.setRunSpeed((short) 0);
                        command2.setCommandEnd((short) 1);
                        commands.add(command2);
                    }
                    break;
                case 3://托盘顶升
                case 4://托盘下降
                    command.setCommandWord((short) 2);
                    command.setStartCodeNum((short) 0);
                    command.setMiddleCodeNum((short) 0);
                    command.setDistCodeNum((short) 0);
                    command.setStartToDistDistance(0);
                    command.setMiddleToDistDistance(0);
                    command.setRunDirection((short) 0);
                    command.setForceMoveDistance(0);
                    command.setPalletLift(assignCommand.getTaskMode() == 3 ? (short)1 : (short)2);
                    command.setRunSpeed((short) 0);
                    command.setCommandEnd((short) 1);
                    commands.add(command);
                    break;
                case 5://强制左移
                case 6://强制右移
                case 7://强制上移
                case 8://强制下移
                    command.setCommandWord((short) 3);
                    command.setStartCodeNum((short) 0);
                    command.setMiddleCodeNum((short) 0);
                    command.setDistCodeNum((short) 0);
                    command.setStartToDistDistance(600);
                    command.setRunDirection((short) (assignCommand.getTaskMode() - 4));
                    command.setForceMoveDistance(600);
                    command.setIOControl((short) 0);
                    command.setCommandEnd((short) 1);
                    command.setRunSpeed((short) 1000);
                    commands.add(command);
                    break;
                case 9://状态复位
                    command.setCommandWord((short) 6);
                    command.setStartCodeNum((short) 0);
                    command.setMiddleCodeNum((short) 0);
                    command.setDistCodeNum((short) 0);
                    command.setStartToDistDistance(0);
                    command.setMiddleToDistDistance(0);
                    command.setRunDirection((short) 0);
                    command.setPalletLift((short) 0);
                    command.setPalletLift((short) 0);
                    command.setForceMoveDistance(0);
                    command.setChargeSwitch((short) 0);
                    command.setIOControl((short) 0);
                    command.setRunSpeed((short) 0);
                    command.setCommandEnd((short) 1);
                    commands.add(command);
                    break;
                case 10://向正方向(左)寻库位
                    command.setCommandWord((short) 4);
                    command.setRunDirection((short) 1);
                    command.setStartToDistDistance(3000);
                    command.setRunSpeed((short) 1000);
                    command.setCommandEnd((short) 1);
                    commands.add(command);
                    break;
                case 11://向负方向(右)寻库位
                    command.setCommandWord((short) 4);
                    command.setRunDirection((short) 2);
                    command.setStartToDistDistance(3000);
                    command.setRunSpeed((short) 1000);
                    command.setCommandEnd((short) 1);
                    commands.add(command);
                    break;
                case 12://向正方向(前)寻库位
                    command.setCommandWord((short) 4);
                    command.setRunDirection((short) 4);
                    command.setStartToDistDistance(3000);
                    command.setRunSpeed((short) 1000);
                    command.setCommandEnd((short) 1);
                    commands.add(command);
                    break;
                case 13://向负方向(后)寻库位
                    command.setCommandWord((short) 4);
                    command.setRunDirection((short) 3);
                    command.setStartToDistDistance(3000);
                    command.setRunSpeed((short) 1000);
                    command.setCommandEnd((short) 1);
                    commands.add(command);
                    break;
                case 14://移动到目标库位
                    LocMast locMast = locMastService.queryByQrCode(shuttleProtocol.getCurrentCode().toString());
                    List<NavigateNode> result = NavigateUtils.calc(locMast.getLocNo(), assignCommand.getLocNo(), ShuttleTaskModeType.PAK_IN.id);
                    if (result != null) {
                        //获取分段路径
                        ArrayList<ArrayList<NavigateNode>> data = NavigateUtils.getSectionPath(result);
                        //将每一段路径分成command指令
                        for (ArrayList<NavigateNode> nodes : data) {
                            //开始路径
                            NavigateNode startPath = nodes.get(0);
                            //目标路径
                            NavigateNode endPath = nodes.get(nodes.size() - 1);
                            Integer allDistance = NavigateUtils.getCurrentPathAllDistance(nodes);//计算当前路径行走总距离
                            LocMast distLocMast = locMastService.queryByLoc(assignCommand.getLocNo());
                            String qrCodeValue = distLocMast.getQrCodeValue();
                            command.setCommandWord((short) 1);
                            command.setStartCodeNum(shuttleProtocol.getCurrentCode());
                            command.setMiddleCodeNum((short) 1);
                            command.setDistCodeNum((short) Integer.parseInt(qrCodeValue));
                            command.setStartToDistDistance(allDistance);
                            command.setRunSpeed((short) 1000);
                            command.setRunDirection(ShuttleRunDirection.get(startPath.getDirection()).id);
                            command.setForceMoveDistance(0);
                            command.setIOControl((short) 0);
                            command.setCommandEnd((short) 1);
                            commands.add(command);
                        }
                    }
                    break;
                case 15://充电开关
                    command.setCommandWord((short) 8);
                    command.setIOControl((short) 4);
                    command.setCommandEnd((short) 1);
                    commands.add(command);
                    break;
                default:
            }
            assignCommand.setCommands(commands);
        }
        ShuttleRedisCommand redisCommand = new ShuttleRedisCommand();
        redisCommand.setShuttleNo(assignCommand.getShuttleNo());//四向穿梭车号
        redisCommand.setWrkNo(assignCommand.getTaskNo());//工作号
        redisCommand.setCommandStep(0);//命令执行步序
        redisCommand.setAssignCommand(assignCommand);//命令
        redisCommand.setErrorCommands(new ArrayList<ShuttleCommand>());//发生错误时尝试执行的指令,优先级最高
        shuttleProtocol.setTaskNo(assignCommand.getTaskNo());
        shuttleProtocol.setAssignCommand(assignCommand);
        shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.WORKING);
        //任务数据保存到redis
        redisUtil.set("wrk_no_" + assignCommand.getTaskNo(), JSON.toJSONString(redisCommand));
        //执行下发任务
        executeWork(assignCommand.getTaskNo());
    }
    //执行下发的指令
    private boolean executeWork(Short wrkNo) {
        //读取redis数据
        if (wrkNo == null) {
            return false;
        }
        Object o = redisUtil.get("wrk_no_" + wrkNo);
        if (o == null) {
            return false;
        }
        ShuttleRedisCommand redisCommand = JSON.parseObject(o.toString(), ShuttleRedisCommand.class);
        if (!checkLiftStation(wrkNo)) {//检测是否有提升机站点,有则调度提升机
            return false;
        }
        //将标记置为false(防止重发)
        shuttleProtocol.setPakMk(false);
        List<ShuttleCommand> errorCommands = redisCommand.getErrorCommands();
        if (errorCommands.size() > 0) {
            //优先执行该指令
            ShuttleCommand errorCommand = errorCommands.get(0);//取出指令
            if(errorCommand.getCommandWord() == 1){//正常行走命令,需要先执行完找库位命令后,再执行
                LocMastService locMastService = SpringUtils.getBean(LocMastService.class);
                LocMast locMast = locMastService.queryByQrCode(shuttleProtocol.getCurrentCode().toString());
                LocMast distLocMast = locMastService.queryByQrCode(errorCommand.getStartCodeNum().toString());
                if (shuttleProtocol.getCurrentCode().equals(errorCommand.getStartCodeNum())) {
                    //起点和终点属于同一库位,无需再执行移动操作
                    errorCommands.remove(0);//移除该命令
                    redisCommand.setErrorCommands(new ArrayList<ShuttleCommand>());
                    shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.WORKING);
                    //当前步序
                    int commandStep = redisCommand.getCommandStep();
                    //步序回退
                    commandStep--;
                    redisCommand.setCommandStep(commandStep);
                    //任务数据保存到redis
                    redisUtil.set("wrk_no_" + wrkNo, JSON.toJSONString(redisCommand));
                    shuttleProtocol.setPakMk(true);
                    return true;
                }else {
                    List<NavigateNode> result = NavigateUtils.calc(locMast.getLocNo(), distLocMast.getLocNo(), ShuttleTaskModeType.PAK_IN.id);
                    if (result != null) {
                        //获取分段路径
                        ArrayList<ArrayList<NavigateNode>> data = NavigateUtils.getSectionPath(result);
                        //将每一段路径分成command指令
                        for (ArrayList<NavigateNode> nodes : data) {
                            //开始路径
                            NavigateNode startPath = nodes.get(0);
                            //目标路径
                            NavigateNode endPath = nodes.get(nodes.size() - 1);
                            Integer allDistance = NavigateUtils.getCurrentPathAllDistance(nodes);//计算当前路径行走总距离
                            String qrCodeValue = distLocMast.getQrCodeValue();
                            errorCommand.setCommandWord((short) 1);
                            errorCommand.setStartCodeNum(shuttleProtocol.getCurrentCode());
                            errorCommand.setMiddleCodeNum((short) 1);
                            errorCommand.setDistCodeNum((short) Integer.parseInt(qrCodeValue));
                            errorCommand.setStartToDistDistance(allDistance);
                            errorCommand.setRunSpeed((short) 1000);
                            errorCommand.setRunDirection(ShuttleRunDirection.get(startPath.getDirection()).id);
                            errorCommand.setForceMoveDistance(0);
                            errorCommand.setIOControl((short) 0);
                            errorCommand.setCommandEnd((short) 1);
                            break;
                        }
                    }
                }
                shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.WORKING);
                //当前步序
                int commandStep = redisCommand.getCommandStep();
                //步序回退
                commandStep--;
                redisCommand.setCommandStep(commandStep);
            }
            if (!write(errorCommand)) {
                News.error("四向穿梭车命令下发失败,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(errorCommand));
                return false;
            } else {
                News.info("四向穿梭车命令下发成功,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(errorCommand));
                errorCommands.remove(0);
                redisCommand.setErrorCommands(errorCommands);
                //任务数据保存到redis
                redisUtil.set("wrk_no_" + wrkNo, JSON.toJSONString(redisCommand));
                return true;
            }
        }
        List<ShuttleCommand> commands = redisCommand.getAssignCommand().getCommands();
        //当前步序
        int commandStep = redisCommand.getCommandStep();
        //path路径数目
        int size = commands.size();
        ShuttleAssignCommand assignCommand = redisCommand.getAssignCommand();
        //取出命令
        ShuttleCommand command = commands.get(commandStep);
        if (assignCommand.getTaskMode() == ShuttleTaskModeType.PAK_IN.id.shortValue()
            || assignCommand.getTaskMode() == ShuttleTaskModeType.PAK_OUT.id.shortValue()
        ) {
            //小车失去坐标,禁止下发命令
            if (shuttleProtocol.getCurrentCode() == 0) {
                return false;
            }
        }
        //下发命令
        if (!write(command)) {
            News.error("四向穿梭车命令下发失败,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(command));
            return false;
        } else {
            News.info("四向穿梭车命令下发成功,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(command));
            //判断数据是否执行完成
            if (commandStep < size - 1) {
                //更新redis数据
                //步序增加
                commandStep++;
                redisCommand.setCommandStep(commandStep);
                //任务数据保存到redis
                redisUtil.set("wrk_no_" + redisCommand.getWrkNo(), JSON.toJSONString(redisCommand));
            }else {
                //已执行完成
                //保存数据到数据库做流水
                BasShuttleOptService shuttleOptService = SpringUtils.getBean(BasShuttleOptService.class);
                if (shuttleOptService != null) {
                    BasShuttleOpt opt = new BasShuttleOpt(
                            assignCommand.getTaskNo().intValue(),
                            assignCommand.getShuttleNo().intValue(),
                            new Date(),
                            ShuttleTaskModeType.get(assignCommand.getTaskMode()).desc,
                            assignCommand.getSourceLocNo(),
                            assignCommand.getLocNo(),
                            null,
                            null,
                            null,
                            JSON.toJSONString(assignCommand)
                    );
                    shuttleOptService.insert(opt);
                }
                if (redisCommand.getLiftSecurityMk()) {
                    //曾锁定过提升机,需要进行解锁
                    LiftThread liftThread = (LiftThread) SlaveConnection.get(SlaveType.Lift, 1);
                    LiftProtocol liftProtocol = liftThread.getLiftProtocol();
                    if (liftProtocol != null) {
                        liftProtocol.setSecurityMk(false);
                    }
                }
                //删除redis
                redisUtil.del("wrk_no_" + redisCommand.getWrkNo());
                if (!assignCommand.getAuto()) {
                    //手动模式不抛出等待状态,直接复位
                    //设置四向穿梭车为空闲状态
                    shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.IDLE);
                    //任务号清零
                    shuttleProtocol.setTaskNo((short) 0);
                    //标记复位
                    shuttleProtocol.setPakMk(true);
                    News.info("四向穿梭车手动任务执行完成,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(command));
                }else {
                    if (!assignCommand.getCharge()) {
                        //对主线程抛出等待确认状态waiting
                        shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.WAITING);
                    }else {
                        shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.CHARGING_WAITING);
                    }
                    News.info("四向穿梭车任务执行完成等待确认中,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(command));
                }
            }
        }
        return true;
    }
    /**
     * 检测是否有提升机站点,有则调度提升机
     */
    private boolean checkLiftStation(Short wrkNo) {
        //读取redis数据
        if (wrkNo == null) {
            return false;
        }
        Object o = redisUtil.get("wrk_no_" + wrkNo);
        if (o == null) {
            return false;
        }
        ShuttleRedisCommand redisCommand = JSON.parseObject(o.toString(), ShuttleRedisCommand.class);
        //当前步序
        int commandStep = redisCommand.getCommandStep();
        //检测是否存在提升机口的指令
        List<ShuttleCommand> commands = redisCommand.getAssignCommand().getCommands();
        BasDevpService basDevpService = SpringUtils.getBean(BasDevpService.class);
        ArrayList<Short> qrCodeValues = new ArrayList<>();
        for (BasDevp basDevp : basDevpService.selectList(null)) {
            //将所有提升机口二维码存入list
            qrCodeValues.add(Short.parseShort(basDevp.getQrCodeValue()));
        }
        //遍历所有指令,判断是否有到提升机口的指令,并获取到达该提升机口所需步序
        int step = 0;
        ShuttleCommand command = null;
        for (int i = 1; i < commands.size(); i++) {
            command = commands.get(i);
            if (qrCodeValues.contains(command.getDistCodeNum())) {
                //存在
                step = i + 1;
                break;
            }
        }
        if (step == 0) {
            //无需后续检测,直接放行
            return true;
        }
        //判断下一步是否为提升机口
        if (commandStep + 1 != step) {
            //下一步不是提升机口,跳过后续流程
            return true;
        }
        //拿到提升机线程
        LiftThread liftThread = (LiftThread) SlaveConnection.get(SlaveType.Lift, 1);
        if (liftThread == null) {
            return false;
        }
        LiftProtocol liftProtocol = liftThread.getLiftProtocol();
        if (liftProtocol == null) {
            return false;
        }
        //获取四向穿梭车当前楼层
        String shuttleLocNo = shuttleProtocol.getCurrentLocNo();//二维码对应库位号
        Integer shuttleLocNoLev = Integer.parseInt(shuttleLocNo.substring(shuttleLocNo.length() - 2, shuttleLocNo.length()));//库位号对应层高
        //程序走到这,表示提升机可能一直就在当前层,可能经过了移动到达了该层
        if (liftProtocol.getProtocolStatusType() == LiftProtocolStatusType.WAITING) {
            //提升机等待确认
            //设置提升机为空闲状态
            liftProtocol.setProtocolStatus(LiftProtocolStatusType.IDLE);
            //任务号清零
            liftProtocol.setTaskNo((short) 0);
            //标记复位
            liftProtocol.setPakMk(true);
            //任务指令清零
            liftProtocol.setAssignCommand(null);
            //提升机解锁
            liftProtocol.setLiftLock(false);
        }
        //判断提升机是否在目标楼层
        if (liftProtocol.getLev().intValue() == shuttleLocNoLev) {
            //同一层,直接放行
            return true;
        }
        //提升机和穿梭车处于不同楼层,需要进行调度
        if (!liftProtocol.isIdle()) {
            //提升机不是空闲
            return false;
        }
        //给提升机分配任务
        liftProtocol.setLiftLock(true);//锁定提升机
        liftProtocol.setTaskNo(shuttleProtocol.getTaskNo());//设置任务号
        liftProtocol.setShuttleNo(shuttleProtocol.getShuttleNo());//设置四向穿梭车号
        liftProtocol.setProtocolStatus(LiftProtocolStatusType.WORKING);//设置提升机状态为工作中
        liftProtocol.setSecurityMk(true);//标记置为true,防止其他任务占用当前提升机
        redisCommand.setLiftSecurityMk(true);//标记置为true,防止其他任务占用当前提升机
        //任务数据保存到redis
        redisUtil.set("wrk_no_" + wrkNo, JSON.toJSONString(redisCommand));
        //命令list
        ArrayList<LiftCommand> liftCommands = new ArrayList<>();
        LiftCommand liftCommand = new LiftCommand();
        liftCommand.setLiftNo(liftProtocol.getLiftNo());//提升机号
        liftCommand.setTaskNo(liftProtocol.getTaskNo());//任务号
        liftCommand.setRun((short) 1);//升降
        liftCommand.setDistPosition(shuttleLocNoLev.shortValue());//目标楼层(穿梭车所在楼层)
        liftCommand.setLiftLock(true);//锁定提升机
        liftCommands.add(liftCommand);//将命令添加进list
        LiftAssignCommand liftAssignCommand = new LiftAssignCommand();
        liftAssignCommand.setCommands(liftCommands);
        liftAssignCommand.setLiftNo(liftProtocol.getLiftNo());
        liftAssignCommand.setTaskNo(liftProtocol.getTaskNo());
        //下发任务
        MessageQueue.offer(SlaveType.Lift, liftProtocol.getLiftNo().intValue(), new Task(3, liftAssignCommand));
        return false;
    }
    /**
     * 复位并尝试修复错误
     */
    private boolean resetAndTryFix(Short wrkNo) {
        //读取redis数据
        if (wrkNo == null) {
            return false;
        }
        Object o = redisUtil.get("wrk_no_" + wrkNo);
        if (o == null) {
            return false;
        }
        ShuttleRedisCommand redisCommand = JSON.parseObject(o.toString(), ShuttleRedisCommand.class);
        List<ShuttleCommand> commands = redisCommand.getAssignCommand().getCommands();
        //当前步序
        int commandStep = redisCommand.getCommandStep();
        //path路径数目
        int size = commands.size();
        ArrayList<ShuttleCommand> list = new ArrayList<>();
        //取出命令
        ShuttleCommand command = commands.get(commandStep - 1);
        //复位命令
        ShuttleCommand resetCommand = new ShuttleCommand();
        resetCommand.setCommandWord((short) 6);
        resetCommand.setStartCodeNum((short) 0);
        resetCommand.setMiddleCodeNum((short) 0);
        resetCommand.setDistCodeNum((short) 0);
        resetCommand.setStartToDistDistance(0);
        resetCommand.setMiddleToDistDistance(0);
        resetCommand.setRunDirection((short) 0);
        resetCommand.setPalletLift((short) 0);
        resetCommand.setPalletLift((short) 0);
        resetCommand.setForceMoveDistance(0);
        resetCommand.setChargeSwitch((short) 0);
        resetCommand.setIOControl((short) 0);
        resetCommand.setRunSpeed((short) 0);
        resetCommand.setCommandEnd((short) 1);
        list.add(resetCommand);
        //车辆空闲,等待写入找库位命令
        //找库位命令
        ShuttleCommand searchCommand = new ShuttleCommand();
        searchCommand.setCommandWord((short) 4);
        short direction = 1;
        switch (command.getRunDirection()) {
            case 1:
                direction = 2;
                break;
            case 2:
                direction = 1;
                break;
            case 3:
                direction = 4;
                break;
            case 4:
                direction = 3;
                break;
            default:
                direction = 1;
        }
        searchCommand.setRunDirection(direction);//运行方向
        searchCommand.setStartToDistDistance(1200);
        searchCommand.setRunSpeed((short) 1000);
        searchCommand.setCommandEnd((short) 1);
        list.add(searchCommand);
        //移动车辆,需要在执行完寻找定位点后再执行
        ShuttleCommand moveCommand = new ShuttleCommand();
        moveCommand.setCommandWord((short) 1);
        moveCommand.setStartCodeNum(command.getStartCodeNum());//存入目标库位号
        list.add(moveCommand);
        redisCommand.setErrorCommands(list);
        //任务数据保存到redis
        redisUtil.set("wrk_no_" + wrkNo, JSON.toJSONString(redisCommand));
        shuttleProtocol.setProtocolStatus(ShuttleProtocolStatusType.FIXING);
        return true;
    }
    /******************************************************************************************/
    /**************************************** 测试专用 *****************************************/
    /*****************************************************************************************/
    public static void main(String[] args) throws InterruptedException {
        ShuttleSlave slave = new ShuttleSlave();
        slave.setId(1);
        slave.setIp("192.168.4.24");
        slave.setPort(502);
//        ShuttleThread thread = new ShuttleThread(slave);
//        thread.connect();
//        thread.readStatus();
//
//        ShuttleCommand command = new ShuttleCommand();
//        command.setCommandWord((short) 0);
//        command.setStartCodeNum((short) 12323);
//        command.setMiddleCodeNum((short) 22323);
//        command.setDistCodeNum((short) 29999);
//        command.setStartToDistDistance(109999);
//        command.setMiddleToDistDistance(5000);
//        command.setRunDirection((short) 1);
//        command.setPalletLift((short) 2);
//        command.setForceMoveDistance(3000);
//        command.setChargeSwitch((short) 2);
//        command.setIOControl((short) 0);
//        command.setRunSpeed((short) 0);
//        command.setRadarTmp((short) 0);
//        command.setCommandEnd((short) 1);
//        thread.write(command);
    ShuttleCommand getUpdateLocationCommand(Integer taskNo, String locNo);//获取更新坐标命令
    }
}