首页 > 基础资料 博客日记

有关Java读取Modbus协议连接PLC的示例(使用modbus4j)

2024-10-04 14:00:07基础资料围观123

Java资料网推荐有关Java读取Modbus协议连接PLC的示例(使用modbus4j)这篇文章给大家,欢迎收藏Java资料网享受知识的乐趣

有关Java读取Modbus协议的Tcp/RTU示例(使用modbus4j)

我最近碰到一个项目,获取数据来方式很多,其中一种便是Modbus协议。这个协议分为Modbus-Tcp和Modbus-RTU两种,我是这么简单理解这个协议的,主要用于信息的采集与下发,而且信息的获取和下发需要对应硬件的物理地址。

下面先讲一下一些实用的知识点,看完之后说不定你就不用开发了。

  • Modbus-TCP
    使用的是RJ45网口通讯,可以在连接网络交互设备通过ip获取
  • Modbus-RTU
    使用485串口通信,只能通过串口连接电脑,

网上也有卖集成485串口的类似交换机的东西,可以去某宝搜索一下。还可以把串口的Modbus协议转换为ModbusTCP协议,并实现双向通信,即可以获取数据,也可以下发指令。甚至可以把数据发送到消息队列中。价格也不是很贵,如果允许买一个能减轻不少工作量,甚至不用再读下去了。但是有些设备可能无法下发,买之前要问清楚。

我使用的modbus服务是下面这款西门子的PLC,开启Modbus-TCP和Modbus-RTU均需要往里面写程序才可以,这部分不是我做的所以我不做阐述。

下面直接上Modbus-TCP和ModbusRTU的数据获取与下发代码,如果有用请点赞哦!

modubus4j Github地址

https://github.com/MangoAutomation/modbus4j

Modbus-TCP

  • Maven依赖
<dependency>
    <groupId>com.infiniteautomation</groupId>
    <artifactId>modbus4j</artifactId>
    <version>3.1.0</version>
</dependency>
  • Java代码
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.locator.StringLocator;
import com.serotonin.modbus4j.msg.*;

public static void main(String[] args) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
	IpParameters ipParameters = new IpParameters();
	ipParameters.setHost("192.168.2.1"); // Modbus-Tcp所在Ip地址
	ipParameters.setPort(502); // 502为默认端口
	ModbusFactory modbusFactory = new ModbusFactory();
	ModbusMaster modbusMaster = modbusFactory.createTcpMaster(ipParameters, true);
	modbusMaster.init();
	// slaveId设备id, 所在位置
	int slaveId = 1, offset = 25;
	// 读保持寄存器
	BaseLocator<Number> holdingRegister = BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);
	Number value = modbusMaster.getValue(holdingRegister);
	System.out.println("value:" + value);
	
	// 写
	int vlaue2 = 210;
	WriteRegisterRequest registerRequest = new WriteRegisterRequest(slaveId, offset, vlaue2);
	WriteRegisterResponse registerResponse = (WriteRegisterResponse) modbusMaster.send(registerRequest);
	System.out.println(!registerResponse.isException());
	
	// 再读看看写进去没
	value = modbusMaster.getValue(holdingRegister);
	System.out.println("value:" + value);
}

PS:

如果你的Modbus-Tcp服务正常,这里你也可能会出现一些问题,这里只指出我碰见的。

  • 问题1: 你拿到的数据不正确或者没有值。

  • 解决方法:
    • 1、Modbus服务中的地址位与你获取的地址位(代码中offset变量)在命名上有所不同,modbus中是以下标1开始的,而代码中是以0开始的所以你需要-1
    • 2、西门子的PLC地址位包涵了寄存器类型的位置,例如保持寄存器是以4开头的,第一位是40001,这时你的offset应该是1,因为modbus4j中是指定寄存器类型的。在源码的RegisterRange类中,已经写给你做了转换
/*
 * ============================================================================
 * GNU General Public License
 * ============================================================================
 *
 * Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
 * @author Matthew Lohbihler
 * 
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.serotonin.modbus4j.code;

/**
 * <p>RegisterRange class.</p>
 *
 * @author Matthew Lohbihler
 * @version 5.0.0
 */
public class RegisterRange {
    /** Constant <code>COIL_STATUS=1</code> */
    public static final int COIL_STATUS = 1;
    /** Constant <code>INPUT_STATUS=2</code> */
    public static final int INPUT_STATUS = 2;
    /** Constant <code>HOLDING_REGISTER=3</code> */
    public static final int HOLDING_REGISTER = 3;
    /** Constant <code>INPUT_REGISTER=4</code> */
    public static final int INPUT_REGISTER = 4;

    /**
     * <p>getFrom.</p>
     *
     * @param id a int.
     * @return a int.
     */
    public static int getFrom(int id) {
        switch (id) {
        case COIL_STATUS:
            return 0;
        case INPUT_STATUS:
            return 0x10000;
        case HOLDING_REGISTER:
            return 0x40000;
        case INPUT_REGISTER:
            return 0x30000;
        }
        return -1;
    }

    /**
     * <p>getTo.</p>
     *
     * @param id a int.
     * @return a int.
     */
    public static int getTo(int id) {
        switch (id) {
        case COIL_STATUS:
            return 0xffff;
        case INPUT_STATUS:
            return 0x1ffff;
        case HOLDING_REGISTER:
            return 0x4ffff;
        case INPUT_REGISTER:
            return 0x3ffff;
        }
        return -1;
    }

    /**
     * <p>getReadFunctionCode.</p>
     *
     * @param id a int.
     * @return a int.
     */
    public static int getReadFunctionCode(int id) {
        switch (id) {
        case COIL_STATUS:
            return FunctionCode.READ_COILS;
        case INPUT_STATUS:
            return FunctionCode.READ_DISCRETE_INPUTS;
        case HOLDING_REGISTER:
            return FunctionCode.READ_HOLDING_REGISTERS;
        case INPUT_REGISTER:
            return FunctionCode.READ_INPUT_REGISTERS;
        }
        return -1;
    }
}

    • 3、检查你下方代码中dataType传入的类型是否与存储的类型一样,如果你不知道可以试试使用不同的类型,看看哪个和你存储的数据相同,然后再改个数据,看看是否正确。
      BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);
    • 4、查看Modbus服务存储数据的寄存器是否与你读取代码中的寄存器类型相同

如果你要获取不同类型的寄存器数据,请点开上方的github中找到源码中的测试类,类面包涵了所有的。查看其他博主发的文章也有很多,我也是抄的,没必要看我的文章,方式也很简单基本相同。

Modbus-RTU

在使用RTU协议时,由于github上的示例代码重要区域注释写着

我只能呵呵了…
由于大家都是BS开发者,谁**写过这玩意啊,这不是2000年就淘汰的玩意吗,我**。
但是我弄好了(快夸我)
直接上代码

Maven依赖

<!-- 串口通信-->
<dependency>
    <groupId>com.fazecast</groupId>
    <artifactId>jSerialComm</artifactId>
    <version>2.9.0</version>
</dependency>
import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortEvent;
import com.serotonin.modbus4j.serial.SerialPortWrapper;

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

public class SerialPortWrapperImpl implements SerialPortWrapper {
    private String commPortId;
    private int baudRate;
    private int flowControlIn;
    private int flowControlOut;
    private int dataBits;
    private int stopBits;
    private int parity;

    private SerialPort serialPort;
    
    public SerialPortWrapperImpl(String commPortId, int baudRate, int flowControlIn,
                                 int flowControlOut, int dataBits, int stopBits, int parity){
        super();
        this.commPortId = commPortId;
        // 波特率
        this.baudRate = baudRate;
        // 输入流timeout时长 不能超过100
        this.flowControlIn = flowControlIn;
        // 输出流timeout时长
        this.flowControlOut = flowControlOut;
        this.dataBits = dataBits;
        this.stopBits = stopBits;
        this.parity = parity;

		SerialPort[] ports = SerialPort.getCommPorts();

        // 打印可用串口的信息
        System.out.println("Available Ports:");
        for (SerialPort port : ports) {
            System.out.println(port.getSystemPortName() + ": " + port.getPortDescription());
            // 判断可用串口存在
            if (port.getSystemPortName().equals(this.commPortId)) {
                this.serialPort = port;
                break;
            }
        }
        
        this.serialPort.setComPortParameters(this.baudRate, this.dataBits, this.stopBits, this.parity);
        this.serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, this.flowControlIn, this.flowControlOut);
        this.serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
		this.serialPort.setComPortParameters(this.baudRate, this.dataBits, this.stopBits, this.parity);
        this.serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, this.flowControlIn, this.flowControlOut);
        this.serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
    }

    @Override
    public void close() throws Exception {
        if (this.serialPort.closePort())
            System.out.println("Port close");
        else
            System.out.println("Prot close export");
    }

	@Override
    public void open() throws Exception {
    	if (serialPort.openPort()) {
            System.out.println("Port opened successfully.");
        } else {
            System.err.println("Unable to open the port.");
            return;
        }
    }

 @Override
    public InputStream getInputStream() {
        InputStream out = null;
        try {
            out = this.serialPort.getInputStream();
        } catch (Exception e) {
            System.out.println("获取输入流异常");
            e.printStackTrace();
        }
        return out;
    }

    @Override
    public OutputStream getOutputStream() {
        OutputStream out = null;
        try {
            out = this.serialPort.getOutputStream();
        } catch (Exception e) {
            System.out.println("获取输出流异常");
            e.printStackTrace();
        }
        return out;
    }

    @Override
    public int getBaudRate() {
        return this.baudRate;
    }

    @Override
    public int getDataBits() {
        return this.dataBits;
    }

    @Override
    public int getStopBits() {
        return this.stopBits;
    }

    @Override
    public int getParity() {
        return this.parity;
    }
}

代码这里最复杂的也就在这里,其实也不复杂主要咱也没用过太冷门了
然后查看链接的串口是多少
我是Mac系统,进入dev执行ls -l tty* 可以查看

cd /dev
ls -l tty*

> crw-rw-rw-  1 root      wheel   0x2000000 May  9 18:12 tty
> crw-rw-rw-  1 root      wheel   0x9000002 May  8 16:54 tty.Bluetooth- Incoming-Port
> crw-rw-rw-  1 root      wheel   0x9000000 May  8 16:54 tty.PowerbeatsPro
> crw-rw-rw-  1 root      wheel   0x9000004 May 10 08:54 tty.usbserial-1140

下面还有很多没有关系的我就不在这里粘贴了,可以看到第一个是蓝牙,第二个是我的蓝牙耳机,第三个“tty.usbserial-1140”就是我的usb串口,拔掉再来一次就没有了,确定是他接下来写读取数据和写入数据的代码

Windows系统的同志,请在设备管理中找到串口一般是COM*

 public static void main(String[] args) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
        String commPortId = "tty.usbserial-1120";
        int baudRate = 9600;
        // 写入timeout 不能超过100
        int flowControlIn = 50;
        // 输出timeout
        int flowControlOut = 50;
        int dataBits = 8;
        int stopBits = 1;
        // 不校验
        int parity = SerialPort.PARITY_NONE;

		// 初始化并建立链接
        SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl(commPortId, baudRate, flowControlIn, flowControlOut, dataBits, stopBits, parity);
        ModbusFactory modbusFactory = new ModbusFactory();
        ModbusMaster master = modbusFactory.createRtuMaster(wrapper);
        master.init();

		// 获取数据
        BaseLocator<Number> loc = BaseLocator.holdingRegister(3, 25, DataType.TWO_BYTE_INT_UNSIGNED);
        System.out.println(master.getValue(loc));
  		
  		// 写入数据
        master.setValue(loc, 200);
        // 再看看写进去了没有
        System.out.println(master.getValue(loc));
    }

PS:

好了,代码到这里就结束了,大家都成功了吗?

成功的同学可以走了,别忘记点赞

没成功的同学请留下,搞完这几招还没成功再走
以下是我遇到的问题

  • 问题1: 打开了串口当写入信息后就重连了

  • 解决方法:
    • 1、查看你的flowControlIn和flowControlOut是否是0,因为好多文档写着0。请改成稍高的数值。因为是timeout时间所以设置0他响应了你,你却不再接收了。
    • 2、检查你的波特率是和你设备设置的相同,虽然大部分是9600但是设备其实可以更改波特率,不同的波特率传输的物理长度不同,相同单位时间内可传输的信息量也不同。
    • 3、如果上面两个都无法解决你的问题,那么可能就不是代码的问题了。你需要找到你购买的串口转USB线,看看你的串口线是怎样定义的引脚。

      没错这里只需要T/R+和T/R-两根线。我**** ,有没搞错只要两根线。
      对的就是只要两个,然后你需要一个引接板,和两根杜邦线。按下图连接,我这里没有杜邦线,所以只能…
  • 问题2: CRC校验错误

  • 解决方法:
    • 1、 是否开启了信息校验,我们这里设置的是不开启SerialPort.PARITY_NONE;,因为PLC是不开启的。
  • 问题3: 数据与实际内容不符

  • 解决方法:
    • 1、请回顾TCP的解决方式

文章来源:https://blog.csdn.net/qq_29317911/article/details/138708552
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云