• ModbusTcp协议的Java Socket


    1 简介
    modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的modbus协议:modbusTCP。

    Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。

    标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

    2 ModbusTCP数据帧
    ModbusTCP的数据帧可分为两部分:MBAP+PDU。

    2.1 报文头MBAP
    MBAP为报文头,长度为7字节,组成如下:


    事务处理标识 协议标识 长度 单元标识符
    2字节 2字节 2字节 1字节
    事务处理标识 :可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
    协议标识符 :00 00表示ModbusTCP协议。
    长度 :表示接下来的数据长度,单位为字节。
    单元标识符 :可以理解为设备地址。

    2.2 帧结构PDU
    PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。

    2.2.1 功能码
    modbus的操作对象有四种:线圈、离散输入、输入寄存器、保持寄存器。

    线圈:PLC的输出位,开关量,在MODBUS中可读可写
    离散量:PLC的输入位,开关量,在MODBUS中只读
    输入寄存器:PLC中只能从模拟量输入端改变的寄存器,在MODBUS中只读
    保持寄存器:PLC中用于输出模拟量信号的寄存器,在MODBUS中可读可写
    根据对象的不同,modbus的功能码有:

    • 0x01:读线圈
    • 0x05:写单个线圈
    • 0x0F:写多个线圈
    • 0x02:读离散量输入
    • 0x04:读输入寄存器
    • 0x03:读保持寄存器
    • 0x06:写单个保持寄存器
    • 0x10:写多个保持寄存器

    二,模拟了直接发送Socket套接字(上位机)跟Modbus Slave软件(下位机)进行通信,代码如下:

            Socket socket = new Socket("192.1.1.4",9600);

            InputStream is=socket.getInputStream();

            OutputStream os=socket.getOutputStream();

            byte[] sendInfo = new byte[] { 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03,0x00, 0x00, 0x00, 0x08 };  //发送给Modbus Slave软件的消息

            os.write(sendInfo);

            os.flush();

            byte[] bs = new byte[32];   

            is.read(bs);

            printHexString(bs);  //输出十六进制

    软件当中Alias从0到9的值如下:

     

    程序取到的结果如下:000700000013010310 0457 08AE 0D05 115C 15B3 1A0A 1E61 22B8 00000000000000

    在解释结果之前,先给出ModbusTcp的报文格式。

    ModbusTcp报文主要是由三部分组成,如下图所示:

    其中,MBAP报文头一般为7个字节,我在这里以上封邮件的例子来做解释:

            byte[] sendInfo = new byte[] { 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03,0x00, 0x00, 0x00, 0x08 }; 

    从byte数组的第1个元素开始到第七个元素,分别是:0007|0000|0006|01

            1)0007:事务处理标识符,用于将请求与未来响应之间建立联系(服务器端应答时候复制该值);

            2)0000:协议标识,其中 0 代表Modbus协议,1代表UNI-TE协议(服务器端应答时候复制该值);

            3)0006:长度,计算其后续所有字节数(服务器端应答时候该字节重新生成,并取返回指令的相同逻辑值);

            4)01:单元标识符,在MODBUS或MODBUS+串行链路子网中对设备进行寻址时,这个域是用于路由的目的(服务器端应答时候复制该值)。

    因此,我们可以得知服务器端应答时候,其返回的MBAP报文头为:

            0007 | 0000 | 0013 | 01

    接下来是功能码(占用一个字节)。Modbus有一大堆的功能码,但常用的命令一般有01,02,03,04等几个

     1)01:读取线圈状态,Modbus对应的地址为00001开始;

            2)02:读取输入状态,Modbus对应的地址为10001开始;

            3)03:读保持寄存器,Modbus对应的地址为40001开始;

            4)04:输入寄存器,Modbus对应的地址为30001开始。

    最后便是数据了。数据段的长度是根据功能码类型来对应的,因此这里我们仍然用sendInfo这个例子:

            该例子的功能码为03,说明读取的是保持寄存器。在功能码后面的 0000 为读取的起始地址,0008 为读取的数量,因此该指令可以理解为:

            读取保持寄存器当中,从0000地址开始(也就是Modbus的40001地址)依次读取八个数据(即16个字节)

    根据模拟器的截图,从保持寄存器的0起始地址开始,依次有以上的数据,因此服务器端应答的内容如下:

            0007|0000|0013|01|03|10 0457 08AE 0D05 115C 15B3 1A0A 1E61 22B8

            其中MBAP报文头的内容在上面已有说明,这里就说下从单元标识符往后的内容:

     1)03:功能码,跟请求的功能码一致;

            2)10:字节计数,就是后续的字节数(同时也是请求指令当中读取数量的double);

            3)之后的便是存放的数据了,以十六进制给出,一个数据占两个字节。

    使用jlibmodbus

    maven依赖
    <dependency>
    <groupId>com.intelligt.modbus</groupId>
    <artifactId>jlibmodbus</artifactId>
    <version>1.2.9.7</version>
    </dependency>

    package com.tcb.jlibmodbus;
    
    import java.net.InetAddress;
    
    import com.intelligt.modbus.jlibmodbus.Modbus;
    import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
    import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
    import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
    import com.intelligt.modbus.jlibmodbus.master.ModbusMaster;
    import com.intelligt.modbus.jlibmodbus.master.ModbusMasterFactory;
    import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
    
    
    /**
     * Hello world!
     *
     */
    public class App {
        public static void main(String[] args) {
            try {
                // 设置主机TCP参数
                TcpParameters tcpParameters = new TcpParameters();
     
                // 设置TCP的ip地址
                InetAddress adress = InetAddress.getByName("127.0.0.1");
     
                // TCP参数设置ip地址
                // tcpParameters.setHost(InetAddress.getLocalHost());
                tcpParameters.setHost(adress);
     
                // TCP设置长连接
                tcpParameters.setKeepAlive(true);
                // TCP设置端口,这里设置是默认端口502
                tcpParameters.setPort(Modbus.TCP_PORT);
     
                // 创建一个主机
                ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
                Modbus.setAutoIncrementTransactionId(true);
     
                int slaveId = 1;//从机地址
                int offset = 0;//寄存器读取开始地址
                int quantity = 10;//读取的寄存器数量
     
     
                try {
                    if (!master.isConnected()) {
                        master.connect();// 开启连接
                    }
     
                    // 读取对应从机的数据,readInputRegisters读取的写寄存器,功能码04
                    int[] registerValues = master.readInputRegisters(slaveId, offset, quantity);
     
                    // 控制台输出
                    for (int value : registerValues) {
                        System.out.println("Address: " + offset++ + ", Value: " + value);
                    }
     
                } catch (ModbusProtocolException e) {
                    e.printStackTrace();
                } catch (ModbusNumberException e) {
                    e.printStackTrace();
                } catch (ModbusIOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        master.disconnect();
                    } catch (ModbusIOException e) {
                        e.printStackTrace();
                    }
                }
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    打印到控制台的信息

    Address: 0, Value: 88
    Address: 1, Value: 66
    Address: 2, Value: 8
    Address: 3, Value: 6
    Address: 4, Value: 32727
    Address: 5, Value: 32808
    Address: 6, Value: 0
    Address: 7, Value: 3
    Address: 8, Value: 2
    Address: 9, Value: 1
    ————————————————

  • 相关阅读:
    Delphi Code Editor 之 几个特性(转)
    如何访问局域网的Access数据库?
    Delphi Live Bindings 初探
    重装Delphi10.2的IDE必要设置
    TClientDataSet数据源设置
    js删除数组里的某个数据
    初识localstorage用法
    Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
    css实现文本溢出用...显示
    原生js和jquery
  • 原文地址:https://www.cnblogs.com/lsp666/p/14595346.html
Copyright © 2020-2023  润新知