当前位置:首页 » 《关于电脑》 » 正文

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

16 人参与  2024年09月14日 18:01  分类 : 《关于电脑》  评论

点击全文阅读


有关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 /devls -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的解决方式

点击全文阅读


本文链接:http://m.zhangshiyu.com/post/159965.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1