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
————————————————

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM