hey-girl东拼西凑原创文章,若有歧义可留言,若需转载需标明出处

前言:之前文章讲述了关于modbus协议的内容。这章主要说说结合modbus4j来使用。首先要明白,本文是用modbus4j实现主设备(客户端)功能。在看这章前请务必知晓modbus相关知识。不然很难解决突发问题。

为什么选择modbus4j: 刚刚接触,暂时只发现这个star比较多。地址modbus4j。说实话。我并不知道这个的文档地址在哪里。要是有知道的兄弟,欢迎留言。

思路: 第一打算先从modbus4j源码的测试代码入手,和slave工具进行通讯 。等通讯成功后,接着在创建自己的项目,引入modbus4j。进行开发。

重点:idea 2020.3.4 、jdk 1.8.0_201、Virtual Serial Port Driver 6.9、Modbus Slave

MouBus4j源码测试RTU模式连接slave工具

需求就是:读从设备地址1,保持寄存器地址40000-40004共5个寄存器的内容

  • 下载源码

接下来做第一部分工作,下载源码地址modbus4j。然后使用idea打开。就是个maven项目。项目结构如下:
【ModBus】modbus之modbus4j的使用(4)-编程之家

  • 打开MasterTest.java(文件在src_test 下的 com.serotonin.modbus4j.test包中),会发现这个测试类中写了很多测试代码。根据我们需求,我们需要修改这个类中的部分数据。(这里我只贴用到的代码。不相关的删除)
package com.serotonin.modbus4j.test;
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 = 2;/*** 校验位*/int parity = 0;/*** 串口包装器*/TestSerialPortWrapper wrapper = new TestSerialPortWrapper(commPortId, baudRate, flowControlIn, flowControlOut, dataBits, stopBits, parity);ModbusFactory modbusFactory = new ModbusFactory();// rtu模式ModbusMaster master = modbusFactory.createRtuMaster(wrapper);try {master.init();/*** 从机地址*/int slaveId = 1;readHoldingRegistersTest(master, slaveId, 0, 5);}finally {master.destroy();}}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());elseSystem.out.println(Arrays.toString(response.getShortData()));}catch (ModbusTransportException e) {e.printStackTrace();}}   
}

上述代码,主要看看readHoldingRegistersTest(master, slaveId, 0, 5); 参数0是保持寄存器的起始地址,5是寄存器数。也就是读几个。

代码部分改完以后,点击运行的话。会报错空指针。为啥有这个报错是因为SerialPortWrapper这个接口。包装器,进一步帮助从串行端口实现抽象Modbus4J
【ModBus】modbus之modbus4j的使用(4)-编程之家
modbus4j没有提供底层串口驱动。解决办法有好几种。参考modbus4j使用。我选择使用 Freedomotic Open IoT Framework 开源框架Freedomotic 。
首先添加jssc的jar包jssc。因为下面的代码依赖了这个包。所以必须。我直接接pom导入的方式

    <!-- https://mvnrepository.com/artifact/org.scream3r/jssc --><dependency><groupId>org.scream3r</groupId><artifactId>jssc</artifactId><version>2.8.0</version></dependency>

在moubus4j的源码中移入freedomotic项目
【ModBus】modbus之modbus4j的使用(4)-编程之家
【ModBus】modbus之modbus4j的使用(4)-编程之家
移入以后,(不修改会报错数组越界)并修改 SerialInputStream.java 中的 read( )方法。如下图红圈地方修改
【ModBus】modbus之modbus4j的使用(4)-编程之家
修改SerialOutputStream.java 的write()方法。
【ModBus】modbus之modbus4j的使用(4)-编程之家
在修改TestSerialPortWrapper.java。

/*** Copyright (C) 2015 Infinite Automation Software. All rights reserved.* @author Terry Packer*/
package com.serotonin.modbus4j.test;
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()*/@Overridepublic void close() throws Exception {// TODO Auto-generated method stubport.closePort();}/* (non-Javadoc)* @see com.serotonin.modbus4j.serial.SerialPortWrapper#open()*/@Overridepublic void open() throws Exception {// TODO Auto-generated method stubtry {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);}}/* (non-Javadoc)* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getInputStream()*/@Overridepublic InputStream getInputStream() {// TODO Auto-generated method stubreturn new SerialInputStream(port);}/* (non-Javadoc)* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getOutputStream()*/@Overridepublic OutputStream getOutputStream() {// TODO Auto-generated method stubreturn new SerialOutputStream(port);}/* (non-Javadoc)* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getBaudRate()*/@Overridepublic int getBaudRate() {// TODO Auto-generated method stubreturn 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()*/@Overridepublic int getStopBits() {// TODO Auto-generated method stubreturn stopBits;}/* (non-Javadoc)* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getParity()*/@Overridepublic int getParity() {// TODO Auto-generated method stubreturn parity;}/* (non-Javadoc)* @see com.serotonin.modbus4j.serial.SerialPortWrapper#getDataBits()*/@Overridepublic int getDataBits() {// TODO Auto-generated method stubreturn dataBits;}}

至此。代码部分改动结束。

  • 虚拟com口
    这个软件之前文章有说过,因为是RTU模式。虚拟端口必不可少【ModBus】modbus之modbus4j的使用(4)-编程之家

  • modbus slave工具
    之前文章已经详细介绍了这个参数设置。不懂的可以回去看看。这里配置只需记住一点。和发的消息帧对的上,串口参数设置一样就行。
    【ModBus】modbus之modbus4j的使用(4)-编程之家
    【ModBus】modbus之modbus4j的使用(4)-编程之家
    这些和上面代码一致就可。都设置好以后ok就行。会发现从机变成如下:红红的未连接的提示没有了哈。这样就可以了。
    【ModBus】modbus之modbus4j的使用(4)-编程之家

  • 测试
    根据需求测试。我们需求是读从设备地址1,保持寄存器地址40000-40004共5个寄存器的内容。
    运行MasterTest。这时可能会有个报错。

A fatal error has been detected by the Java Runtime Environment: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000007110b5db, pid=19740, tid=0x000000000000664c # # JRE version: Java™ SE Runtime Environment (8.0_261-b12) (build 1.8.0_261-b12) # Java VM: Java HotSpot™ 64-Bit Server VM (25.261-b12 mixed mode windows-amd64 compressed oops) # Problematic frame: # C [jSSC-2.8_x86_64.dll+0xb5db] # # Failed to write core dump. Minidumps are not enabled by default on client versions of Windows # # An error report file with more information is saved as: # D:360DownloadsSkeltonhs_err_pid19740.log # # If you would like to submit a bug report, please visit: # http://bugreport.java.com/bugreport/crash.jsp # The crash happened outside the Java Virtual Machine in native code. # See problematic frame for where to report the bug.

出现这个错误,主要看看JRE version: OpenJDK Runtime Environment JBR-11.0.10.8-1145.96-jcef (11.0.10+8) (build 11.0.10+8-b1145.96)和jSSC-2.8_x86_64.dll+0xb5db。就是版本不兼容。解决办法就是下调jdk版本。我这里用1.8.0_201的的就不报错。
正常运行MasterTest,会在控制台输出[0, 0, 22, 0, 0](类似这样的值)。分析下收发的报文。
【ModBus】modbus之modbus4j的使用(4)-编程之家
Rx:000012-01 03 00 00 00 05 85 C9 这就主设备发的(这里理解我们程序发的)
Rx:Receive(rx) Data 接收
000012:序列码
01:从机地址
03:功能码 读保持寄存器
00 00 :起始地址高位 、低位
00 05: 寄存器数高位 、低位
85 C9:校验码
合起来不就是我们的需求么。读寄存器地址0-4 。共5个(不规范解释哈)

Tx:000013-01 03 0A 00 00 00 00 00 16 00 00 00 00 6D 75从设备发的(也就是slave工具)
TX: Transmit(tx) Data 发送
000013:序列码
01:从机地址
03:功能码 读保持寄存器
0A :数据长度(十进制就是10).就是说下面10个字节的数据
00 00 00 00 00 16 00 00 00 00: 数据。保持寄存器的单位是word(等于2byte).依次表达的就是数据1的高位和低位一直到数据5。这里的16(十进制是22).所以程序输出[0, 0, 22, 0, 0]
6D 75:校验码

如果程序没有输出这结果,而是报错。请根据发送和接收的报文分析错误。

上面其实就是最简单的一个通讯demo。那说到这里了。作为内卷行业。不整点原理解析下不合适。

demo分析

串口:串行通讯端口( cluster communication port )即COM口,简称串口。按电气标准及协议来分包括RS-232-C、RS-422、RS485、USB等。作为个小仙女我就理解这是电脑上插线的口子。
驱动:参考什么是驱动。我强行给理解了一波。就是比如有个硬件和电脑连接上了。电脑肯定不知道你是个什么玩意。驱动就负责初始化,配置硬件。负责数据的接受和发送,或者叫读取和写入。中间商的感觉。

言归正传,上面的demo或者说我们平时测试poll和slave工具进行主从通讯的时候。(没有真实硬件设备的情况)我们都是使用工具虚拟的COM。想想需求不都是这样么。我不给你设备。但是你得完成需求。然我们光有虚拟的COM不行。那java如何连接COM?modbus4j没有提供底层串口驱动。网上的方式一般比较常见的解决方案。一种使用rxtx库。一种是jssc。也就是上面demo写的。自己去实现SerialPortWrapper。

好的。我们结合demo看看源码。
首先是定义了些串口通讯参数。这是通讯必不可少的。而且这些值必须和从设备保持一致。如下:

    	String commPortId = "COM1"; // 串口int baudRate = 9600; // 波特率int flowControlIn = 0; //int flowControlOut = 0;int dataBits = 8; // 数据位int stopBits = 2; // 停止位int parity = 0; // 校验位

在看看下面代码TestSerialPortWrapper其实是实现了SerialPortWrapper接口。SerialPortWrapper的实现在modbus4j没有写的。所以需要我们自己写个串口实现。也就是上面demo中我们引入了jssc来实现的。

TestSerialPortWrapper wrapper = new TestSerialPortWrapper(commPortId, baudRate, flowControlIn, flowControlOut, dataBits, stopBits, parity);

经过上面,我们已经实现了串口驱动。接着往下。modbus工厂类.这个类给我们提供了RTU/ASCII/TCP/UDP这几种传输模式的构建。可以构建主端或者从端。比如创建RTU,这里就调用了RtuMaster。至于代码中发送消息部分,请接着看下面项目实际使用的时候,进行分析。

ModbusFactory modbusFactory = new ModbusFactory();
ModbusMaster master = modbusFactory.createRtuMaster(wrapper);

篇幅较长,请看下篇。