使用java的modbus4j的Rtu方式获取监测数据

最近要从modbus总线上获取数据,还要用java开发,找了半天,感觉modbus4j在github上评价比较高,尝试了一下,果然很好用,比之前的方法方便很多。在使用过程中也踩了不少坑,记录下来,分享给大家,100%好用,上传了全部代码,东西一点都不缺少。

1、配置串口环境
使用virtual serial port driver模拟串口com1、com2
使用java的modbus4j的Rtu方式获取监测数据_第1张图片
2、配置下位机环境
使用modsim模拟下位设备
deviceid:设备ID
modbus point type:寄存器模式
address:寄存器起始位置
length:寄存器存储长度
使用java的modbus4j的Rtu方式获取监测数据_第2张图片
选择COM1口
使用java的modbus4j的Rtu方式获取监测数据_第3张图片
3、上位机接口使用java的modbus4j实现
modbus4j的SerialPortWrapper接口没有实现类,推荐使用Freedomotic中的三个类实现:
SerialPortWrapperImpl.java
SerialInputStream.java
SerialOutputStream.java

为了大家使用方便,这里直接上源代码
SerialPortWrapperImpl.java

package com.dj.collection;

/**
 *
 * Copyright (c) 2009-2020 Freedomotic Team http://www.freedomotic-iot.com
 *
 * This file is part of Freedomotic
 *
 * This Program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2, or (at your option) any later version.
 *
 * This Program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * Freedomotic; see the file COPYING. If not, see
 * .
 */

import com.serotonin.modbus4j.serial.SerialPortWrapper;
import jssc.SerialPort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import jssc.SerialPortException;

/**
 *
 */
public class SerialPortWrapperImpl implements SerialPortWrapper {

    private static final Logger LOG = LoggerFactory.getLogger(SerialPortWrapperImpl.class);
    private SerialPort port;
    private String commPortId;
    private int baudRate;
    private int dataBits;
    private int stopBits;
    private int parity;
    private int flowControlIn;
    private int flowControlOut;

    public SerialPortWrapperImpl(String commPortId, int baudRate, int dataBits, int stopBits, int parity, int flowControlIn,
                                 int flowControlOut) {

        this.commPortId = commPortId;
        this.baudRate = baudRate;
        this.dataBits = dataBits;
        this.stopBits = stopBits;
        this.parity = parity;
        this.flowControlIn = flowControlIn;
        this.flowControlOut = flowControlOut;

        port = new SerialPort(this.commPortId);

    }

    @Override
    public void close() throws Exception {
        port.closePort();
        //listeners.forEach(PortConnectionListener::closed);
        LOG.debug("Serial port {} closed", port.getPortName());
    }

    @Override
    public void open() {
        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);
        }
    }

    @Override
    public InputStream getInputStream() {
        return new SerialInputStream(port);
    }

    @Override
    public OutputStream getOutputStream() {
        return new SerialOutputStream(port);
    }

    @Override
    public int getBaudRate() {
        return baudRate;
        //return SerialPort.BAUDRATE_9600;
    }

    @Override
    public int getFlowControlIn() {
        return flowControlIn;
        //return SerialPort.FLOWCONTROL_NONE;
    }

    @Override
    public int getFlowControlOut() {
        return flowControlOut;
        //return SerialPort.FLOWCONTROL_NONE;
    }

    @Override
    public int getDataBits() {
        return dataBits;
        //return SerialPort.DATABITS_8;
    }

    @Override
    public int getStopBits() {
        return stopBits;
        //return SerialPort.STOPBITS_1;
    }

    @Override
    public int getParity() {
        return parity;
        //return SerialPort.PARITY_NONE;
    }
}

SerialInputStream.java
126行、127行必须进行如下修改

//            System.arraycopy(readBuf, 0, buf, offset, length);
            System.arraycopy(readBuf, 0, buf, offset, readBuf.length);
package com.dj.collection;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import jssc.SerialPort;

import java.io.IOException;
import java.io.InputStream;

/**
 * Class that wraps a {@link SerialPort} to provide {@link InputStream}
 * functionality. This stream also provides support for performing blocking
 * reads with timeouts.
 * 
* It is instantiated by passing the constructor a {@link SerialPort} instance. * Do not create multiple streams for the same serial port unless you implement * your own synchronization. * * @author Charles Hache * * Attribution: https://github.com/therealchalz/java-simple-serial-connector * */
public class SerialInputStream extends InputStream { private SerialPort serialPort; private int defaultTimeout = 0; /** * Instantiates a SerialInputStream for the given {@link SerialPort} Do not * create multiple streams for the same serial port unless you implement * your own synchronization. * * @param sp The serial port to stream. */ public SerialInputStream(SerialPort sp) { serialPort = sp; } /** * Set the default timeout (ms) of this SerialInputStream. This affects * subsequent calls to {@link #read()}, {@link #blockingRead(int[])}, and * {@link #blockingRead(int[], int, int)} The default timeout can be 'unset' * by setting it to 0. * * @param time The timeout in milliseconds. */ public void setTimeout(int time) { defaultTimeout = time; } /** * Reads the next byte from the port. If the timeout of this stream has been * set, then this method blocks until data is available or until the timeout * has been hit. If the timeout is not set or has been set to 0, then this * method blocks indefinitely. */ @Override public int read() throws IOException { return read(defaultTimeout); } /** * The same contract as {@link #read()}, except overrides this stream's * default timeout with the given timeout in milliseconds. * * @param timeout The timeout in milliseconds. * @return The read byte. * @throws IOException On serial port error or timeout */ public int read(int timeout) throws IOException { byte[] buf = new byte[1]; try { if (timeout > 0) { buf = serialPort.readBytes(1, timeout); } else { buf = serialPort.readBytes(1); } return buf[0]; } catch (Exception e) { throw new IOException(e); } } /** * Non-blocking read of up to buf.length bytes from the stream. This call * behaves as read(buf, 0, buf.length) would. * * @param buf The buffer to fill. * @return The number of bytes read, which can be 0. * @throws IOException on error. */ @Override public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } /** * Non-blocking read of up to length bytes from the stream. This method * returns what is immediately available in the input buffer. * * @param buf The buffer to fill. * @param offset The offset into the buffer to start copying data. * @param length The maximum number of bytes to read. * @return The actual number of bytes read, which can be 0. * @throws IOException on error. */ @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, length); System.arraycopy(readBuf, 0, buf, offset, readBuf.length); return readBuf.length; } catch (Exception e) { throw new IOException(e); } } /** * Blocks until buf.length bytes are read, an error occurs, or the default * timeout is hit (if specified). This behaves as blockingRead(buf, 0, * buf.length) would. * * @param buf The buffer to fill with data. * @return The number of bytes read. * @throws IOException On error or timeout. */ public int blockingRead(byte[] buf) throws IOException { return blockingRead(buf, 0, buf.length, defaultTimeout); } /** * The same contract as {@link #blockingRead(byte[])} except overrides this * stream's default timeout with the given one. * * @param buf The buffer to fill. * @param timeout The timeout in milliseconds. * @return The number of bytes read. * @throws IOException On error or timeout. */ public int blockingRead(byte[] buf, int timeout) throws IOException { return blockingRead(buf, 0, buf.length, timeout); } /** * Blocks until length bytes are read, an error occurs, or the default * timeout is hit (if specified). Saves the data into the given buffer at * the specified offset. If the stream's timeout is not set, behaves as * {@link #read(byte[], int, int)} would. * * @param buf The buffer to fill. * @param offset The offset in buffer to save the data. * @param length The number of bytes to read. * @return the number of bytes read. * @throws IOException on error or timeout. */ public int blockingRead(byte[] buf, int offset, int length) throws IOException { return blockingRead(buf, offset, length, defaultTimeout); } /** * The same contract as {@link #blockingRead(byte[], int, int)} except * overrides this stream's default timeout with the given one. * * @param buf The buffer to fill. * @param offset Offset in the buffer to start saving data. * @param length The number of bytes to read. * @param timeout The timeout in milliseconds. * @return The number of bytes read. * @throws IOException On error or timeout. */ public int blockingRead(byte[] buf, int offset, int length, int timeout) throws IOException { if (buf.length < offset + length) { throw new IOException("Not enough buffer space for serial data"); } if (timeout < 1) { return read(buf, offset, length); } try { byte[] readBuf = serialPort.readBytes(length, timeout); System.arraycopy(readBuf, 0, buf, offset, length); return readBuf.length; } catch (Exception e) { throw new IOException(e); } } @Override public int available() throws IOException { int ret; try { ret = serialPort.getInputBufferBytesCount(); if (ret >= 0) { return ret; } throw new IOException("Error checking available bytes from the serial port."); } catch (Exception e) { throw new IOException("Error checking available bytes from the serial port."); } } }

SerialOutputStream.java

package com.dj.collection;

/**
 *
 * Copyright (c) 2009-2020 Freedomotic Team http://www.freedomotic-iot.com
 *
 * This file is part of Freedomotic
 *
 * This Program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2, or (at your option) any later version.
 *
 * This Program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * Freedomotic; see the file COPYING. If not, see
 * .
 */

import jssc.SerialPort;
import jssc.SerialPortException;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Class that wraps a {@link SerialPort} to provide {@link OutputStream}
 * functionality.
 * 
* It is instantiated by passing the constructor a {@link SerialPort} instance. * Do not create multiple streams for the same serial port unless you implement * your own synchronization. * * @author Charles Hache * * Attribution: https://github.com/therealchalz/java-simple-serial-connector * */
public class SerialOutputStream extends OutputStream { SerialPort serialPort; /** * Instantiates a SerialOutputStream for the given {@link SerialPort} Do not * create multiple streams for the same serial port unless you implement * your own synchronization. * * @param sp The serial port to stream. */ public SerialOutputStream(SerialPort sp) { serialPort = sp; } @Override public void write(int b) throws IOException { try { serialPort.writeInt(b); } catch (SerialPortException e) { throw new IOException(e); } } @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(byte[] b, int off, int len) throws IOException { byte[] buffer = new byte[len]; System.arraycopy(b, off, buffer, 0, len); try { serialPort.writeBytes(buffer); } catch (SerialPortException e) { throw new IOException(e); } } }

4、pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>data-collection</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <!-- 若想引用modbus4j需要引入下列repository id:ias-snapshots id:ias-releases 两个 -->
    <repositories>
        <repository>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <id>ias-snapshots</id>
            <name>Infinite Automation Snapshot Repository</name>
            <url>https://maven.mangoautomation.net/repository/ias-snapshot/</url>
        </repository>
        <repository>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>ias-releases</id>
            <name>Infinite Automation Release Repository</name>
            <url>https://maven.mangoautomation.net/repository/ias-release/</url>
        </repository>
    </repositories>
    <dependencies>
        <!-- 串口通信  begin-->
        <!-- https://mvnrepository.com/artifact/org.rxtx/rxtx -->
        <dependency>
            <groupId>org.rxtx</groupId>
            <artifactId>rxtx</artifactId>
            <version>2.1.7</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.scream3r/jssc -->
        <dependency>
            <groupId>org.scream3r</groupId>
            <artifactId>jssc</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>com.infiniteautomation</groupId>
            <artifactId>modbus4j</artifactId>
            <version>3.0.3</version>
        </dependency>
        <!-- 串口通信  end-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
</project>

5、主调用程序

package com.dj.collection;

import java.util.Arrays;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import com.serotonin.modbus4j.serial.SerialPortWrapper;

/**
 * 通过串口解析MODBUS协议
 * @author yaohj
 */
public class CollectionMain {
    // 设定MODBUS网络上从站地址
    private final static int SLAVE_ADDRESS = 1;
    //串行波特率
    private final static int BAUD_RATE = 9600;

    public static void main(String[] args) {
        SerialPortWrapper serialParameters = new
                SerialPortWrapperImpl("COM2", BAUD_RATE, 8, 1, 0, 0, 0);
        /* 创建ModbusFactory工厂实例 */
        ModbusFactory modbusFactory = new ModbusFactory();
        /* 创建ModbusMaster实例 */
        ModbusMaster master = modbusFactory.createRtuMaster(serialParameters);
        /* 初始化 */
        try {
            master.init();
            readHoldingRegistersTest(master, SLAVE_ADDRESS, 99, 24);
        } catch (ModbusInitException e) {
            e.printStackTrace();
        } finally {
            master.destroy();
        }
    }

    /**
     * 读保持寄存器上的内容
     * @param master 主站
     * @param slaveId 从站地址
     * @param start 起始地址的偏移量
     * @param len 待读寄存器的个数
     */
    private 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()));
                short[] list = response.getShortData();
                for (int i = 0; i < list.length; i++) {
                    System.out.print(list[i] + " ");
                }
            }
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }
}

6、执行结果
使用java的modbus4j的Rtu方式获取监测数据_第4张图片
正确的取回了值,使用起来相当方便

你可能感兴趣的:(后端相关)