Modbus在Android上的应用之Modbus TCP Slave

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" />
  1. 使用ModbusTCP这个库,添加依赖
    在工程的build.gradle添加
allprojects {
    repositories {
        ......
        maven { url 'https://jitpack.io' }
    }
}
  1. 在app/build.gradle添加
implementation 'com.github.hwx95:ModbusTCP:v1.1'
  1. 准备过程映像
SimpleProcessImage spi = new SimpleProcessImage();
  1. 添加寄存器
        //线圈寄存器(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));
        
  1. 创建耦合器

        ModbusCoupler.getReference().setProcessImage(spi);
        ModbusCoupler.getReference().setMaster(false);//默认是true,这里需要设置为false
        ModbusCoupler.getReference().setUnitID(1);//从站地址

  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();
  1. 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);
          }
          

写到最后感谢这个库的作者。GitHub地址

原创文章 19 获赞 26 访问量 9211

猜你喜欢

转载自blog.csdn.net/qq_36270361/article/details/105552262
今日推荐