首页 > 基础资料 博客日记
有关Java读取Modbus协议连接PLC的示例(使用modbus4j)
2024-10-04 14:00:07基础资料围观123次
有关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);
- 3、检查你下方代码中dataType传入的类型是否与存储的类型一样,如果你不知道可以试试使用不同的类型,看看哪个和你存储的数据相同,然后再改个数据,看看是否正确。
-
- 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-两根线。我**** ,有没搞错只要两根线。
对的就是只要两个,然后你需要一个引接板,和两根杜邦线。按下图连接,我这里没有杜邦线,所以只能…
- 3、如果上面两个都无法解决你的问题,那么可能就不是代码的问题了。你需要找到你购买的串口转USB线,看看你的串口线是怎样定义的引脚。
-
问题2: CRC校验错误
-
解决方法:
-
- 1、 是否开启了信息校验,我们这里设置的是不开启SerialPort.PARITY_NONE;,因为PLC是不开启的。
-
问题3: 数据与实际内容不符
-
解决方法:
-
- 1、请回顾TCP的解决方式
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: