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地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章