#
whycq
2024-08-12 2583078fdf32b87da154b1594f3e616a77824175
#
9个文件已修改
19个文件已添加
1595 ■■■■■ 已修改文件
app/src/main/java/com/example/agvcontroller/MainActivity.java 72 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/StartActivity.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AGV_01_DOWN.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AGV_02_DOWN.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AGV_03_DOWN.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AGV_80_DOWN.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AGV_A1_DOWN.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AGV_F0_DOWN.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AgvPackage.java 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/AgvProtocol.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/CRCUtils.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/Cools.java 367 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/DirectionType.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/EncryType.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/ICodedStatus.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/ICommandBody.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/IMessageBody.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/PacBody.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/PacErrorType.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/PacHeader.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/PackagePart.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/ProtocolEncoder.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/ProtocolPojoType.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/ProtocolType.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/ProtocolUtils.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/SystemProperties.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/Utils.java 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/protocol/ValidUtil.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/agvcontroller/MainActivity.java
@@ -8,6 +8,8 @@
import android.os.Bundle;
import android.os.Vibrator;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
@@ -32,54 +34,58 @@
    private Button vibrateButton;
    private Socket socket;
    SocketManager socketManager;
    String clientId;
    NettyServerHandler nettyServerHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 作为客户端连接
        //SocketManager.getInstance().setHostAndPort("192.168.4.188", 802);
        //SocketManager.getInstance().connect();
        //SocketManager.getInstance().setPort(8080);
        //SocketManager.getInstance().startServer();
        // 设置端口号并启动服务端
        //SocketManager.getInstance().setPort(8080);
        //socketManager = new SocketManager();
        //socketManager.startServer(8080);
        // 启动服务端
        //new Thread(() -> {
        //    SocketManager.getInstance().startServer();
        //}).start();
        vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        vibrateButton = findViewById(R.id.btn_stop);
        Intent intent = getIntent();
        String clientId = intent.getStringExtra("message");
        //String clientId = intent.getStringExtra("message");
        clientId = intent.getStringExtra("message");
        Log.i("message1",clientId);
        // 初始化单轴使能
        byte[] message2 = new byte[]{0x01, 0x02, 0x03, 0x06}; // 示例消息
        nettyServerHandler.sendMessageToClient(clientId, message2); // 发送消息到客户端
        vibrateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (vibrator != null && vibrator.hasVibrator()) {
                    //vibrator.vibrate(500);
                }
                //String clientId = "/192.168.4.188:63160";
                byte[] message1 = new byte[]{0x01, 0x02, 0x03, 0x04}; // 示例消息
                Log.i("message2",clientId);
                Log.i("message3", Arrays.toString(message1));
                nettyServerHandler.sendMessageToClient(clientId, message1); // 发送消息到客户端
            }
        });
        //vibrateButton.setOnClickListener(new View.OnClickListener() {
        //    @Override
        //    public void onClick(View v) {
        //        if (vibrator != null && vibrator.hasVibrator()) {
        //            //vibrator.vibrate(500);
        //        }
        //
        //        //String clientId = "/192.168.4.188:63160";
        //        byte[] message1 = new byte[]{0x01, 0x02, 0x03, 0x04}; // 示例消息
        //        Log.i("message2",clientId);
        //        Log.i("message3", Arrays.toString(message1));
        //
        //        nettyServerHandler.sendMessageToClient(clientId, message1); // 发送消息到客户端
        //    }
        //});
        vibrateButton.setOnTouchListener(new CarTouchButton());
    }
    private class CarTouchButton implements View.OnTouchListener {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (motionEvent.getAction() == KeyEvent.ACTION_DOWN) {
                byte[] message2 = new byte[]{0x01, 0x02, 0x03, 0x06}; // 示例消息
                nettyServerHandler.sendMessageToClient(clientId, message2); // 发送消息到客户端
            } else if (motionEvent.getAction() == KeyEvent.ACTION_UP) {
                byte[] message2 = new byte[]{0x01, 0x02, 0x03, 0x07}; // 示例消息
                nettyServerHandler.sendMessageToClient(clientId, message2); // 发送消息到客户端
            }
            return false;
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
app/src/main/java/com/example/agvcontroller/StartActivity.java
@@ -52,7 +52,7 @@
            }
        });
        socketManager = new SocketManager();
        socketManager.startServer(8022);
        socketManager.startServer(8024);
    }
    @Subscribe(threadMode = ThreadMode.MAIN)
app/src/main/java/com/example/agvcontroller/protocol/AGV_01_DOWN.java
New file
@@ -0,0 +1,50 @@
package com.example.agvcontroller.protocol;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
 * 路径数据包
 * Created by vincent on 2023/3/16
 */
public class AGV_01_DOWN implements IMessageBody, Serializable {
    private static final long serialVersionUID = -2786382674008041014L;
    @Override
    public byte[] writeToBytes() {
        String serialNo = Utils.zeroFill(this.serialNo, 16);
        byte[] serialNoBytes = Utils.reverse(serialNo.getBytes());
        //byte pathLen = (byte) actionItems.size();
        byte[] pathBytes = new byte[0];
        //for (AgvActionItem<? extends IActionBody> item : actionItems) {
        //    //byte[] bytes = RadixTools.intToBytes(Integer.parseInt(item.getQrCode()));
        //    //byte[] qrCodeBytes = Utils.reverse(bytes);
        //    //byte actionCodeByte = (byte) item.getActionCmdType().actionCode;
        //    //byte valByte = (byte) item.getVal();
        //    //byte[] actionBodyBytes = item.getActionBody().writeToBytes();
        //    //byte[] merge = Utils.merge(qrCodeBytes, actionCodeByte, valByte, actionBodyBytes);
        //    pathBytes = Utils.merge(pathBytes, merge);
        //}
        return Utils.merge(serialNoBytes, pathLen, pathBytes);
    }
    @Override
    public void readFromBytes(byte[] messageBodyBytes) {
    }
    // 流水号 - 16
    private String serialNo;
    // 路径长度
    private int pathLen;
    // 路径列表
    //private List<AgvActionItem<? extends IActionBody>> actionItems = new ArrayList<>();
}
app/src/main/java/com/example/agvcontroller/protocol/AGV_02_DOWN.java
@@ -1,8 +1,5 @@
package com.zy.acs.common.domain.protocol;
package com.example.agvcontroller.protocol;
import com.zy.acs.common.utils.Utils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
@@ -10,7 +7,6 @@
 * 动作命令包
 * Created by vincent on 2023/3/21
 */
@Data
public class AGV_02_DOWN implements IMessageBody, Serializable {
    private static final long serialVersionUID = 1664188062202647371L;
@@ -19,8 +15,10 @@
    public byte[] writeToBytes() {
        String serialNo = Utils.zeroFill(this.serialNo, 16);
        byte[] serialNoBytes = Utils.reverse(serialNo.getBytes());
        byte cmdCodeByte = (byte) this.getCmdCode();
        byte valByte = (byte) this.getVal();
        //byte cmdCodeByte = (byte) this.getCmdCode();
        byte cmdCodeByte = (byte) 1;
        //byte valByte = (byte) this.getVal();
        byte valByte = (byte) 1;
        byte[] cmdBodyBytes = commandBody.writeToBytes();
app/src/main/java/com/example/agvcontroller/protocol/AGV_03_DOWN.java
New file
@@ -0,0 +1,23 @@
package com.example.agvcontroller.protocol;
import java.io.Serializable;
/**
 * 心跳
 * Created by vincent on 2023/3/16
 */
public class AGV_03_DOWN implements IMessageBody, Serializable {
    private static final long serialVersionUID = 6922520599887812372L;
    @Override
    public byte[] writeToBytes() {
        return new byte[0];
    }
    @Override
    public void readFromBytes(byte[] messageBodyBytes) {
    }
}
app/src/main/java/com/example/agvcontroller/protocol/AGV_80_DOWN.java
New file
@@ -0,0 +1,24 @@
package com.example.agvcontroller.protocol;
import java.io.Serializable;
/**
 * 激活包
 * Created by vincent on 2023/3/16
 */
public class AGV_80_DOWN implements IMessageBody, Serializable {
    private static final long serialVersionUID = -8289774377582991418L;
    @Override
    public byte[] writeToBytes() {
        return new byte[0];
    }
    @Override
    public void readFromBytes(byte[] messageBodyBytes) {
    }
}
app/src/main/java/com/example/agvcontroller/protocol/AGV_A1_DOWN.java
New file
@@ -0,0 +1,28 @@
package com.example.agvcontroller.protocol;
import java.io.Serializable;
/**
 * 应答正常包
 * Created by vincent on 2023/3/16
 */
public class AGV_A1_DOWN implements IMessageBody, Serializable {
    private static final long serialVersionUID = -1744072958515341165L;
    @Override
    public byte[] writeToBytes() {
        return Utils.merge(ackSign, Utils.reverse(temp));
    }
    @Override
    public void readFromBytes(byte[] messageBodyBytes) {
    }
    private byte ackSign = 0x11;
    private byte[] temp = new byte[4];
}
app/src/main/java/com/example/agvcontroller/protocol/AGV_F0_DOWN.java
New file
@@ -0,0 +1,32 @@
package com.example.agvcontroller.protocol;
import java.io.Serializable;
/**
 * Created by vincent on 2023/3/15
 */
public class AGV_F0_DOWN implements IMessageBody, Serializable {
    private static final long serialVersionUID = 5769100530170067014L;
    @Override
    public byte[] writeToBytes() {
        Utils.reverse(sysMark);
        Utils.reverse(sysVersion);
        Utils.reverse(temp);
        return Utils.merge(sysMark, sysVersion, temp);
    }
    @Override
    public void readFromBytes(byte[] bytes) {
    }
    private byte[] sysMark = new byte[] {0x00, 0x01};
    private byte[] sysVersion = new byte[] {0x01, 0x01};
    private byte[] temp = new byte[4];
}
app/src/main/java/com/example/agvcontroller/protocol/AgvPackage.java
@@ -1,8 +1,5 @@
package com.zy.acs.gateway.domain;
package com.example.agvcontroller.protocol;
import com.core.common.Cools;
import com.zy.acs.gateway.constant.AgvConstant;
import com.zy.acs.gateway.constant.PacErrorType;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
@@ -12,7 +9,7 @@
 * AGV报文模型
 * Created by vincent on 2023/3/10
 */
public class AgvPackage implements PackageSupport {
public class AgvPackage {
    /**
     * 源数据包缓冲区(引用)
@@ -58,14 +55,14 @@
        return agvPackage;
    }
    public String toLogString() {
        StringBuilder pacSb = new StringBuilder();
        pacSb.append("AGV(").append(getHeader().getUniqueNo()).append(")")
            .append("上传")
            .append("[").append(getHeader().getProtocolType().getDes()).append("]消息.")
            .append("原始消息[").append(getSourceHexStr()).append("]");
        return pacSb.toString();
    }
    //public String toLogString() {
    //    StringBuilder pacSb = new StringBuilder();
    //    pacSb.append("AGV(").append(getHeader().getUniqueNo()).append(")")
    //        .append("上传")
    //        .append("[").append(getHeader().getProtocolType().getDes()).append("]消息.")
    //        .append("原始消息[").append(getSourceHexStr()).append("]");
    //    return pacSb.toString();
    //}
    public ByteBuf getSourceBuff() {
        // 因为处理buffer时,总是希望拿到一个重置了readerIndex的buf
@@ -139,19 +136,19 @@
        return this;
    }
    public ByteBuf convert(ByteBuf byteBuf){
        byteBuf.writeByte(this.getHeader().getStartSymbol())
                .writeShort(this.getHeader().getContentLength())
                .writeByte(this.getHeader().getProtocolType().getCode())
                .writeBytes(this.getHeader().getUniqueNo().getBytes(AgvConstant.CHARSET_GBK))
                .writeByte(this.getHeader().getEncryptType().getCode())
                .writeBytes(this.getBody().getContent())
                .writeByte(this.getValidCode());
        // 计算并设置校验码
//        this.setValidCode(ValidUtil.caculateValidByteFromBuff(byteBuf));
        byteBuf.resetReaderIndex();
        byteBuf.writerIndex(byteBuf.readableBytes() - 1).writeByte(this.getValidCode());
        return byteBuf;
    }
//    public ByteBuf convert(ByteBuf byteBuf){
//        byteBuf.writeByte(this.getHeader().getStartSymbol())
//                .writeShort(this.getHeader().getContentLength())
//                .writeByte(this.getHeader().getProtocolType().getCode())
//                .writeBytes(this.getHeader().getUniqueNo().getBytes(AgvConstant.CHARSET_GBK))
//                .writeByte(this.getHeader().getEncryptType().getCode())
//                .writeBytes(this.getBody().getContent())
//                .writeByte(this.getValidCode());
//        // 计算并设置校验码
////        this.setValidCode(ValidUtil.caculateValidByteFromBuff(byteBuf));
//        byteBuf.resetReaderIndex();
//        byteBuf.writerIndex(byteBuf.readableBytes() - 1).writeByte(this.getValidCode());
//        return byteBuf;
//    }
}
app/src/main/java/com/example/agvcontroller/protocol/AgvProtocol.java
New file
@@ -0,0 +1,37 @@
package com.example.agvcontroller.protocol;
import java.io.Serializable;
/**
 * Created by vincent on 2023/3/23
 */
//@Data
public class AgvProtocol implements Serializable {
    private static final long serialVersionUID = 8858385758421567504L;
    private String agvNo;
    private int timestamp;
    private IMessageBody messageBody;
    public static AgvProtocol build(String agvNo) {
        AgvProtocol protocol = new AgvProtocol();
        protocol.setAgvNo(agvNo);
        //protocol.setTimestamp((int) (System.currentTimeMillis()/1000));
        return protocol;
    }
    public AgvProtocol setAgvNo(String agvNo) {
        this.agvNo = agvNo;
        return this;
    }
    public AgvProtocol setMessageBody(IMessageBody messageBody) {
        this.messageBody = messageBody;
        return this;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/CRCUtils.java
@@ -1,4 +1,4 @@
package com.zy.acs.gateway.utils;
package com.example.agvcontroller.protocol;
/**
 * CRC16循环冗余校验码
app/src/main/java/com/example/agvcontroller/protocol/Cools.java
New file
@@ -0,0 +1,367 @@
package com.example.agvcontroller.protocol;
import java.lang.reflect.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
 * Created by vincent on 2019-06-09
 */
public class Cools {
    public static boolean isEmpty(Object... objects) {
        for (Object obj : objects){
            if (isEmpty(obj)){
                return true;
            }
        }
        return false;
    }
    @SuppressWarnings("rawtypes")
    public static boolean isEmpty(Object o) {
        if (o == null) {
            return true;
        }
        if (o instanceof String) {
            if (o.toString().trim().equals("")) {
                return true;
            }
        } else if (o instanceof List) {
            if (((List) o).size() == 0) {
                return true;
            }
        } else if (o instanceof Map) {
            if (((Map) o).size() == 0) {
                return true;
            }
        } else if (o instanceof Set) {
            if (((Set) o).size() == 0) {
                return true;
            }
        } else if (o instanceof Object[]) {
            if (((Object[]) o).length == 0) {
                return true;
            }
        } else if (o instanceof int[]) {
            if (((int[]) o).length == 0) {
                return true;
            }
        } else if (o instanceof long[]) {
            if (((long[]) o).length == 0) {
                return true;
            }
        }
        return false;
    }
    public static int sqlLimitIndex(Integer pageIndex, Integer pageSize){
        return (pageIndex - 1) * pageSize;
    }
    //public static String enToken(String username, String password){
    //    return AesUtils.encrypt(username, zerofill(password, 16));
    //}
    //public static String deTokn(String token, String password){
    //    //return AesUtils.decrypt(token, zerofill(password, 16));
    //}
    public static String zerofill(String msg, Integer count){
        if (msg.length() == count){
            return msg;
        } else if (msg.length() > count){
            return msg.substring(0, 16);
        } else {
            StringBuilder msgBuilder = new StringBuilder(msg);
            for (int i = 0; i<count-msg.length(); i++){
                msgBuilder.append("0");
            }
            return msgBuilder.toString();
        }
    }
    /**
     * 截取字符串(默认end=true)
     * @param str 被截字符串
     * @param end true:最后一个字符 / false:第一个字符
     */
    public static String deleteChar(String str, boolean end){
        if (isEmpty(str)){
            return "";
        }
        if (end){
            return str.substring(0, str.length()-1);
        } else {
            return str.substring(1);
        }
    }
    public static String deleteChar(String str){
        return deleteChar(str, true);
    }
    /**
     * map 转 对象
     */
    public static <T> T conver(Map<? extends String, ?> map, Class<T> cls){
        T instance = null;
        try {
            Constructor<T> constructor = cls.getDeclaredConstructor();
            boolean constructorAccessible = constructor.isAccessible();
            constructor.setAccessible(true);
            instance = constructor.newInstance();
            constructor.setAccessible(constructorAccessible);
        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        Class<?> prototype = cls;
        do {
            for (Field field : prototype.getDeclaredFields()){
                if (Modifier.isFinal(field.getModifiers())
                        || Modifier.isStatic(field.getModifiers())
                        || Modifier.isTransient(field.getModifiers())){
                    continue;
                }
                String fieldName = field.getName();
                Object val = null;
                if (map.containsKey(fieldName)){
                    val = map.get(fieldName);
                }
                if (val != null){
                    boolean fieldAccessible = field.isAccessible();
                    field.setAccessible(true);
                    Class<?> type = field.getType();
                    try {
                        Constructor<?> constructor = type.getDeclaredConstructor(String.class);
                        boolean constructorAccessible = constructor.isAccessible();
                        constructor.setAccessible(true);
                        field.set(instance, constructor.newInstance(String.valueOf(val)));
                        constructor.setAccessible(constructorAccessible);
                    } catch (IllegalAccessException
                            | InstantiationException
                            | InvocationTargetException
                            | NoSuchMethodException e) {
                        System.err.println("convert error ===> Class["+prototype+"],Field:["+fieldName+"],Type:["+type+"],Value:["+val+"]");
                    }
                    field.setAccessible(fieldAccessible);
                }
            }
            prototype = prototype.getSuperclass();
        } while (!Object.class.equals(prototype));
        return instance;
    }
    /**
     * 对象 转 map
     */
    public static Map<String, Object> conver(Object obj){
        Class<?> cls = obj.getClass();
        Field[] fields = getAllFields(cls);
        Map<String, Object> map = new HashMap<>();
        for (Field field : fields) {
            if (Modifier.isFinal(field.getModifiers())
                    || Modifier.isStatic(field.getModifiers())
                    || Modifier.isTransient(field.getModifiers())){
                continue;
            }
            String key = field.getName();
            boolean flag = field.isAccessible();
            field.setAccessible(true);
            Object val = null;
            try {
                val = field.get(obj);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            field.setAccessible(flag);
            if (val != null){
                map.put(key, val);
            }
        }
        return map;
    }
    /**
     * 获取指定Class(及其SuperClass)的成员变量
     */
    public static Field[] getAllFields(Class<?> cls){
        return getAllFields(cls, null);
    }
    /**
     * 递归合并基类Field
     */
    private static Field[] getAllFields(Class<?> cls, Field[] params){
        Field[] fields = (params == null) ? cls.getDeclaredFields() : params;
        Class<?> superCls = cls.getSuperclass();
        if (superCls == null || superCls == Object.class){
            return fields;
        }
        Field[] superClsFields = superCls.getDeclaredFields();
        fields = addAll(fields, superClsFields);
        return getAllFields(superCls, fields);
    }
    /**
     * 根据fieldName获取Field对象
     */
    public static Field getField(Class<?> cls, String fieldName) {
        Field[] allFields = getAllFields(cls);
        for (Field field : allFields) {
            if (field.getName().equals(fieldName)) {
                return field;
            }
        }
        return null;
    }
    /**
     * 获取对象中某个Field的值
     */
    public static Object getFieldValue(Object obj, Field field) {
        if (null == field) {
            return null;
        } else {
            if (obj instanceof Class) {
                obj = null;
            }
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            try {
                return field.get(obj);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
    /**
     * 数组叠加
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] addAll(T[] array1, T... array2) {
        if (array1 == null) {
            return clone(array2);
        } else if (array2 == null) {
            return clone(array1);
        } else {
            Class<?> cls = array1.getClass().getComponentType();
            T[] joinedArray = (T[]) Array.newInstance(cls, array1.length + array2.length);
            System.arraycopy(array1, 0, joinedArray, 0, array1.length);
            try {
                System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
                return joinedArray;
            } catch (ArrayStoreException e) {
                Class<?> type2 = array2.getClass().getComponentType();
                if (!cls.isAssignableFrom(type2)) {
                    throw new RuntimeException("Cannot store " + type2.getName() + " in an array of " + cls.getName(), e);
                } else {
                    throw e;
                }
            }
        }
    }
    /**
     * 克隆
     */
    private static <T> T[] clone(T[] array) {
        return array == null ? null : array.clone();
    }
    /**
     * map操作
     */
    public static CoolMap add(String key,Object value){
        CoolMap map = new CoolMap();
        map.put(key, value);
        return map;
    }
    public static class CoolMap extends HashMap<String, Object>{
        public CoolMap add(String key,Object value){
            this.put(key, value);
            return this;
        }
        public CoolMap $(String key,Object value){
            this.put(key, value);
            return this;
        }
    }
    public static String md5(String string){
        try{
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes(StandardCharsets.UTF_8));
            char[] chars = new char[bytes.length * 2];
            for (int i = 0; i < bytes.length; i++) {
                int b = bytes[i];
                chars[i * 2] = hexDigits[(b & 0xF0) >> 4];
                chars[i * 2 + 1] = hexDigits[b & 0x0F];
            }
            return new String(chars).toLowerCase();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("md5加密失败,str=".concat(string));
        }
    }
    private static char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    //public static Map<String, Object> translate(Object obj){
    //    Class<?> cls = obj.getClass();
    //    Field[] fields = getAllFields(cls);
    //    Map<String, Object> map = new HashMap<>();
    //    for (Field field : fields) {
    //        String key = field.getName();
    //        if (field.isAnnotationPresent(CoolTranslate.class)){
    //            CoolTranslate annotation = field.getAnnotation(CoolTranslate.class);
    //            if (!isEmpty(annotation.value())) {
    //                key = annotation.value();
    //            }
    //        }
    //        boolean flag = field.isAccessible();
    //        field.setAccessible(true);
    //        Object val = null;
    //        try {
    //            val = field.get(obj);
    //        } catch (IllegalAccessException e) {
    //            e.printStackTrace();
    //        }
    //        field.setAccessible(flag);
    //        if (val != null){
    //            map.put(key, val);
    //        }
    //    }
    //    return map;
    //}
    public static boolean eq(String str, String str0) {
        if (Cools.isEmpty(str) && Cools.isEmpty(str0)) {
            return true;
        }
        if (Cools.isEmpty(str) && !Cools.isEmpty(str0)) {
            return false;
        }
        if (Cools.isEmpty(str0) && !Cools.isEmpty(str)) {
            return false;
        }
        if (str.equals(str0)) {
            return true;
        }
        return false;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/DirectionType.java
New file
@@ -0,0 +1,9 @@
package com.example.agvcontroller.protocol;
public enum DirectionType {
    DOWN,
    UP,
    ;
}
app/src/main/java/com/example/agvcontroller/protocol/EncryType.java
New file
@@ -0,0 +1,66 @@
package com.example.agvcontroller.protocol;
/**
 * 加密标识枚举
 * Created by vincent on 2019-04-02
 */
public enum EncryType implements ICodedStatus {
    /**
     * 数据不加密
      */
    ENCRY_EMPTY(0x01),
    /**
     * RSA算法加密
     */
    ENCRY_RSA(0x02),
    /**
     * AES128位算法加密
     */
    ENCRY_AES128(0x03),
    /**
     * 异常
     */
    ENCRY_EXCEPTION(0xFE),
    /**
     * 无效
     */
    ENCRY_INVALID(0xFF);
    /**
     * 其他数值为预留
     */
    private int code;
    EncryType(int code) {
        this.code = code;
    }
    public static EncryType getByCode(int code) {
        for (EncryType e :
                EncryType.values()) {
            if (e.getCode() == code) {
                return e;
            }
        }
        return null;
    }
    @Override
    public int getCode() {
        return code;
    }
    // 预期的期望值,逗号分隔
    public static String expectedVals() {
        StringBuilder valSb = new StringBuilder();
        for (EncryType c :
                EncryType.values()) {
            valSb.append(c.getCode()).append(",");
        }
        // 删除最后一个逗号
        valSb.deleteCharAt(valSb.length() - 1);
        return valSb.toString();
    }
}
app/src/main/java/com/example/agvcontroller/protocol/ICodedStatus.java
New file
@@ -0,0 +1,9 @@
package com.example.agvcontroller.protocol;
/**
 * 预留接口,反射时调用
 * Created by vincent on 2019-04-03
 */
public interface ICodedStatus {
    int getCode();
}
app/src/main/java/com/example/agvcontroller/protocol/ICommandBody.java
New file
@@ -0,0 +1,12 @@
package com.example.agvcontroller.protocol;
/**
 * 命令参数
 */
public interface ICommandBody {
    byte[] writeToBytes();
    void readFromBytes(byte[] messageBodyBytes);
}
app/src/main/java/com/example/agvcontroller/protocol/IMessageBody.java
@@ -1,4 +1,4 @@
package com.zy.acs.common.domain.protocol;
package com.example.agvcontroller.protocol;
import java.io.Serializable;
app/src/main/java/com/example/agvcontroller/protocol/PacBody.java
New file
@@ -0,0 +1,38 @@
package com.example.agvcontroller.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
/**
 * 协议体
 * Created by vincent on 2019-04-03
 */
public class PacBody {
    private ByteBuf content;
    private IMessageBody messageBody;
    public ByteBuf getContent() {
        return content;
    }
    public PacBody setContent(ByteBuf content) {
        this.content = content.duplicate();
        return this;
    }
    public IMessageBody getMessageBody() {
        return messageBody;
    }
    public void setMessageBody(IMessageBody messageBody) {
        this.messageBody = messageBody;
    }
    @Override
    public String toString() {
        return ByteBufUtil.hexDump(content);
    }
}
app/src/main/java/com/example/agvcontroller/protocol/PacErrorType.java
New file
@@ -0,0 +1,48 @@
package com.example.agvcontroller.protocol;
public enum PacErrorType implements ICodedStatus {
    /**
     * 解析异常
     */
    PAC_ANALYZE_ERROR(0x01, "报文解析异常"),
    /**
     * 报文长度验证异常
     */
    PAC_LENGTH_ERROR(0x02, "报文长度验证异常"),
    /**
     * 报文校验异常
     */
    PAC_VALID_ERROR(0x023, "报文长度验证异常"),
    ;
    private int code;   // 编码
    private String des; // 描述
    PacErrorType(int code, String des) {
        this.code = code;
        this.des = des;
    }
    public int getCode() {
        return code;
    }
    public String getDes() {
        return des;
    }
    public static PacErrorType getByCode(int code) {
        for (PacErrorType c :
                PacErrorType.values()) {
            if (c.getCode() == code) {
                return c;
            }
        }
        return null;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/PacHeader.java
@@ -1,15 +1,10 @@
package com.zy.acs.gateway.domain;
package com.example.agvcontroller.protocol;
import com.zy.acs.gateway.constant.EncryType;
import com.zy.acs.gateway.constant.ProtocolType;
import lombok.Data;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
 * 协议头
 * Created by vincent on 2019-04-03
 */
@Data
public class PacHeader {
    /**
@@ -84,15 +79,15 @@
        return this;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("startSymbol", startSymbol)
                .append("contentLength", contentLength)
                .append("uniqueNo", uniqueNo)
                .append("timestamp", timestamp)
                .append("protocolType", protocolType.getDes())
                .append("serialNum", serialNum)
                .toString();
    }
    //@Override
    //public String toString() {
    //    return new ToStringBuilder(this)
    //            .append("startSymbol", startSymbol)
    //            .append("contentLength", contentLength)
    //            .append("uniqueNo", uniqueNo)
    //            .append("timestamp", timestamp)
    //            .append("protocolType", protocolType.getDes())
    //            .append("serialNum", serialNum)
    //            .toString();
    //}
}
app/src/main/java/com/example/agvcontroller/protocol/PackagePart.java
New file
@@ -0,0 +1,87 @@
package com.example.agvcontroller.protocol;
/**
 * 报文标识枚举
 * 下标1: 起始索引
 * 下标2: 长度
 * 下标3: 描述
 * Created by vincent on 2019-04-03
 */
public enum PackagePart {
    /**
     * 起始符
     */
    START_SYMBOL(0, 1, "起始符"),
    /**
     * 数据单元长度
     */
    CONTENT_LENGTH(1, 2, "数据单元长度"),
    /**
     * 唯一标识码
     */
    UNIQUENO(3, 4, "唯一标识码"),
    /**
     * 时间戳
     */
    TIMESTAMP(7, 4, "时间戳"),
    /**
     * 命令标识
     */
    COMMAND_MARK(11, 1, "命令标识"),
    /**
     * 数据单元
     */
    CONTENT(12, -1, "数据单元"),
    /**
     * 校验码
     */
    VALIDE_CODE( -1, 2, "校验码"),
    /**
     * 应答标志
     */
    ACK_MARK(3, 1, "应答标志"),
    /**
     * 数据单元加密方式
     */
    ENCRYPT_TYPE(21, 1, "数据单元加密方式"),
    ;
    // 字节段开始索引
    private Integer startIndex;
    // 字节段总字节数
    private Integer len;
    // 描述
    private String des;
    PackagePart(int startIndex, int len, String des) {
        this.startIndex = startIndex;
        this.len = len;
        this.des = des;
    }
    public Integer getStartIndex() {
        return startIndex;
    }
    public Integer getLen() {
        return len;
    }
    public String getDes() {
        return des;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/ProtocolEncoder.java
@@ -1,20 +1,12 @@
package com.zy.acs.gateway.handler.coder;
package com.example.agvcontroller.protocol;
import com.core.common.RadixTools;
import com.zy.acs.common.utils.Utils;
import com.zy.acs.gateway.config.SystemProperties;
import com.zy.acs.gateway.constant.PackagePart;
import com.zy.acs.gateway.domain.AgvPackage;
import com.zy.acs.gateway.utils.ValidUtil;
import java.util.logging.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * 编码器
@@ -22,13 +14,13 @@
 * 处理方式: 异或和
 * Created by vincent on 2019-04-02
 */
@Component
//@Component
@ChannelHandler.Sharable
public class ProtocolEncoder extends MessageToByteEncoder<Object> {
    private static final Logger log = LoggerFactory.getLogger(ProtocolEncoder.class);
    //private static final Logger log = LoggerFactory.getLogger(ProtocolEncoder.class);
    @Autowired
    //@Autowired
    private SystemProperties systemProperties;
    @Override
@@ -48,9 +40,9 @@
            byte[] bodyBytes = pac.getBody().getMessageBody().writeToBytes();   // body
            String uniqueNo = pac.getHeader().getUniqueNo();
            //String uniqueNo = pac.getHeader().getUniqueNo();
                byte[] uniquenoBytes = RadixTools.intToBytes(Integer.parseInt(pac.getHeader().getUniqueNo()));   // uniqueno
                //byte[] uniquenoBytes = RadixTools.intToBytes(Integer.parseInt(pac.getHeader().getUniqueNo()));   // uniqueno
            int len = PackagePart.UNIQUENO.getLen()     // len
@@ -58,14 +50,14 @@
                    + PackagePart.COMMAND_MARK.getLen()
                    + bodyBytes.length;
            out.writeByte(pac.getHeader().getStartSymbol())         // symbol
                    .writeShortLE(len)
                    .writeBytes(Utils.reverse(uniquenoBytes))       // uniqueno
                    .writeIntLE((int) (System.currentTimeMillis() / 1000))   // timestamp
                    .writeByte(pac.getHeader().getProtocolType().getCode()) // type
                    .writeBytes(bodyBytes)                          // body
                    .writeShort(pac.getValidCode())                 // valid
            ;
            //out.writeByte(pac.getHeader().getStartSymbol())         // symbol
            //        .writeShortLE(len)
            //        .writeBytes(Utils.reverse(uniquenoBytes))       // uniqueno
            //        .writeIntLE((int) (System.currentTimeMillis() / 1000))   // timestamp
            //        .writeByte(pac.getHeader().getProtocolType().getCode()) // type
            //        .writeBytes(bodyBytes)                          // body
            //        .writeShort(pac.getValidCode())                 // valid
            //;
            pac.setValidCode(ValidUtil.calculateValidByteFromBuff(out));
            out.resetReaderIndex();
@@ -75,7 +67,7 @@
            if (systemProperties.isPrintPacLog()){
                log.info("Agv [{}] 下行 [{}] >>> {}", uniqueNo, pac.getHeader().getProtocolType().getDes(), ByteBufUtil.hexDump(out).toUpperCase());
                //log.info("Agv [{}] 下行 [{}] >>> {}", uniqueNo, pac.getHeader().getProtocolType().getDes(), ByteBufUtil.hexDump(out).toUpperCase());
            }
        }
app/src/main/java/com/example/agvcontroller/protocol/ProtocolPojoType.java
New file
@@ -0,0 +1,54 @@
package com.example.agvcontroller.protocol;
/**
 * 应答实体枚举
 * Created by vincent on 2019-04-02
 */
public enum ProtocolPojoType {
    PATH_COMMAND(ProtocolType.PATH_COMMAND, AGV_01_DOWN.class),
    ACTION_COMMAND(ProtocolType.ACTION_COMMAND, AGV_02_DOWN.class),
    LOGIN_ACK(ProtocolType.LOGIN_ACK, AGV_F0_DOWN.class),
    ACTION_SUCCESS_ACK(ProtocolType.ACTION_SUCCESS_ACK, AGV_A1_DOWN.class),
    ACTIVATION_COMMAND(ProtocolType.ACTIVATION_COMMAND, AGV_80_DOWN.class),
    HEARTBEAT_COMMAND(ProtocolType.HEARTBEAT_COMMAND, AGV_03_DOWN.class),
    ;
    public final ProtocolType protocolType;
    public final Class<? extends IMessageBody> clazz;
    ProtocolPojoType(ProtocolType protocolType, Class<? extends IMessageBody> clazz) {
        this.protocolType = protocolType;
        this.clazz = clazz;
    }
    public static ProtocolPojoType query(ProtocolType protocolType) {
        for (ProtocolPojoType type : ProtocolPojoType.values()) {
            if (protocolType.equals(type.protocolType)) {
                return type;
            }
        }
        return null;
    }
    public static ProtocolPojoType query(Class<? extends IMessageBody> clazz) {
        for (ProtocolPojoType type : ProtocolPojoType.values()) {
            if (clazz.equals(type.clazz)) {
                return type;
            }
        }
        return null;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/ProtocolType.java
New file
@@ -0,0 +1,82 @@
package com.example.agvcontroller.protocol;
/**
 * 标识枚举
 * Created by vincent on 2019-04-02
 */
public enum ProtocolType implements ICodedStatus {
    // 下行 -------------------------------------------------------------------
    PATH_COMMAND(0x01, "路径数据包", DirectionType.DOWN),
    ACTION_COMMAND(0x02, "动作命令包", DirectionType.DOWN),
    HEARTBEAT_COMMAND(0x03, "心跳包", DirectionType.DOWN),
    FAULT_CLEAR_COMMAND(0x04, "故障命令包", DirectionType.DOWN),
    ACTIVATION_COMMAND(0x80, "激活包", DirectionType.DOWN),
    LOGIN_ACK(0xF0, "登录应答包", DirectionType.DOWN),
    ACTION_SUCCESS_ACK(0xA1, "动作完成成功应答", DirectionType.DOWN),
    ACTION_FAIL_ACK(0xA0, "动作完成失败应答", DirectionType.DOWN),
    // 上行 -------------------------------------------------------------------
    PATH_ACK(0x01, "路径应答包", DirectionType.UP),
    COMMAND_ACK(0x02, "命令应答包", DirectionType.UP),
    ACTION_COMPLETE(0x11, "动作完成包", DirectionType.UP),
    DATA_CODE_REPORT(0x12, "有码实时数据包", DirectionType.UP),
    DATA_WITHOUT_CODE_REPORT(0x13, "无码实时数据包", DirectionType.UP),
    HEARTBEAT_REPORT(0x03, "心跳包", DirectionType.UP),
    FAULT_REPORT(0x04, "故障数据包", DirectionType.UP),
    HANDLE_FALUT_ACK(0x14, "故障清除应答包", DirectionType.UP),
    SILO_REPORT(0x70, "料仓信息包", DirectionType.UP),
    LOGIN_REPORT(0xF0, "机器人登陆数据包", DirectionType.UP),
    ;
    private final int code;   // 编码
    private final String des; // 描述
    private final DirectionType direction;
    ProtocolType(int code, String des, DirectionType direction) {
        this.code = code;
        this.des = des;
        this.direction = direction;
    }
    public int getCode() {
        return code;
    }
    public String getDes() {
        return des;
    }
    public DirectionType getDirection() {
        return direction;
    }
    public static ProtocolType getByCode(int code, DirectionType direction) {
        for (ProtocolType type : ProtocolType.values()) {
            if (type.getCode() == code && type.getDirection() == direction) {
                return type;
            }
        }
        return null;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/ProtocolUtils.java
@@ -1,38 +1,33 @@
package com.zy.acs.gateway.utils;
package com.example.agvcontroller.protocol;
import com.zy.acs.common.domain.AgvProtocol;
import com.zy.acs.common.domain.protocol.IMessageBody;
import com.zy.acs.gateway.constant.ProtocolPojoType;
import com.zy.acs.gateway.constant.ProtocolType;
import com.zy.acs.gateway.domain.AgvPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.logging.Logger;
/**
 * Created by vincent on 2019-04-03
 */
public class ProtocolUtils {
    private static final Logger log = LoggerFactory.getLogger(ProtocolUtils.class);
    //private static final Logger log = LoggerFactory.getLogger(ProtocolUtils.class);
    // 下行协议报文组装
    public static AgvPackage installDownProtocol(AgvProtocol protocol){
        ProtocolPojoType protocolPojo = ProtocolPojoType.query(protocol.getMessageBody().getClass());
        //ProtocolPojoType protocolPojo = ProtocolPojoType.query(protocol.getMessageBody().getClass());
        assert protocolPojo !=   null;
        //assert protocolPojo !=   null;
        AgvPackage ackPackage = AgvPackage.valueOfEmpty();
        ackPackage.getHeader()
                .setStartSymbol((byte)0xEE)
                .setContentLength(0)
                .setUniqueNo(protocol.getAgvNo())
                .setTimestamp(protocol.getTimestamp())
                .setProtocolType(protocolPojo.protocolType)
                //.setUniqueNo(protocol.getAgvNo())
                //.setTimestamp(protocol.getTimestamp())
                //.setProtocolType(protocolPojo.protocolType)
        ;
        ackPackage.getBody().setMessageBody(protocol.getMessageBody());
        //ackPackage.getBody().setMessageBody(protocol.getMessageBody());
        return ackPackage;
@@ -65,9 +60,9 @@
        } catch (ReflectiveOperationException reflectiveOperationException) {
            log.error("MessageBodyHandler ReflectiveOperationException", reflectiveOperationException);
            //log.error("MessageBodyHandler ReflectiveOperationException", reflectiveOperationException);
            log.error("java反射创建实例失败:{}", protocolPojo.clazz.getName());
            //log.error("java反射创建实例失败:{}", protocolPojo.clazz.getName());
            return null;
        }
app/src/main/java/com/example/agvcontroller/protocol/SystemProperties.java
New file
@@ -0,0 +1,94 @@
package com.example.agvcontroller.protocol;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 * vc系统配置
 * Created by luxiaotao on 2018/10/15
 */
//@Configuration
//@ConfigurationProperties(prefix = "agv-tcp")
public class SystemProperties {
    public static String HOST_NAME;
    static {
        try {
            HOST_NAME = InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            System.err.println("find hostname err");
        }
    }
    private int port;
    private int heartSeconds;
    private int backlog;
    private boolean keepAlive;
    private int sndbuf;
    private int rcvbuf;
    private boolean printPacLog;
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
    public int getHeartSeconds() {
        return heartSeconds;
    }
    public void setHeartSeconds(int heartSeconds) {
        this.heartSeconds = heartSeconds;
    }
    public int getBacklog() {
        return backlog;
    }
    public void setBacklog(int backlog) {
        this.backlog = backlog;
    }
    public boolean isKeepAlive() {
        return keepAlive;
    }
    public void setKeepAlive(boolean keepAlive) {
        this.keepAlive = keepAlive;
    }
    public int getSndbuf() {
        return sndbuf;
    }
    public void setSndbuf(int sndbuf) {
        this.sndbuf = sndbuf;
    }
    public int getRcvbuf() {
        return rcvbuf;
    }
    public void setRcvbuf(int rcvbuf) {
        this.rcvbuf = rcvbuf;
    }
    public boolean isPrintPacLog() {
        return printPacLog;
    }
    public void setPrintPacLog(final boolean printPacLog) {
        this.printPacLog = printPacLog;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/Utils.java
New file
@@ -0,0 +1,239 @@
package com.example.agvcontroller.protocol;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
 * Created by vincent on 2023/3/14
 */
public class Utils {
    /**
     * 数组倒序
     * @param bytes
     * @param <T>
     */
    public static <T> byte[] reverse(byte[] bytes) {
        if (bytes == null) return null;
        for (int start = 0, end = bytes.length - 1; start < end; start++, end--) {
            byte temp = bytes[end];
            bytes[end] = bytes[start];
            bytes[start] = temp;
        }
        return bytes;
    }
    /**
     * 截取数组
     * @param bytes 原数组
     * @param pos   定位(截取后包含定位点数据)
     * @param len   长度
     * @return new arr
     */
    public static byte[] slice(byte[] bytes, int pos, int len) {
        if (bytes == null || bytes.length == 0 || len == 0) {
            return new byte[0];
        }
        if (pos + len > bytes.length) {
            throw new RuntimeException("com.zy.acs.common.utils ArrayIndexOutOfBoundsException\n" +
                    "原数组 bytes 长度为 " + bytes.length + ",截取长度超过原数组!");
        }
        byte[] arr = new byte[len];
        System.arraycopy(bytes, pos, arr, 0, len);
        return arr;
    }
    public static byte[] sliceWithReverse(byte[] bytes, int pos, int len) {
        byte[] slice = slice(bytes, pos, len);
        reverse(slice);
        return slice;
    }
    public static byte[] merge(Object... objects) {
        int len = 0;
        for (Object object : objects) {
            if (object instanceof byte[]) {
                byte[] bytes = (byte[]) object;
                len += bytes.length;
            }
            if (object instanceof Byte) {
                len++;
            }
        }
        byte[] arr = new byte[len];
        int idx = 0;
        for (Object object : objects) {
            if (object instanceof byte[]) {
                byte[] bytes = (byte[]) object;
                System.arraycopy(bytes, 0, arr, idx, bytes.length);
                idx += bytes.length;
            }
            if (object instanceof Byte) {
                byte[] bytes = new byte[] {(Byte) object};
                System.arraycopy(bytes, 0, arr, idx, bytes.length);
                idx += bytes.length;
            }
        }
        return arr;
    }
    public static <T> String join(T[] array, String seq) {
        StringBuilder sb = new StringBuilder();
        if (array != null) {
            for (int i = 0; i < array.length; i++) {
                sb.append(array[i]);
                if (i < array.length - 1) {
                    sb.append(seq);
                }
            }
        }
        return sb.toString();
    }
    public static String zeroFill(String msg, Integer len) {
        len = Optional.ofNullable(len).orElse(16);
        if (msg.length() == len){
            return msg;
        } else if (msg.length() > len){
            return msg.substring(0, 16);
        } else {
            StringBuilder msgBuilder = new StringBuilder(msg);
            for (int i = 0; i<len-msg.length(); i++){
                msgBuilder.insert(0,"0");
            }
            return msgBuilder.toString();
        }
    }
    public static String removePrefix(String str, String prefix) {
        if (!Cools.isEmpty(str) && !Cools.isEmpty(prefix)) {
            if (str.startsWith(prefix)) {
                return str.substring(prefix.length());
            } else {
                return str;
            }
        } else {
            return str;
        }
    }
    public static String removeSuffix(String str, String suffix) {
        if (!Cools.isEmpty(str) && !Cools.isEmpty(suffix)) {
            if (str.endsWith(suffix)) {
                return str.substring(0, str.indexOf(suffix));
            } else {
                return str;
            }
        } else {
            return str;
        }
    }
    /**
     * 大驼峰 转 symbol小驼峰
     */
    public static String toSymbolCase(String str, char symbol) {
        if (str == null) {
            return null;
        } else {
            int length = str.length();
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < length; ++i) {
                char c = str.charAt(i);
                if (Character.isUpperCase(c)) {
                    Character preChar = i > 0 ? str.charAt(i - 1) : null;
                    Character nextChar = i < str.length() - 1 ? str.charAt(i + 1) : null;
                    if (null != preChar) {
                        if (symbol == preChar) {
                            if (null == nextChar || Character.isLowerCase(nextChar)) {
                                c = Character.toLowerCase(c);
                            }
                        } else if (Character.isLowerCase(preChar)) {
                            sb.append(symbol);
                            if (null == nextChar || Character.isLowerCase(nextChar)) {
                                c = Character.toLowerCase(c);
                            }
                        } else if (null == nextChar || Character.isLowerCase(nextChar)) {
                            sb.append(symbol);
                            c = Character.toLowerCase(c);
                        }
                    } else if (null == nextChar || Character.isLowerCase(nextChar)) {
                        c = Character.toLowerCase(c);
                    }
                }
                sb.append(c);
            }
            return sb.toString();
        }
    }
    public static String toCamelCase(CharSequence name) {
        if (null == name) {
            return null;
        } else {
            String name2 = name.toString();
            if (name2.contains("_")) {
                int length = name2.length();
                StringBuilder sb = new StringBuilder(length);
                boolean upperCase = false;
                for(int i = 0; i < length; ++i) {
                    char c = name2.charAt(i);
                    if (c == '_') {
                        upperCase = true;
                    } else if (upperCase) {
                        sb.append(Character.toUpperCase(c));
                        upperCase = false;
                    } else {
                        sb.append(Character.toLowerCase(c));
                    }
                }
                return sb.toString();
            } else {
                return name2;
            }
        }
    }
    public static String sub(String str, Integer maxLen) {
        if (str.length() > maxLen) {
            return str.substring(0, maxLen);
        }
        return str;
    }
    public static String generateSeqNum(String lastSeqNum) {
        if (Cools.isEmpty(lastSeqNum)) {
            return zeroFill("1", 4);
        } else {
            int i = Integer.parseInt(lastSeqNum);
            if (i >= 9999) {
                return zeroFill("1", 4);
            } else {
                return zeroFill(String.valueOf(i+1), 4);
            }
        }
    }
    // pos start in 0
    public static boolean getBit(byte b, int position) {
        if (position < 0 || position > 7) {
            throw new IllegalArgumentException("Bit position must be between 0 and 7");
        }
        int mask = 1 << position;
        return (b & mask) != 0;
    }
    public static <T> List<T> singletonList(T o) {
        List<T> list = new ArrayList<>();
        list.add(o);
        return list;
    }
}
app/src/main/java/com/example/agvcontroller/protocol/ValidUtil.java
New file
@@ -0,0 +1,55 @@
package com.example.agvcontroller.protocol;
import io.netty.buffer.ByteBuf;
/**
 * 校验工具类
 * Created by vincent on 2019-04-03
 */
public class ValidUtil {
    public static final Integer MAX_DEFAULT = 65531;
    /**
     * 校验消息中数值是否在对应允许的范围内
     * @param item  被校验数据值
     * @param min   允许的最小值
     * @param max   允许的最大值
     */
    public static boolean rangeValid(int item, int min, int max) {
        return !(item < min || item > max);
    }
    /**
     * 异或校验包完整
     *      从命令单元(第三个字节)开始,一次与后一个字节异或,直至倒数第二个字节。
     *      将异或的结果与报文中最后一个字节比较。
     *      -相等:报文完整
     *      -不相等:报文不完整
     */
    public static boolean validPac(AgvPackage pac) {
        ByteBuf buf = pac.getSourceBuff();
        return getValdByteInMsg(pac) == calculateValidByteFromBuff(buf);
    }
    /**
     * 从当前数据包计算校验码
     */
    public static int calculateValidByteFromBuff(ByteBuf buf) {
        buf.resetReaderIndex();
        byte[] bytes = new byte[buf.readableBytes() - PackagePart.VALIDE_CODE.getLen()];
        buf.readBytes(bytes);
        int currValidByte = CRCUtils.crc16(bytes);
        // 重置读指针
        buf.resetReaderIndex();
        return currValidByte;
    }
    /**
     * 获取当前数据包中的校验码(最后一位字节)
     */
    public static int getValdByteInMsg(AgvPackage pac) {
        return pac.getValidCode();
    }
}