目录
之前提到过 由于项目需求,需要封装 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<String> batch = new BatchRead<String>(); // 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<String> 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<Number> 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)
原网址: 访问
创建于: 2023-09-14 18:14:45
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论