Modbus TCP Slave
这篇文章是接着我上一篇文章的。Modbus在Android上的应用之Modbus TCP Master 之前做了很多项目都是在用Master,Android端做主站,去读下面PLC数据。但是网上关于Android端做从站的案例很少,资料就更不用提了,几乎都是纸上谈兵,没有用代码实现的。
既然是做Slave,被其他Master读取数据,那么肯定要理解下面这四种寄存器:
前提:知道bit,byte和word的区别:bit------------位,byte---------字节,word--------字
1字 = 2字节(1 word = 2byte), 1字节 = 8位 (1 byte = 8 bit)
- 线圈寄存器:
一个线圈寄存器相当于一个bit,也相当于一个开关量,反映的是状态信号,如开关开、合,就是0或1。每一个bit对应一个信号的开关状态,所以一个byte就可以同时控制8路的信号开关状态,一个word就可以同时控制16路的信号开关状态。线圈寄存器是支持读写的,在常用公共功能代码里面,读单个或者多个线圈寄存器功能码是0x01,写单个线圈寄存器功能码0x05 ,写多个线圈寄存器功能码是0x0f。
线圈寄存器(DO)Modbus地址范围:00000~09999
- 离散输入寄存器(也可以叫数字量输入寄存器):
离散输入寄存器相当于线圈寄存器的只读模式,它也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的0x02。
离散输入寄存器(DI)Modbus地址范围:10000~19999
- 保持寄存器:
一个保持寄存器相当于一个word,也就是两个byte,用来存放具体的数据量的,比如存放温度设定值,保持寄存器是支持读写的,读单个或者多个保持寄存器功能码是0x03,写单个保持寄存器功能码是0x06,写多个保持寄存器功能码是0x10。
保持寄存器(AO)Modbus地址范围:40000~49999
- 输入寄存器:
输入寄存器相当于保持寄存器的只读模式,一个寄存器也是占用2byte空间,但是存放的数据量只能被读取,比如存放室内实际温度值,所以它的功能码也只有一个读的0x04。
输入寄存器(AI)Modbus地址范围:30000~39999
Android端如何实现?
首先是添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
- 使用ModbusTCP这个库,添加依赖
在工程的build.gradle添加
allprojects {
repositories {
......
maven { url 'https://jitpack.io' }
}
}
- 在app/build.gradle添加
implementation 'com.github.hwx95:ModbusTCP:v1.1'
- 准备过程映像
SimpleProcessImage spi = new SimpleProcessImage();
- 添加寄存器
//线圈寄存器(DO)
spi.addDigitalOut(new SimpleDigitalOut(true));
spi.addDigitalOut(new SimpleDigitalOut(true));
spi.addDigitalOut(new SimpleDigitalOut(true));
spi.addDigitalOut(new SimpleDigitalOut(true));
//离散输入寄存器(DI)
spi.addDigitalIn(new SimpleDigitalIn(false));
spi.addDigitalIn(new SimpleDigitalIn(true));
spi.addDigitalIn(new SimpleDigitalIn(false));
spi.addDigitalIn(new SimpleDigitalIn(true));
//保持寄存器(AO)
spi.addRegister(new SimpleRegister(251));
spi.addRegister(new SimpleRegister(13));
spi.addRegister(new SimpleRegister(26));
spi.addRegister(new SimpleRegister(240));
//输入寄存器(AI)
spi.addInputRegister(new SimpleInputRegister(45));
spi.addInputRegister(new SimpleInputRegister(210));
spi.addInputRegister(new SimpleInputRegister(75));
spi.addInputRegister(new SimpleInputRegister(39));
- 创建耦合器
ModbusCoupler.getReference().setProcessImage(spi);
ModbusCoupler.getReference().setMaster(false);//默认是true,这里需要设置为false
ModbusCoupler.getReference().setUnitID(1);//从站地址
- 创建ModbusTCPListener,监听数据交换
/**
* 网络操作相关的子线程
*/
Runnable networkTask = new Runnable() {
@Override
public void run() {
listener = new ModbusTCPListener(3);
//Android 1024 以下端口属于系统端口,需要root权限
listener.setPort(port);//port=1025也是可以的
listener.start();
}
};
//线程启动,整个创建过程全部完成
new Thread(networkTask).start();
- Slave自身寄存器的操作(读取和写值)
//寄存器地址,从0开始,等于之前add的顺序
int registerAddress = 0;
//线圈寄存器(DO)
DigitalOut digitalOut = spi.getDigitalOut(registerAddress);
//读值
boolean readDigitalOut = digitalOut.isSet();
//写值
digitalOut.set(true);
//离散输入寄存器(DI)
DigitalIn digitalIn = spi.getDigitalIn(registerAddress);
//只能读值,不能写值
boolean readDigitalIn = digitalIn.isSet();
//保持寄存器(AO)
Register register = spi.getRegister(registerAddress);
//读值
int readRegisterInt = register.getValue();//int类型
int readRegisterUnsignedShort = register.toUnsignedShort();//无符号整型
short readRegisterShort = register.toShort();//short类型
byte[] readRegisterBytes = register.toBytes();//byte[]类型
//写值
//int类型
register.setValue(26);
//short类型
short s = 56;
register.setValue(s);
//byte[]类型
byte[] bytes = new byte[] {1, 1};
register.setValue(bytes);
//输入寄存器(AI)
InputRegister inputRegister = spi.getInputRegister(registerAddress);
//只能读值,不能写值
int readInputRegisterInt = inputRegister.getValue();//int类型
int readInputRegisterUnsignedShort = inputRegister.toUnsignedShort();//无符号整型
short readInputRegisterShort = inputRegister.toShort();//short类型
byte[] readInputRegisterBytes = inputRegister.toBytes();//byte[]类型
由于离散输入寄存器和输入寄存器这两种寄存器都是只读模式,如果想修改寄存器里面的数值,应该怎么办呢?可以按照我下面的办法:
//举个例子,修改第3个输入寄存器里面的数值,离散输入寄存器原理相同
int index = 2;//因为是从0开始,所以寄存器下标为2
int newValue = 28;
SimpleInputRegister simpleInputRegister = new SimpleInputRegister(newValue);
spi.setInputRegister(index, simpleInputRegister);
写到这里,Android端基本就可以实现一个简单的Modbus TCP Slave了。
监听Master发送过来的报文
整个TCP网络处理都在TCPConnectionHandler这个类里面
......
public void run() {
try {
do {
//1. read the request
ModbusRequest request = m_Transport.readRequest();
//System.out.println("Request:" + request.getHexMessage());
ModbusResponse response = null;
//test if Process image exists
if (ModbusCoupler.getReference().getProcessImage() == null) {
response =
request.createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
} else {
response = request.createResponse();
}
/*DEBUG*/
if (Modbus.debug) System.out.println("Request:" + request.getHexMessage());
if (Modbus.debug) System.out.println("Response:" + response.getHexMessage());
//System.out.println("Response:" + response.getHexMessage());
m_Transport.writeMessage(response);
} while (true);
} catch (ModbusIOException ex) {
if (!ex.isEOF()) {
//other troubles, output for debug
ex.printStackTrace();
}
} finally {
try {
m_Connection.close();
} catch (Exception ex) {
//ignore
}
}
}//run
......
重点是上面这一段代码,我举个例子,如果想监听到Master对Slave进行了写值操作,可以这么做:
看过我上一篇文章的同学,对于Modbus TCP报文应该是非常熟悉的,功能码的位置是在MBAP报文头的后面,由于MBAP报文头的长度是固定的,所以可以推算出功能码的下标是21和22。
//获取Master发过来的网络请求
String requestStr = request.getHexMessage();
//写单个保持寄存器
if (requestStr.charAt(22) == '6') {
//TODO 根据业务处理
//例如发送广播,让广播接收者处理
mIntent.putExtra("isWriteMultiple", false);
mIntent.putExtra("request", requestStr);
mContext.sendBroadcast(mIntent);
} else if (requestStr.charAt(21) == '1') {//写多个保持寄存器
//TODO 根据业务处理
//例如发送广播,让广播接收者处理
mIntent.putExtra("isWriteMultiple", true);
mIntent.putExtra("request", requestStr);
mContext.sendBroadcast(mIntent);
}