初探 ModBus4j -简单使用指南

目录
前言
开发环境
工具准备
具体实现
下载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 下载 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 -简单使用指南
文章图片

解决空指针异常 这是由于 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; }}

此外我们还需要将下图两个文件添加到我们的项目内
初探 ModBus4j -简单使用指南
文章图片

这时再运行程序还会出现错误:数组越界错误
初探 ModBus4j -简单使用指南
文章图片

解决数组越界 修改 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 虚拟连接
初探 ModBus4j -简单使用指南
文章图片

添加完成后可在设备管理器中查看
初探 ModBus4j -简单使用指南
文章图片

ModBus Slave
F8 配置
初探 ModBus4j -简单使用指南
文章图片

F3配置
初探 ModBus4j -简单使用指南
文章图片

正式测试 读多个保持寄存器
初探 ModBus4j -简单使用指南
文章图片

写多个保持寄存器
初探 ModBus4j -简单使用指南
文章图片

初探 ModBus4j -简单使用指南
文章图片

注意到,最后一个寄存器写 65535 却显示 -1
这是由于显示格式导致的,我们可在 Display 中设置为其他显示格式,例如十六进制Hex
初探 ModBus4j -简单使用指南
文章图片

初探 ModBus4j -简单使用指南
文章图片

若想查看详细的数据,可在Modbus Slave 中点击工具栏的 Display--Communication Traffic 查看详细的读写信息
初探 ModBus4j -简单使用指南
文章图片

以文中写多个寄存器为例
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(应答信号)
【初探 ModBus4j -简单使用指南】01 (Addr)10 (Cmd)00 00(Reg Addr) 00 06(Number) 40 0B (CRC)

    推荐阅读