有关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-两根线。我**** ,有没搞错只要两根线。
对的就是只要两个,然后你需要一个引接板,和两根杜邦线。按下图连接,我这里没有杜邦线,所以只能…