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);
}
寫到最後感謝這個庫的作者。GitHub地址