(回答来自简书)
Modbus网络是一个工业通信系统,由带智能终端的可编程序控制器和计算机通过公用线路或局部专用线路连接而成。其系统结构既包括硬件、亦包括软件。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。它已经成为一通用工业标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。
此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如果回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。
Modbus协议是一项应用层报文传输协议,采用master/slave方式通信。1996年施耐德公司推出基于以太网TCP/IP的modbus协议:modbusTCP。
Modbus TCP数据帧包含了报文头、功能代码和数据三部分
MBAP(Modbus Application Protocol, Modbus应用协议)报文头 分四个域(传输标志、协议标志、长度和单元标志),共7个字节
功能代码:
一般常用的公共功能代码如下:(十六进制显示)
读线圈状态----------------01
读离散输入寄存器-------02
读保持寄存器-------------03
读输入寄存器-------------04
写单个线圈----------------05
写单个保持寄存器-------06
写多个线圈----------------0F
写多个保持寄存器-------10
数据
数据内容跟前面的功能代码有关系的。
举例:
下面是一段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这一块内容我会在下一篇文章会讲到。
首先是添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
有两种方式可以做到:
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();
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地址