MODBUS协议对接

MODBUS协议对接

通讯方式:NIO

SocketChanel简介:

socketChanel属于NIOSocket,不同与传统socket, NIOSocket是引入了三个概念:Channel、Selector、Buffer。Buffer是将很多请求打包,一次性发出去。有Selector扮演选择器的角色,将请求分转给对应的通道(Channel)进行处理响应。

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。

这里用到的是ServerSocketChannel 的非阻塞模式。

关键方法:
.open()//创建连接
.close()//关闭连接
.configureBlocking//设定阻塞或非阻塞
.write()//写入
.read()//获取
.bind()//绑定监听端口
.register(Selector sel, Int ops)//向给定的选择器注册此通道,返回一个选择键( SelectionKey)
           // ops参数:
           // OP_ACCEPT:用于套接字接受操作的操作集位
           // OP_CONNECT:用于套接字连接操作的操作集位
           // OP_READ:用于读取操作的操作集位
           // OP_WRITE:用于写入操作的操作集位 

实现代码:

public void messageReceiver() throws Exception {
        //监听新进来的TCP连接 对每一个新进来的连接都会创建一个SocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定8089端口
        serverSocketChannel.socket().bind(new InetSocketAddress(8098));
        //与Selector一起使用时,Channel必须处于非阻塞模式下
        serverSocketChannel.configureBlocking(false);
        //打开Selector 可在单线程状态监听多个Channel
        Selector selector = Selector.open();
        //注册监听的channel
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        logger.info("服务器启动成功");
        while (true) {
            selector.select();
            //注册到该Selector的通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //ServerSocketChannel 接受了一个连接
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ);
                    logger.info("客户端连接成功:" + socketChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    //ServerSocketChannel已准备好阅读
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    InetSocketAddress addresss = (InetSocketAddress) socketChannel.getRemoteAddress();
                    String address = addresss.getAddress().getHostAddress();
                    String port = String.valueOf(addresss.getPort());
                    //创建容量为 128 字节的缓冲区
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    //获取读取到的字节数,读入缓冲区
                    int len = socketChannel.read(byteBuffer);
                    if (len > 0) {
                        byteBuffer.flip();//使缓冲区准备好读取
                        byte[] dst = new byte[len];
                        byteBuffer.get(dst);//一次读取到的字节数据
                        logger.info("接收到消息:" + toHex(dst) + "  len:" + dst.length + "  from:" + address + ":" + port);
                        
                        /**
                         * 业务模块
                        **/
                        
                     } else if (len == -1) {
                        //如果客户端断开连接,关闭socket
                        logger.info("客户端断开连接");
                        socketChannel.close();
                    }
                }
                //从事件集合里删除本次处理的key,防止下次select重复处理
                iterator.remove();
            }
        }
    }

协议解析:对象、报文互转

modbus通用工具类:

数据转对象:

    public static ModbusDataAnalyzeBean dataAnalyzeBill(byte[] data) {
        ModbusDataAnalyzeBean modbusDataAnalyzeBean = new ModbusDataAnalyzeBean();
        modbusDataAnalyzeBean.setFrameHeader(Integer.parseInt(getOctFromHexBytes(data, 0, 1)));//获取帧头
        modbusDataAnalyzeBean.setLength(Integer.parseInt(getOctFromHexBytes(data, 2)));//获取数据长度
        modbusDataAnalyzeBean.setFuncode(Integer.parseInt(getOctFromHexBytes(data, 4)));//获取控制字
        modbusDataAnalyzeBean.setAddr(Integer.parseInt(getOctFromHexBytes(data, 5)));//获取loraId(地址)
        modbusDataAnalyzeBean.setDataO(bcdToString(data, 7, 18));
        return modbusDataAnalyzeBean;
    }
对象转数据:

    public static byte[] data(ModbusDataFormationBean modbusDataFormationBean) {
        byte[] command = {};
        command = append(command, octInt2ByteArray(modbusDataFormationBean.getFrameHeader1(), 0));//设置帧头1
        command = append(command, octInt2ByteArray(modbusDataFormationBean.getFrameHeader2(), 1));//设置帧头1
        command = append(command, octInt2ByteArray(modbusDataFormationBean.getLength(), 0)); //设置数据长度
        command = append(command, octInt2ByteArray(modbusDataFormationBean.getLength2(), 0));
        command = append(command, octInt2ByteArray(modbusDataFormationBean.getFuncode(), 0));//设置控制字
        command = append(command, octInt2ByteArray(modbusDataFormationBean.getLoraIdLow(), 0));//设置地址低字节(loraId)
        command = append(command, octInt2ByteArray(modbusDataFormationBean.getLoraIdHigh(), 0));//设置地址高字节
        command = append(command, str2Bcd(modbusDataFormationBean.getData()));//设置数据值
        command = append(command, octInt2ByteArray(andVerification(command), 0));// 设置效验和

        return command;
    }
字符串转换为Ascii码

    public static String stringTransformAscii(String hex) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < hex.length() - 1; i += 2) {
            String h = hex.substring(i, (i + 2));
            int decimal = Integer.parseInt(h, 16);
            sb.append((char) decimal);
        }
        return sb.toString();
    }
取得十制数组的from~to位,并按照十六进制转化值
  private static String getOctFromHexBytes(byte[] data, Object from, Object... to) {
        if (data != null && data.length > 0 && from != null) {
            try {
                byte[] value;
                int fromIndex = Integer.parseInt(from.toString());
                if (to != null && to.length > 0) {
                    int toIndex = Integer.parseInt(to[0].toString());
                    if (fromIndex >= toIndex || toIndex <= 0) {
                        value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
                    } else {
                        value = Arrays.copyOfRange(data, fromIndex, toIndex + 1);
                    }
                } else {
                    value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
                }
                if (value != null && value.length > 0) {
                    long octValue = 0L;
                    int j = -1;
                    for (int i = value.length - 1; i >= 0; i--, j++) {
                        int d = value[i];
                        if (d < 0) {
                            d += 256;
                        }
                        octValue += Math.round(d * Math.pow(10, 2 * j + 2));
                    }
                    return new Long(octValue).toString();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
16进制的字符串表示转成字节数组
 public static byte[] hexString2ByteArray(String hexString, int... capacity) {
        hexString = hexString.toLowerCase();
        if (hexString.length() % 2 != 0) {
            hexString = "0" + hexString;
        }
        int length = hexString.length() / 2;
        if (length < 1) {
            length = 1;
        }
        int size = length;
        if (capacity != null && capacity.length > 0 && capacity[0] >= length) {
            size = capacity[0];
        }
        final byte[] byteArray = new byte[size];
        int k = 0;
        for (int i = 0; i < size; i++) {
            if (i < size - length) {
                byteArray[i] = 0;
            } else {
                byte high = (byte) (Character.digit(hexString.charAt(k), 16) & 0xff);
                if (k + 1 < hexString.length()) {
                    byte low = (byte) (Character.digit(hexString.charAt(k + 1), 16) & 0xff);
                    byteArray[i] = (byte) (high << 4 | low);
                } else {
                    byteArray[i] = (byte) (high);
                }
                k += 2;
            }
        }
        return byteArray;

    }
连接字节流
private static byte[] append(byte[] datas, byte[] data) {
        if (datas == null) {
            return data;
        }
        if (data == null) {
            return datas;
        } else {
            return concat(datas, data);
        }
    }
字节流拼接
private static byte[] concat(byte[]... data) {
        if (data != null && data.length > 0) {
            int size = 0;
            for (int i = 0; i < data.length; i++) {
                size += data[i].length;
            }
            byte[] byteArray = new byte[size];
            int pos = 0;
            for (int i = 0; i < data.length; i++) {
                byte[] b = data[i];
                for (int j = 0; j < b.length; j++) {
                    byteArray[pos++] = b[j];
                }
            }
            return byteArray;
        }
        return null;
    }
校验和获取,所有数据求和后%256 ,返回一个字节
public static Integer andVerification(byte[] bytes) {
        int iSum = 0;
        for (int i = 0; i < bytes.length; i++) {
            iSum += bytes[i];
        }
        iSum %= 0x100;
        return iSum;
    }
String转成BCD码
public static byte[] StrToBCDBytes(String s) {
        if (s.length() % 2 != 0) {
            s = "0" + s;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        char[] cs = s.toCharArray();
        for (int i = 0; i < cs.length; i += 2) {
            int high = cs[i] - 48;
            int low = cs[i + 1] - 48;
            baos.write(high << 4 | low);
        }
        return baos.toByteArray();
    }
BCD码的from~to位转成String
public static String bcdToString(byte[] data, Object from, Object... to) {
        if (data != null && data.length > 0 && from != null) {
            try {
                byte[] value;
                int fromIndex = Integer.parseInt(from.toString());
                if (to != null && to.length > 0) {
                    int toIndex = Integer.parseInt(to[0].toString());
                    if (fromIndex >= toIndex || toIndex <= 0) {
                        value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
                    } else {
                        value = Arrays.copyOfRange(data, fromIndex, toIndex + 1);
                    }
                } else {
                    value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
                }
                if (value != null && value.length > 0) {
                    StringBuffer sb = new StringBuffer();
                    for (int i = 0; i < value.length; i++) {
                        int h = ((value[i] & 0xff) >> 4) + 48;
                        sb.append((char) h);
                        int l = (value[i] & 0x0f) + 48;
                        sb.append((char) l);
                    }
                    return sb.toString();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

对象设置:

modbus解析为对象的实体类
public class ModbusDataAnalyzeBean {
    private Integer frameHeader;//帧头
    private Integer length;//数据长度
    private Integer funcode;//控制字
    private Integer addr;//地址
    private ArrayList<String> data;//数据段
    private Integer num;//集抄数
    private String dataO;//单数据

    public Integer getFrameHeader() {
        return frameHeader;
    }

    public void setFrameHeader(Integer frameHeader) {
        this.frameHeader = frameHeader;
    }

    public Integer getLength() {
        return length;
    }

    public void setLength(Integer length) {
        this.length = length;
    }

    public Integer getFuncode() {
        return funcode;
    }

    public void setFuncode(Integer funcode) {
        this.funcode = funcode;
    }

    public Integer getAddr() {
        return addr;
    }

    public void setAddr(Integer addr) {
        this.addr = addr;
    }

    public ArrayList<String> getData() {
        return data;
    }

    public void setData(ArrayList<String> data) {
        this.data = data;
    }

    public String getDataO() {
        return dataO;
    }

    public void setDataO(String dataO) {
        this.dataO = dataO;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }
}
拼接为modbus数据的实体类
public class ModbusDataFormationBean {
    private Integer frameHeader1;//帧头1
    private Integer frameHeader2;//帧头2
    private Integer length;//数据长度
    private Integer length2;//
    private Integer funcode;//控制字
    private Integer loraIdLow;//(地址)低字节
    private Integer loraIdHigh;// (地址)高字节
    private Integer num;//数据个数
    private String data;//数据段
    private ArrayList<String> dataLong; //数据段集合
    private Integer checksum;//效验和


    public ArrayList<String> getDataLong() {
        return dataLong;
    }

    public void setDataLong(ArrayList<String> dataLong) {
        this.dataLong = dataLong;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public Integer getLength2() {
        return length2;
    }

    public void setLength2(Integer length2) {
        this.length2 = length2;
    }

    public Integer getFrameHeader1() {
        return frameHeader1;
    }

    public void setFrameHeader1(Integer frameHeader1) {
        this.frameHeader1 = frameHeader1;
    }

    public Integer getFrameHeader2() {
        return frameHeader2;
    }

    public void setFrameHeader2(Integer frameHeader2) {
        this.frameHeader2 = frameHeader2;
    }

    public Integer getLength() {
        return length;
    }

    public void setLength(Integer length) {
        this.length = length;
    }

    public Integer getFuncode() {
        return funcode;
    }

    public void setFuncode(Integer funcode) {
        this.funcode = funcode;
    }

    public Integer getLoraIdLow() {
        return loraIdLow;
    }

    public void setLoraIdLow(Integer loraIdLow) {
        this.loraIdLow = loraIdLow;
    }

    public Integer getLoraIdHigh() {
        return loraIdHigh;
    }

    public void setLoraIdHigh(Integer loraIdHigh) {
        this.loraIdHigh = loraIdHigh;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Integer getChecksum() {
        return checksum;
    }

    public void setChecksum(Integer checksum) {
        this.checksum = checksum;
    }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
3 CRC 检测 使用RTU 模式消息包括了一基于CRC 方法的错误检测域CRC 域检测了整个消息的内容 CRC 域是两个字节包含一16 位的二进制值它由传输设备计算后加入到消息中接收设备重 新计算收到消息的CRC 并与接收到的CRC 域中的值比较如果两值不同则有误 CRC 是先调入一值是全1”的16 位寄存器然后调用一过程将消息中连续的8 位字节各当前寄 存器中的值进行处理仅每个字符中的8Bit 数据对CRC 有效起始位和停止位以及奇偶校验位 均无效 CRC 产生过程中每个8 位字符都单独和寄存器内容相或OR 结果向最低有效位方向移动 最高有效位以0 填充LSB 被提取出来检测如果LSB 为1 寄存器单独和预置的值或一下如 果LSB 为0 则不进行整个过程要重复8 次在最后一位第8 位完成后下一个8 位字节 又单独和寄存器的当前值相或最终寄存器中的值是消息中所有的字节都执行之后的CRC 值 珠海市裕泉水务公司 Http://plc.21ds.com Email:zhuhai007@tom.com 8 CRC 添加到消息中时低字节先加入然后高字节 CRC 简单函数如下 unsigned short CRC16(puchMsg, usDataLen) unsigned char *puchMsg ; /* 要进行CRC 校验的消息 */ unsigned short usDataLen ; /* 消息中字节数 */ { unsigned char uchCRCHi = 0xFF ; /* 高CRC 字节初始化 */ unsigned char uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */ unsigned uIndex ; /* CRC 循环中的索引 */ while (usDataLen--) /* 传输消息缓冲区 */ { uIndex = uchCRCHi ^ *puchMsgg++ ; /* 计算CRC */ uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex} ; uchCRCLo = auchCRCLo[uIndex] ; } return (uchCRCHi << 8 | uchCRCLo) ; } /* CRC 高位字节值表 */ static unsigned char auchCRCHi[] = 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 珠海市裕泉水务公司 Http://plc.21ds.com Email:zhuhai007@tom.com 9 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 } ; /* CRC 低位字节值表*/ static char auchCRCLo[] = 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 珠海市裕泉水务公司 Http://plc.21ds.com Email:zhuhai007@tom.com 10 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值