内容目录
本文目录
- 1. 一、EasyModbus库介绍
- 2. 二、环境搭建
- 3. 三、基础连接与读写操作
- 4. 四、浮点数与32位数据处理
- 5. 五、完整的工具类封装
- 6. 六、实际应用示例:继电器模块控制
- 7. 七、异常处理与最佳实践
- 8. 八、其他Java Modbus库对比
在工业物联网和智能制造系统中,Java应用程序经常需要与PLC、传感器、继电器模块等Modbus设备进行通讯。本文详细介绍如何使用EasyModbus库实现Java Modbus TCP客户端,包括环境搭建、连接管理、线圈读写、寄存器读写、浮点数处理、异常处理以及完整的项目示例。
一、EasyModbus库介绍
EasyModbus是一个功能强大的Java Modbus通讯库,支持Modbus TCP、Modbus RTU和Modbus UDP三种协议。它具有以下特点:
- 协议支持全面:支持Modbus TCP、RTU、UDP,可作为客户端和服务端
- API简洁易用:方法命名直观,几行代码即可完成读写操作
- 数据类型丰富:支持16位整数、32位浮点数、64位长整数、字符串等
- 自动重连:内置连接状态检测和自动重连机制
- 线程安全:支持多线程环境下的并发访问
- 开源免费:基于MIT协议,可免费用于商业项目
二、环境搭建
2.1 Maven依赖
在pom.xml中添加EasyModbus依赖:
<dependency>
<groupId>com.github.s7connector</groupId>
<artifactId>easymodbus</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 或者使用JAR包方式 -->
<!-- 下载地址:https://github.com/rossmann-engineering/EasyModbusTCP -->
2.2 手动导入JAR包
- 从GitHub下载EasyModbusTCP.jar
- 在Eclipse中:右键项目 → Build Path → Add External Archives
- 在IntelliJ IDEA中:File → Project Structure → Libraries → + → Java
- 导入包:import de.re.easymodbus.modbusclient.ModbusClient;
三、基础连接与读写操作
3.1 建立连接
import de.re.easymodbus.modbusclient.ModbusClient;
public class ModbusTcpExample {
public static void main(String[] args) {
// 创建Modbus客户端实例
ModbusClient modbusClient = new ModbusClient("192.168.1.100", 502);
try {
// 建立连接
modbusClient.Connect();
System.out.println("连接成功!");
// 设置从站地址(默认为1)
modbusClient.setUnitIdentifier((byte) 1);
// 设置连接超时(毫秒)
modbusClient.setConnectionTimeout(5000);
// 设置读写超时(毫秒)
modbusClient.setReceiveTimeout(3000);
// 业务逻辑...
// 关闭连接
modbusClient.Disconnect();
System.out.println("连接已关闭");
} catch (Exception e) {
System.err.println("连接失败:" + e.getMessage());
e.printStackTrace();
}
}
}
3.2 读线圈(功能码01)
// 读取从地址0开始的8个线圈状态
boolean[] coils = modbusClient.ReadCoils(0, 8);
// 输出每个线圈的状态
for (int i = 0; i < coils.length; i++) {
System.out.println("线圈" + i + "状态:" + (coils[i] ? "ON" : "OFF"));
}
// 方法说明:
// ReadCoils(int startingAddress, int quantity)
// startingAddress: 起始地址(0-based)
// quantity: 读取数量(1-2000)
// 返回: boolean数组,true=线圈吸合,false=线圈断开
3.3 读离散输入(功能码02)
// 读取从地址0开始的4个离散输入
boolean[] inputs = modbusClient.ReadDiscreteInputs(0, 4);
for (int i = 0; i < inputs.length; i++) {
System.out.println("输入" + i + "状态:" + (inputs[i] ? "高电平" : "低电平"));
}
3.4 读保持寄存器(功能码03)
// 读取从地址0开始的10个保持寄存器
int[] registers = modbusClient.ReadHoldingRegisters(0, 10);
for (int i = 0; i < registers.length; i++) {
System.out.println("寄存器" + i + "值:" + registers[i]);
}
// 方法说明:
// ReadHoldingRegisters(int startingAddress, int quantity)
// 返回: int数组,每个元素为16位无符号整数(0-65535)
// 如果需要有符号整数,需要自行转换:(short) registers[i]
3.5 读输入寄存器(功能码04)
// 读取从地址0开始的2个输入寄存器(通常用于传感器数据)
int[] inputRegs = modbusClient.ReadInputRegisters(0, 2);
System.out.println("温度:" + inputRegs[0] / 10.0 + "℃");
System.out.println("湿度:" + inputRegs[1] / 10.0 + "%RH");
3.6 写单个线圈(功能码05)
// 控制地址0的线圈吸合
modbusClient.WriteSingleCoil(0, true);
// 控制地址0的线圈断开
modbusClient.WriteSingleCoil(0, false);
// 方法说明:
// WriteSingleCoil(int address, boolean value)
// value: true=FF00(吸合),false=0000(断开)
3.7 写单个寄存器(功能码06)
// 向地址0写入数值1234
modbusClient.WriteSingleRegister(0, 1234);
// 写入有符号数(需要转换为无符号)
short signedValue = -100;
modbusClient.WriteSingleRegister(0, signedValue & 0xFFFF);
3.8 写多个线圈(功能码15)
// 同时控制前4个线圈
boolean[] coilValues = {true, false, true, false};
modbusClient.WriteMultipleCoils(0, coilValues);
// 方法说明:
// WriteMultipleCoils(int startingAddress, boolean[] values)
// values数组长度即为写入数量
3.9 写多个寄存器(功能码16)
// 向地址0开始写入3个寄存器
int[] regValues = {100, 200, 300};
modbusClient.WriteMultipleRegisters(0, regValues);
四、浮点数与32位数据处理
Modbus协议本身只传输16位寄存器,32位浮点数需要占用两个连续的寄存器。EasyModbus提供了便捷的转换方法。
4.1 读取32位浮点数
// 读取地址11开始的2个寄存器,转换为32位浮点数
float value = ModbusClient.ConvertRegistersToFloat(
modbusClient.ReadHoldingRegisters(11, 2)
);
System.out.println("浮点数值:" + value);
// 指定字节序(默认ABCD,可选BADC、CDAB、DCBA)
float value2 = ModbusClient.ConvertRegistersToFloat(
modbusClient.ReadHoldingRegisters(11, 2),
ModbusClient.RegisterOrder.HighByteFirst // 或 LowByteFirst
);
System.out.println("浮点数值(指定字节序):" + value2);
4.2 写入32位浮点数
// 将浮点数123.56转换为2个寄存器并写入地址11
float floatValue = 123.56f;
int[] regs = ModbusClient.ConvertFloatToTwoRegisters(floatValue);
modbusClient.WriteMultipleRegisters(11, regs);
System.out.println("写入的寄存器值:" + regs[0] + ", " + regs[1]);
4.3 32位整数处理
// 读取32位整数
int[] regs32 = modbusClient.ReadHoldingRegisters(0, 2);
int int32Value = (regs32[0] << 16) | regs32[1];
System.out.println("32位整数值:" + int32Value);
// 写入32位整数
int writeValue = 12345678;
int[] writeRegs = { (writeValue >> 16) & 0xFFFF, writeValue & 0xFFFF };
modbusClient.WriteMultipleRegisters(0, writeRegs);
4.4 字符串处理
// 读取字符串(每个寄存器存2个字符)
int[] strRegs = modbusClient.ReadHoldingRegisters(0, 10);
StringBuilder sb = new StringBuilder();
for (int reg : strRegs) {
sb.append((char) ((reg >> 8) & 0xFF));
sb.append((char) (reg & 0xFF));
}
String str = sb.toString().trim();
System.out.println("读取的字符串:" + str);
五、完整的工具类封装
在实际项目中,建议将Modbus操作封装为工具类,便于复用和管理。
import de.re.easymodbus.modbusclient.ModbusClient;
import java.io.IOException;
public class ModbusTcpUtil {
private ModbusClient client;
private String host;
private int port;
private int unitId;
private boolean connected = false;
public ModbusTcpUtil(String host, int port, int unitId) {
this.host = host;
this.port = port;
this.unitId = unitId;
}
/**
* 建立连接(带自动重连)
*/
public synchronized boolean connect() {
try {
if (client == null) {
client = new ModbusClient(host, port);
client.setUnitIdentifier((byte) unitId);
client.setConnectionTimeout(5000);
client.setReceiveTimeout(3000);
}
if (!connected) {
client.Connect();
connected = true;
System.out.println("Modbus连接成功:" + host + ":" + port);
}
return true;
} catch (Exception e) {
System.err.println("Modbus连接失败:" + e.getMessage());
connected = false;
return false;
}
}
/**
* 断开连接
*/
public synchronized void disconnect() {
try {
if (client != null && connected) {
client.Disconnect();
connected = false;
}
} catch (Exception e) {
System.err.println("断开连接异常:" + e.getMessage());
}
}
/**
* 读取保持寄存器(带重试)
*/
public int[] readHoldingRegisters(int addr, int quantity) {
int retry = 3;
while (retry-- > 0) {
try {
if (!connected) connect();
return client.ReadHoldingRegisters(addr, quantity);
} catch (Exception e) {
System.err.println("读取失败,剩余重试次数:" + retry);
connected = false;
try { Thread.sleep(500); } catch (InterruptedException ie) {}
}
}
throw new RuntimeException("读取保持寄存器失败,地址:" + addr);
}
/**
* 读取浮点数
*/
public float readFloat(int addr) {
int[] regs = readHoldingRegisters(addr, 2);
return ModbusClient.ConvertRegistersToFloat(regs);
}
/**
* 写入浮点数
*/
public void writeFloat(int addr, float value) {
int[] regs = ModbusClient.ConvertFloatToTwoRegisters(value);
writeMultipleRegisters(addr, regs);
}
/**
* 写多个寄存器(带重试)
*/
public void writeMultipleRegisters(int addr, int[] values) {
int retry = 3;
while (retry-- > 0) {
try {
if (!connected) connect();
client.WriteMultipleRegisters(addr, values);
return;
} catch (Exception e) {
System.err.println("写入失败,剩余重试次数:" + retry);
connected = false;
try { Thread.sleep(500); } catch (InterruptedException ie) {}
}
}
throw new RuntimeException("写入寄存器失败,地址:" + addr);
}
/**
* 读线圈
*/
public boolean[] readCoils(int addr, int quantity) {
try {
if (!connected) connect();
return client.ReadCoils(addr, quantity);
} catch (Exception e) {
connected = false;
throw new RuntimeException("读线圈失败:" + e.getMessage());
}
}
/**
* 写单个线圈
*/
public void writeCoil(int addr, boolean value) {
try {
if (!connected) connect();
client.WriteSingleCoil(addr, value);
} catch (Exception e) {
connected = false;
throw new RuntimeException("写线圈失败:" + e.getMessage());
}
}
/**
* 检查连接状态
*/
public boolean isConnected() {
return connected;
}
}
六、实际应用示例:继电器模块控制
public class RelayControlDemo {
public static void main(String[] args) {
// 连接到8路网络继电器模块
ModbusTcpUtil modbus = new ModbusTcpUtil("192.168.1.100", 502, 1);
try {
modbus.connect();
// 1. 读取所有继电器状态
System.out.println("=== 当前继电器状态 ===");
boolean[] states = modbus.readCoils(0, 8);
for (int i = 0; i < states.length; i++) {
System.out.println("继电器" + (i+1) + ":" + (states[i] ? "吸合" : "断开"));
}
// 2. 控制第1路继电器吸合
System.out.println("\n=== 控制第1路继电器吸合 ===");
modbus.writeCoil(0, true);
Thread.sleep(500);
// 3. 读取数字输入状态
System.out.println("\n=== 数字输入状态 ===");
boolean[] inputs = modbus.readCoils(0, 8); // 实际应使用ReadDiscreteInputs
for (int i = 0; i < inputs.length; i++) {
System.out.println("输入" + (i+1) + ":" + (inputs[i] ? "有信号" : "无信号"));
}
// 4. 读取模块温度(假设在寄存器100)
System.out.println("\n=== 模块温度 ===");
float temp = modbus.readFloat(100);
System.out.println("温度:" + temp + "℃");
// 5. 依次吸合所有继电器(跑马灯效果)
System.out.println("\n=== 跑马灯测试 ===");
for (int i = 0; i < 8; i++) {
modbus.writeCoil(i, true);
Thread.sleep(200);
modbus.writeCoil(i, false);
}
// 6. 全部断开
boolean[] allOff = {false, false, false, false, false, false, false, false};
// modbus.writeMultipleCoils(0, allOff); // 需要封装此方法
System.out.println("\n测试完成!");
} catch (Exception e) {
e.printStackTrace();
} finally {
modbus.disconnect();
}
}
}
七、异常处理与最佳实践
7.1 常见异常类型
| 异常 | 原因 | 解决方法 |
|---|---|---|
| ConnectException | 连接被拒绝 | 检查IP和端口,确认设备在线 |
| SocketTimeoutException | 连接或读写超时 | 增加超时时间,检查网络延迟 |
| ModbusException | Modbus协议错误 | 检查从站地址、功能码、寄存器地址 |
| IllegalFunctionException | 非法功能码 | 设备不支持该功能码 |
| IllegalAddressException | 非法地址 | 读取/写入地址超出设备范围 |
| IllegalValueException | 非法数据值 | 写入的数据不符合设备要求 |
7.2 最佳实践
- 使用连接池:频繁创建和销毁连接会影响性能,建议复用连接
- 添加重试机制:网络不稳定时,读写失败应自动重试2-3次
- 控制读写频率:避免过快轮询,建议间隔不小于100ms
- 批量读写:尽量一次读取多个寄存器,减少通讯次数
- 异常恢复:捕获异常后重新建立连接,不要直接退出程序
- 线程安全:多线程访问时使用synchronized或锁机制
- 资源释放:使用完毕后调用Disconnect()释放连接
- 日志记录:记录每次读写的地址、数据和耗时,便于排查问题
7.3 性能优化建议
// 优化示例:批量读取 vs 单次读取
// 低效:循环读取10个寄存器
for (int i = 0; i < 10; i++) {
int val = modbusClient.ReadHoldingRegisters(i, 1)[0]; // 10次通讯
}
// 高效:一次读取10个寄存器
int[] vals = modbusClient.ReadHoldingRegisters(0, 10); // 1次通讯
// 优化示例:合理设置超时
modbusClient.setConnectionTimeout(3000); // 连接超时3秒
modbusClient.setReceiveTimeout(2000); // 读写超时2秒
// 超时设置过短会导致频繁失败,过长会导致程序卡死
八、其他Java Modbus库对比
| 库名 | 协议支持 | 特点 | 适用场景 |
|---|---|---|---|
| EasyModbus | TCP/RTU/UDP | API简洁,支持浮点数转换 | 快速开发,中小型项目 |
| j2mod | TCP/RTU/ASCII | 功能全面,社区活跃 | 需要RTU串口通讯 |
| Modbus4J | TCP/RTU/ASCII | 架构灵活,可扩展性强 | 大型企业级项目 |
| Jamod | TCP/RTU | 经典老牌,文档丰富 | 学习和维护老项目 |
| JLibModbus | TCP/RTU | 纯Java实现,无本地依赖 | 跨平台部署 |
本文提供的代码可直接复制到项目中使用。建议先使用Modbus Poll等调试工具确认设备通讯正常,再将Java代码接入实际项目。如需Modbus RTU串口通讯,可使用j2mod或JLibModbus库,通过RXTX或jSerialComm访问串口。
发表回复