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 04Rx:029-00 59 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04Tx:030-00 5A 00 00 00 06 01 03 00 00 00 04Rx:031-00 5A 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04Tx:032-00 5B 00 00 00 06 01 03 00 00 00 04Rx:033-00 5B 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04Tx:034-00 5C 00 00 00 06 01 03 00 00 00 04Rx:035-00 5C 00 00 00 0B 01 03 08 00 01 00 02 00 03 00 04Tx:036-00 5D 00 00 00 06 01 03 00 00 00 04Rx: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\" />
有两种方式可以做到:
- 非常简单 ,使用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 {@Overridepublic 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) {@Overrideprotected void onDataReceived(byte[] readBuffer, int size) {//todo 根据业务解析数据}};socketForModbusTCP.connect();socketForModbusTCP.sendHex(\"005A00000006010300000004\");socketForModbusTCP.close();
- 使用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>() {@Overridepublic void onSuccess(String s) {Log.d(TAG, \"onSuccess \" + s);}@Overridepublic void onFailed(String msg) {Log.d(TAG, \"onFailed \" + msg);}});
slaveId—-从站地址, startAddress—-起始地址,quantityOfRegister—-读取寄存器个数
读线圈
ModbusReq.getInstance().readCoil(new OnRequestBack<boolean[]>() {@Overridepublic void onSuccess(boolean[] booleen) {Log.d(TAG, \"readCoil onSuccess \" + Arrays.toString(booleen));}@Overridepublic void onFailed(String msg) {Log.e(TAG, \"readCoil onFailed \" + msg);}}, slaveId, startAddress, quantityOfRegisterx);
读单个/多个离散输入寄存器
ModbusReq.getInstance().readDiscreteInput(new OnRequestBack<boolean[]>() {@Overridepublic void onSuccess(boolean[] booleen) {Log.d(TAG, \"readDiscreteInput onSuccess \" + Arrays.toString(booleen));}@Overridepublic void onFailed(String msg) {Log.e(TAG, \"readDiscreteInput onFailed \" + msg);}}, slaveId, startAddress, quantityOfRegister);
读单个/多个保持寄存器
ModbusReq.getInstance().readHoldingRegisters(new OnRequestBack<short[]>() {@Overridepublic void onSuccess(short[] data) {Log.d(TAG, \"readHoldingRegisters onSuccess \" + Arrays.toString(data));}@Overridepublic void onFailed(String msg) {Log.e(TAG, \"readHoldingRegisters onFailed \" + msg);}}, slaveId, startAddress, quantityOfRegister);
读单个/多个输入寄存器
ModbusReq.getInstance().readInputRegisters(new OnRequestBack<short[]>() {@Overridepublic void onSuccess(short[] data) {Log.d(TAG, \"readInputRegisters onSuccess \" + Arrays.toString(data));}@Overridepublic 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>() {@Overridepublic void onSuccess(String s) {Log.e(TAG, \"writeCoil onSuccess \" + s);}@Overridepublic void onFailed(String msg) {Log.e(TAG, \"writeCoil onFailed \" + msg);}}, slaveId, writeAddress, booleanValue);
写多个线圈
ModbusReq.getInstance().writeCoils(new OnRequestBack<String>() {@Overridepublic void onSuccess(String s) {Log.e(TAG, \"writeCoil onSuccess \" + s);}@Overridepublic void onFailed(String msg) {Log.e(TAG, \"writeCoil onFailed \" + msg);}}, slaveId, quantityOfRegister, booleanValues);
写单寄存器
ModbusReq.getInstance().writeRegister(new OnRequestBack<String>() {@Overridepublic void onSuccess(String s) {Log.e(TAG, \"writeRegister onSuccess \" + s);}@Overridepublic void onFailed(String msg) {Log.e(TAG, \"writeRegister onFailed \" + msg);}}, slaveId, writeAddress, intValue);
写多个寄存器
ModbusReq.getInstance().writeRegisters(new OnRequestBack<String>() {@Overridepublic void onSuccess(String s) {Log.e(TAG, \"writeRegisters onSuccess \" + s);}@Overridepublic void onFailed(String msg) {Log.e(TAG, \"writeRegisters onFailed \" + msg);}}, slaveId, quantityOfRegister, shortValues);
销毁Modbus实例
ModbusReq.getInstance().destroy();
写到最后,感谢一下这个库的作者。GitHub地址