目录
前言
开发环境
工具准备
具体实现
下载Modbus4j
解决空指针异常
解决数组越界
测试
测试环境准备
正式测试
之前提到过 由于项目需求,需要封装 ModBus协议,ModBus协议较早,网上开源开源库也不少,可参见 Modbus 史上最全实例资料汇总。安卓上支持ModBus-RTU的库包较为稀缺,毕竟一般安卓手机不会带个串口。所幸运 Android 是一个大的框架,因而我想到了两种思路:
从底层出发,使用 C/C++ 或 Python 的开源库,通过 JNI 为应用层提供调用。
在应用层移植现有ModBus-java协议库,再通过修改协议的传输层将串行通讯修改为 BLE无线传输。
本文采用的是第二种方法,使用的库是 ModBus4j(点击跳转至下载地址),在 Java 平台调试后再移植到Android,随和修改数据传输方式,在此之前会梳理开源库包的实现,如有必要对自行搭轮子也有不小的帮助。
- VSCode1.39.2
- JDK1.8.0_221
- JRE1.8.0_221
工欲善其事必先利其器。 --- 不是我说的
使用 Modbus4j 前我们需要准备以下工具以便调试
Modbus Poll(模拟ModBus主站)&& Modbus Slave(模拟ModBus从站)
下载地址
Virtual Serial Port Driver Pro(虚拟串口)
下载地址
安装好工具,我一般会先 玩会 测试一下,使用方法见:
modbus slave 和 modbus poll 使用说明
Modbus 测试工具 ModbusPoll 与 Modbus Slave 使用方法
下载 ModBus4j ,并用VSCode 打开
运行 MasterTest.java (这里修改了一下,故而贴出)
package com.serotonin.modbus4j.test;
import java.util.Arrays;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ModbusTransportException;
//import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.ReadCoilsRequest;
import com.serotonin.modbus4j.msg.ReadCoilsResponse;
import com.serotonin.modbus4j.msg.ReadDiscreteInputsRequest;
import com.serotonin.modbus4j.msg.ReadDiscreteInputsResponse;
import com.serotonin.modbus4j.msg.ReadExceptionStatusRequest;
import com.serotonin.modbus4j.msg.ReadExceptionStatusResponse;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import com.serotonin.modbus4j.msg.ReadInputRegistersRequest;
import com.serotonin.modbus4j.msg.ReadInputRegistersResponse;
import com.serotonin.modbus4j.msg.ReportSlaveIdRequest;
import com.serotonin.modbus4j.msg.ReportSlaveIdResponse;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import com.serotonin.modbus4j.msg.WriteCoilsRequest;
import com.serotonin.modbus4j.msg.WriteCoilsResponse;
import com.serotonin.modbus4j.msg.WriteMaskRegisterRequest;
import com.serotonin.modbus4j.msg.WriteMaskRegisterResponse;
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
import com.serotonin.modbus4j.msg.WriteRegisterResponse;
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
import com.serotonin.modbus4j.msg.WriteRegistersResponse;
public class MasterTest {
public static void main(String[] args) throws Exception {
String commPortId = "COM1";
int baudRate = 9600;
int flowControlIn = 0;
int flowControlOut = 0;
int dataBits = 8;
int stopBits = 1;
int parity = 0;
TestSerialPortWrapper wrapper = new TestSerialPortWrapper(commPortId, baudRate, flowControlIn, flowControlOut, dataBits, stopBits, parity);
//IpParameters ipParameters = new IpParameters();
//ipParameters.setHost("localhost");
ModbusFactory modbusFactory = new ModbusFactory();
ModbusMaster master = modbusFactory.createRtuMaster(wrapper);
// ModbusMaster master = modbusFactory.createAsciiMaster(wrapper);
//ModbusMaster master = modbusFactory.createTcpMaster(ipParameters, false);
// ModbusMaster master = modbusFactory.createUdpMaster(ipParameters);
try {
master.init();
int slaveId = 1;
// readCoilTest(master, slaveId, 0, 10);
// readCoilTest(master, slaveId, 99, 200);
// readDiscreteInputTest(master, slaveId, 1, 10);
// readDiscreteInputTest(master, slaveId, 449, 72);
/*
//This is Success
//读取保持寄存器
readHoldingRegistersTest(master, slaveId, 9, 125);
*/
// readHoldingRegistersTest(master, slaveId, 9, 120);
// readInputRegistersTest(master, slaveId, 0, 1);
// readInputRegistersTest(master, slaveId, 14, 8);
// writeCoilTest(master, slaveId, 1, true);
// writeCoilTest(master, slaveId, 110, true);
/*
//This is Success
//写单个寄存器
writeRegisterTest(master, slaveId, 0, 1);
*/
// writeRegisterTest(master, slaveId, 14, 12345);
// readExceptionStatusTest(master, slaveId);
// reportSlaveIdTest(master, slaveId);
// writeCoilsTest(master, slaveId, 50, new boolean[] {true, false, false, true, false});
// writeCoilsTest(master, slaveId, 115, new boolean[] {true, false, false, true, false});
//This is Success
//写多个寄存器
writeRegistersTest(master, slaveId, 300, new short[] {1, 10, 100, 1000, 10000, (short)65535});
// writeRegistersTest(master, slaveId, 21, new short[] {1, 10, 100, 1000, 10000, (short)65535});
//This is Success
// writeMaskRegisterTest(master, slaveId, 26, 0xf2, 0x25);
// readCoilTest(master, slaveId, 9, 5);
// readCoilTest(master, slaveId, 10, 5);
// readDiscreteInputTest(master, slaveId, 10, 6);
// readDiscreteInputTest(master, slaveId, 10, 5);
// readHoldingRegistersTest(master, slaveId, 9, 7);
// readHoldingRegistersTest(master, slaveId, 10, 5);
// readInputRegistersTest(master, slaveId, 0, 1);
// readInputRegistersTest(master, slaveId, 10, 5);
// writeCoilTest(master, slaveId, 8, true);
// writeCoilTest(master, slaveId, 11, true);
// writeRegisterTest(master, slaveId, 1, 1);
// writeRegisterTest(master, slaveId, 14, 12345);
// readExceptionStatusTest(master, slaveId);
// reportSlaveIdTest(master, slaveId);
// writeCoilsTest(master, slaveId, 11, new boolean[] {false, true, false, false, true});
// writeCoilsTest(master, slaveId, 10, new boolean[] {false, true, false, false, true});
// writeRegistersTest(master, slaveId, 11, new short[] {(short)65535, 1000, 100, 10, 1});
// writeRegistersTest(master, slaveId, 10, new short[] {(short)65535, 1000, 100, 10, 1});
// writeMaskRegisterTest(master, slaveId, 9, 0xf2, 0x25);
// writeMaskRegisterTest(master, slaveId, 10, 0xf2, 0x25);
// Automatic WriteMaskRegister failover test
// ModbusLocator locator = new ModbusLocator(slaveId, RegisterRange.HOLDING_REGISTER, 15, (byte)2);
// System.out.println(master.getValue(locator));
// master.setValue(locator, true);
// System.out.println(master.getValue(locator));
// master.setValue(locator, false);
// System.out.println(master.getValue(locator));
// BatchRead batch = new BatchRead();
// batch.addLocator("hr1", new ModbusLocator(31, RegisterRange.HOLDING_REGISTER, 80,
// DataType.TWO_BYTE_BCD));
// batch.addLocator("hr2", new ModbusLocator(31, RegisterRange.HOLDING_REGISTER, 81,
// DataType.FOUR_BYTE_BCD));
// BatchResults results = master.send(batch);
// System.out.println(results.getValue("hr1"));
// System.out.println(results.getValue("hr2"));
// This's Successful Way to set Reg Data
// BaseLocator locator = BaseLocator.holdingRegister(slaveId, 10, DataType.EIGHT_BYTE_INT_UNSIGNED);
// master.setValue(locator, 10000000);
// System.out.println(master.getValue(locator));
}
finally {
master.destroy();
}
}
public static void readCoilTest(ModbusMaster master, int slaveId, int start, int len) {
try {
ReadCoilsRequest request = new ReadCoilsRequest(slaveId, start, len);
ReadCoilsResponse response = (ReadCoilsResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println(Arrays.toString(response.getBooleanData()));
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void readDiscreteInputTest(ModbusMaster master, int slaveId, int start, int len) {
try {
ReadDiscreteInputsRequest request = new ReadDiscreteInputsRequest(slaveId, start, len);
ReadDiscreteInputsResponse response = (ReadDiscreteInputsResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println(Arrays.toString(response.getBooleanData()));
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void readHoldingRegistersTest(ModbusMaster master, int slaveId, int start, int len) {
try {
ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);
ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println(Arrays.toString(response.getShortData()));
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void readInputRegistersTest(ModbusMaster master, int slaveId, int start, int len) {
try {
ReadInputRegistersRequest request = new ReadInputRegistersRequest(slaveId, start, len);
ReadInputRegistersResponse response = (ReadInputRegistersResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println(Arrays.toString(response.getShortData()));
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void writeCoilTest(ModbusMaster master, int slaveId, int offset, boolean value) {
try {
WriteCoilRequest request = new WriteCoilRequest(slaveId, offset, value);
WriteCoilResponse response = (WriteCoilResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println("Success");
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void writeRegisterTest(ModbusMaster master, int slaveId, int offset, int value) {
try {
WriteRegisterRequest request = new WriteRegisterRequest(slaveId, offset, value);
WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println("Success");
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void readExceptionStatusTest(ModbusMaster master, int slaveId) {
try {
ReadExceptionStatusRequest request = new ReadExceptionStatusRequest(slaveId);
ReadExceptionStatusResponse response = (ReadExceptionStatusResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println(response.getExceptionStatus());
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void reportSlaveIdTest(ModbusMaster master, int slaveId) {
try {
ReportSlaveIdRequest request = new ReportSlaveIdRequest(slaveId);
ReportSlaveIdResponse response = (ReportSlaveIdResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println(Arrays.toString(response.getData()));
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void writeCoilsTest(ModbusMaster master, int slaveId, int start, boolean[] values) {
try {
WriteCoilsRequest request = new WriteCoilsRequest(slaveId, start, values);
WriteCoilsResponse response = (WriteCoilsResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println("Success");
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void writeRegistersTest(ModbusMaster master, int slaveId, int start, short[] values) {
try {
WriteRegistersRequest request = new WriteRegistersRequest(slaveId, start, values);
WriteRegistersResponse response = (WriteRegistersResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println("Success");
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
public static void writeMaskRegisterTest(ModbusMaster master, int slaveId, int offset, int and, int or) {
try {
WriteMaskRegisterRequest request = new WriteMaskRegisterRequest(slaveId, offset, and, or);
WriteMaskRegisterResponse response = (WriteMaskRegisterResponse) master.send(request);
if (response.isException())
System.out.println("Exception response: message=" + response.getExceptionMessage());
else
System.out.println("Success");
}
catch (ModbusTransportException e) {
e.printStackTrace();
}
}
}
运行结果:空指针异常
这是由于 ModBus4J 并没有给我们提供底层串口驱动
落后的解决方法:
- 使用 Sum 基本放弃的 javacomm
最常见的解决方法:
- 使用 RXTXcomm.jar
受到前辈启发:
modbus4j初次使用总结(该解决方案主要参照了 Freedomotic Open IoT Framework 开源框架)
- 使用 Jssc.jar
如果你不会导入库包,请前往 Visual Studio Code 手动导入 jar 包
导入库包后,我们去实现 TestSerialPortWrapper.java
/**
* Copyright (C) 2015 Infinite Automation Software. All rights reserved.
* @author Terry Packer
*/
package com.serotonin.modbus4j.test;
import com.serotonin.modbus4j.serial.SerialPortWrapper;
import jssc.SerialPort;
import java.io.InputStream;
import java.io.OutputStream;
import jssc.SerialPortException;
//The project cannot be built until build path errors are resolved
/**
*
* This class is not finished
*
* @author Terry Packer
*
*/
public class TestSerialPortWrapper implements SerialPortWrapper{
private SerialPort port;
private String commPortId;
private int baudRate;
private int flowControlIn;
private int flowControlOut;
private int dataBits;
private int stopBits;
private int parity;
public TestSerialPortWrapper(String commPortId, int baudRate, int flowControlIn,
int flowControlOut, int dataBits, int stopBits, int parity){
this.commPortId = commPortId;
this.baudRate = baudRate;
this.flowControlIn = flowControlIn;
this.flowControlOut = flowControlOut;
this.dataBits = dataBits;
this.stopBits = stopBits;
this.parity = parity;
port = new SerialPort(this.commPortId);
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#close()
*/
@Override
public void close() throws Exception {
port.closePort();
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#open()
*/
@Override
public void open() throws Exception {
try {
port.openPort();
port.setParams(this.getBaudRate(), this.getDataBits(), this.getStopBits(), this.getParity());
port.setFlowControlMode(this.getFlowControlIn() | this.getFlowControlOut());
//listeners.forEach(PortConnectionListener::opened);
//LOG.debug("Serial port {} opened", port.getPortName());
} catch (SerialPortException ex) {
//LOG.error("Error opening port : {} for {} ", port.getPortName(), ex);
}
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getInputStream()
*/
@Override
public InputStream getInputStream() {
// TODO Auto-generated method stub
return new SerialInputStream(port);
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getOutputStream()
*/
@Override
public OutputStream getOutputStream() {
// TODO Auto-generated method stub
return new SerialOutputStream(port);
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getBaudRate()
*/
@Override
public int getBaudRate() {
// TODO Auto-generated method stub
return baudRate;
}
public int getFlowControlIn() {
return flowControlIn;
//return SerialPort.FLOWCONTROL_NONE;
}
public int getFlowControlOut() {
return flowControlOut;
//return SerialPort.FLOWCONTROL_NONE;
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getStopBits()
*/
@Override
public int getStopBits() {
// TODO Auto-generated method stub
return stopBits;
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getParity()
*/
@Override
public int getParity() {
// TODO Auto-generated method stub
return parity;
}
/* (non-Javadoc)
* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getDataBits()
*/
@Override
public int getDataBits() {
// TODO Auto-generated method stub
return dataBits;
}
}
此外我们还需要将下图两个文件添加到我们的项目内
这时再运行程序还会出现错误:数组越界错误
修改 SerialInputStream.java 中的 read( )
@Override
public int read(byte[] buf, int offset, int length) throws IOException {
if (buf.length < offset + length) {
length = buf.length - offset;
}
int available = this.available();
if (available > length) {
available = length;
}
try {
byte[] readBuf = serialPort.readBytes(available);
System.arraycopy(readBuf, 0, buf, offset, readBuf.length);
return readBuf.length;
} catch (Exception e) {
throw new IOException(e);
}
}
修改 SerialOutputStream.java 中的 write( )
@Override
public void write(byte[] b, int off, int len) throws IOException {
byte[] buffer = new byte[len];
System.arraycopy(b, off, buffer, 0, b.length);
try {
serialPort.writeBytes(buffer);
} catch (SerialPortException e) {
throw new IOException(e);
}
}
虚拟串口:建立 COM1 和 COM2 虚拟连接
添加完成后可在设备管理器中查看
ModBus Slave
F8 配置
F3配置
读多个保持寄存器
写多个保持寄存器
注意到,最后一个寄存器写 65535 却显示 -1
这是由于显示格式导致的,我们可在 Display 中设置为其他显示格式,例如十六进制Hex
若想查看详细的数据,可在Modbus Slave 中点击工具栏的 Display--Communication Traffic 查看详细的读写信息
以文中写多个寄存器为例
000002-Rx(写寄存器请求)
01(Addr)10(Cmd) 00 00 (Reg Addr)00 06(Number) 0C(不知道作用,是 Number*2 ) 00 01(data1:1) 00 0A (data2:10)00 64 (data3:100)03 E8(data4:1000) 27 10(data5:10000) FF FF (data6:65535)3F A8 (CRC)
000003-Tx(应答信号)
01 (Addr)10 (Cmd)00 00(Reg Addr) 00 06(Number) 40 0B (CRC)