Modbus在Android上的應用之Modbus TCP Master

Mobus

(回答來自簡書)
Modbus網絡是一個工業通信系統,由帶智能終端的可編程序控制器和計算機通過公用線路或局部專用線路連接而成。其系統結構既包括硬件、亦包括軟件。通過此協議,控制器相互之間、控制器經由網絡(例如以太網)和其它設備之間可以通信。它已經成爲一通用工業標準。有了它,不同廠商生產的控制設備可以連成工業網絡,進行集中監控。
此協議定義了一個控制器能認識使用的消息結構,而不管它們是經過何種網絡進行通信的。它描述了一控制器請求訪問其它設備的過程,如果迴應來自其它設備的請求,以及怎樣偵測錯誤並記錄。它制定了消息域格局和內容的公共格式。

Modbus協議是一項應用層報文傳輸協議,採用master/slave方式通信。1996年施耐德公司推出基於以太網TCP/IP的modbus協議:modbusTCP

Modbus TCP協議結構

Modbus TCP數據幀包含了報文頭、功能代碼和數據三部分
在這裏插入圖片描述
MBAP(Modbus Application Protocol, Modbus應用協議)報文頭 分四個域(傳輸標誌、協議標誌、長度和單元標誌),共7個字節
在這裏插入圖片描述
功能代碼
一般常用的公共功能代碼如下:(十六進制顯示)
讀線圈狀態----------------01
讀離散輸入寄存器-------02
讀保持寄存器-------------03
讀輸入寄存器-------------04
寫單個線圈----------------05
寫單個保持寄存器-------06
寫多個線圈----------------0F
寫多個保持寄存器-------10
數據
數據內容跟前面的功能代碼有關係的。
舉例:

  • 如果是讀數據,比如功能代碼是03, 那數據內容是:寄存器起始地址(兩字節) + 讀取寄存器個數 (兩字節)。
  • 如果是寫數據,比如功能代碼是06,那數據內容是: 寫入寄存器地址(兩字節)+ 寫入數據(兩字節)

下面是一段Modbus TCP發送和接收的報文內容:
Tx爲Client端,即Master
Rx爲Server端,即Slave

Tx:028-00 59 00 00 00 06 01 03 00 00 00 04 
Rx:029-00 59 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04 
Tx:030-00 5A 00 00 00 06 01 03 00 00 00 04 
Rx:031-00 5A 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04 
Tx:032-00 5B 00 00 00 06 01 03 00 00 00 04 
Rx:033-00 5B 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04 
Tx:034-00 5C 00 00 00 06 01 03 00 00 00 04 
Rx:035-00 5C 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04 
Tx:036-00 5D 00 00 00 06 01 03 00 00 00 04 
Rx:037-00 5D 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04 

重點解析Tx的報文格式
00 5A 00 00 00 06 01 03 00 00 00 04

“00 5A 00 00 00 06 01”爲MBAP報文頭
其中
“00 5A”->事務處理標識符,由Client發起,Server複製,可以看到Rx報文開頭就是這個標識符。
“00 00”->協議標誌,等於0,代表是Modbus協議。
“00 06”->長度,從下一個字節開始到最後的數據長度,上面的數據長度等於6。
“01”->從站地址等於1
“03”->功能代碼,讀保持寄存器
“00 00”->寄存器起始地址等於0
“00 04”->讀取寄存器個數等於4

看到這裏,是不是想寫個報文測試下呢?
這篇文章是講如何在Android上實現Modbus TCP Master,對於Modbus TCP Slave這一塊內容我會在下一篇文章會講到。

如何在Android上實現Modbus TCP Master?

首先是添加網絡權限:

<uses-permission android:name="android.permission.INTERNET" />

有兩種方式可以做到:

  1. 非常簡單 ,使用Socket建立雙方連接,但是需要手寫報文,才能實現數據傳輸
    public void connect() {

        try {
            this.mSocket = new Socket();
            this.mSocket.setKeepAlive(true);
            this.mSocketAddress = new InetSocketAddress(ip, port);
            this.mSocket.connect( mSocketAddress, 3000 );// 設置連接超時時間爲3秒

            this.mOutputStream = mSocket.getOutputStream();
            this.mInputStream = mSocket.getInputStream();

            this.mReadThread = new ReadThread();
            this.mReadThread.start();
            this._isConnected = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

如果雙方連接建立成功,就可以發送數據和接收數據了。

    //發送數據
    public void sendHex(String sHex) {
        byte[] bOutArray = ByteUtil.HexToByteArr(sHex);//轉成16進制
        send(bOutArray);
    }
    //數據監聽
    protected abstract void onDataReceived(byte[] readBuffer, int size);
    

所謂手寫報文,就是按照上面Modbus TCP的報文結構寫出來,比如‘00 5A 00 00 00 06 01 03 00 00 00 04’,調用sendHex(“005A00000006010300000004”)
貼上完整代碼:

public abstract class SocketForModbusTCP
{
    private String ip;
    private int port;

    private Socket mSocket;
    private SocketAddress mSocketAddress;
    private OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;
    private boolean _isConnected = false;

    public SocketForModbusTCP(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    public void connect() {

        try {
            this.mSocket = new Socket();
            this.mSocket.setKeepAlive(true);
            this.mSocketAddress = new InetSocketAddress(ip, port);
            this.mSocket.connect( mSocketAddress, 3000 );// 設置連接超時時間爲3秒

            this.mOutputStream = mSocket.getOutputStream();
            this.mInputStream = mSocket.getInputStream();

            this.mReadThread = new ReadThread();
            this.mReadThread.start();
            this._isConnected = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void close() {
        if (this.mReadThread != null) {
            this.mReadThread.interrupt();
        }
        if (this.mSocket != null) {
            try {
                this.mSocket.close();
                this.mSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        this._isConnected = false;
    }

    public boolean isConnected() {
        return this._isConnected;
    }

    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (!isInterrupted()) {
                try {
                    if (SocketForModbusTCP.this.mInputStream == null) {
                        return;
                    }
                    int available = SocketForModbusTCP.this.mInputStream.available();
                    if (available > 0) {
                        byte[] buffer = new byte[available];
                        int size = SocketForModbusTCP.this.mInputStream.read(buffer);
                        if (size > 0) {
                            SocketForModbusTCP.this.onDataReceived(buffer, size);
                        }
                    } else {
                        SystemClock.sleep(50);
                    }


                } catch (Throwable e) {
                    Log.e("error", e.getMessage());
                    return;
                }
            }
        }
    }

    private void send(byte[] bOutArray) {
        try {
            this.mOutputStream.write(bOutArray);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void sendHex(String sHex) {
        byte[] bOutArray = ByteUtil.HexToByteArr(sHex);//轉成16進制
        send(bOutArray);
    }

    protected abstract void onDataReceived(byte[] readBuffer, int size);
}

調用方式:

       SocketForModbusTCP socketForModbusTCP = new SocketForModbusTCP(ip, port) {
            @Override
            protected void onDataReceived(byte[] readBuffer, int size) {
                //todo 根據業務解析數據
            }
        };

        socketForModbusTCP.connect();

        socketForModbusTCP.sendHex("005A00000006010300000004");

        socketForModbusTCP.close();
  1. 使用modbus4Android-1.2.jar下載地址
    這個庫封裝了modbus的tcp/ip模式的協議,還蠻好用的,但是無法讀取多個控制器的數據,因爲ModbusReq用了單例模式,如果Android端需要跟多個控制器做數據交換,可以在自己的項目上新建一個ModbusReq,將原來的單例模式改掉,將構造函數暴露出來。
    將modbus4Android-1.2.jar複製到app/libs下面,修改app/build.gradle
implementation files('libs/modbus4Android-1.2.jar')

創建並初始化ModbusReq實例

ModbusReq.getInstance().setParam(new ModbusParam()
                .setHost("192.168.0.105") 
                .setPort(502) 
                .setEncapsulated(false)
                .setKeepAlive(true)
                .setTimeout(2000)
                .setRetries(0))
                .init(new OnRequestBack<String>() {
            @Override
            public void onSuccess(String s) {
                Log.d(TAG, "onSuccess " + s);
            }

            @Override
            public void onFailed(String msg) {
                Log.d(TAG, "onFailed " + msg);
            }
        });

slaveId----從站地址, startAddress----起始地址,quantityOfRegister----讀取寄存器個數
讀線圈

ModbusReq.getInstance().readCoil(new OnRequestBack<boolean[]>() {
                    @Override
                    public void onSuccess(boolean[] booleen) {
                        Log.d(TAG, "readCoil onSuccess " + Arrays.toString(booleen));
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "readCoil onFailed " + msg);
                    }
                }, slaveId, startAddress, quantityOfRegisterx);

讀單個/多個離散輸入寄存器

ModbusReq.getInstance().readDiscreteInput(new OnRequestBack<boolean[]>() {
                    @Override
                    public void onSuccess(boolean[] booleen) {
                        Log.d(TAG, "readDiscreteInput onSuccess " + Arrays.toString(booleen));
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "readDiscreteInput onFailed " + msg);
                    }
                }, slaveId, startAddress, quantityOfRegister);

讀單個/多個保持寄存器

ModbusReq.getInstance().readHoldingRegisters(new OnRequestBack<short[]>() {
                    @Override
                    public void onSuccess(short[] data) {
                        Log.d(TAG, "readHoldingRegisters onSuccess " + Arrays.toString(data));
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "readHoldingRegisters onFailed " + msg);
                    }
                }, slaveId, startAddress, quantityOfRegister);

讀單個/多個輸入寄存器

 ModbusReq.getInstance().readInputRegisters(new OnRequestBack<short[]>() {
                    @Override
                    public void onSuccess(short[] data) {
                        Log.d(TAG, "readInputRegisters onSuccess " + Arrays.toString(data));
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "readInputRegisters onFailed " + msg);
                    }
                }, slaveId, startAddress, quantityOfRegister);

writeAddress----寫入寄存器地址, quantityOfRegister----寫入寄存器個數
booleanValue----true/false值, booleanValues-----boolea[]數組,intValue----int值,shortValues-------short[]數組

寫線圈

ModbusReq.getInstance().writeCoil(new OnRequestBack<String>() {
                    @Override
                    public void onSuccess(String s) {
                        Log.e(TAG, "writeCoil onSuccess " + s);
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "writeCoil onFailed " + msg);
                    }
                }, slaveId, writeAddress, booleanValue);

寫多個線圈

ModbusReq.getInstance().writeCoils(new OnRequestBack<String>() {
                    @Override
                    public void onSuccess(String s) {
                        Log.e(TAG, "writeCoil onSuccess " + s);
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "writeCoil onFailed " + msg);
                    }
                }, slaveId, quantityOfRegister, booleanValues);

寫單寄存器

ModbusReq.getInstance().writeRegister(new OnRequestBack<String>() {
                    @Override
                    public void onSuccess(String s) {
                        Log.e(TAG, "writeRegister onSuccess " + s);
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "writeRegister onFailed " + msg);
                    }
                }, slaveId, writeAddress, intValue);

寫多個寄存器

ModbusReq.getInstance().writeRegisters(new OnRequestBack<String>() {
                    @Override
                    public void onSuccess(String s) {
                        Log.e(TAG, "writeRegisters onSuccess " + s);
                    }

                    @Override
                    public void onFailed(String msg) {
                        Log.e(TAG, "writeRegisters onFailed " + msg);
                    }
                }, slaveId, quantityOfRegister, shortValues);

銷燬Modbus實例

ModbusReq.getInstance().destroy();

寫到最後,感謝一下這個庫的作者。GitHub地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章