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 \'Android开发/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,这里需要设置为falseModbusCoupler.getReference().setUnitID(1);//从站地址
- 创建ModbusTCPListener,监听数据交换
/*** 网络操作相关的子线程*/Runnable networkTask = new Runnable() {@Overridepublic 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开始,所以寄存器下标为2int 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 requestModbusRequest request = m_Transport.readRequest();//System.out.println(\"Request:\" + request.getHexMessage());ModbusResponse response = null;//test if Process image existsif (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 debugex.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);}
写到最后感谢这个库的作者。GitHub地址