内容目录
EasyModbus是Java生态中最流行的Modbus通讯库之一,以其简洁的API、丰富的功能和良好的文档著称。它支持Modbus TCP、RTU和UDP三种协议,既可作为客户端也可作为服务端,广泛应用于工业自动化、数据采集、物联网网关等领域。本文详细介绍EasyModbus的安装配置、连接管理、线圈读写、寄存器读写、浮点数处理以及完整的项目示例。
一、EasyModbus库概述
1.1 核心特性
- 多协议支持:Modbus TCP、RTU、UDP三种传输模式
- 主从兼备:既可作为客户端(主站),也可作为服务端(从站)
- 功能完整:支持01-16所有标准功能码
- 数据类型丰富:支持16位/32位整数、32位/64位浮点数、字符串
- 字节序可配:支持大端、小端、字交换等多种字节序
- 事件驱动:支持数据变化监听、连接状态回调
- 跨平台:Windows、Linux、Mac OS、嵌入式Linux均可运行
- 开源免费:MIT协议,可免费用于商业项目
1.2 Maven依赖配置
<!-- pom.xml -->
<dependency>
<groupId>com.github.s7connector</groupId>
<artifactId>easy-modbus</artifactId>
<version>5.0.0</version>
</dependency>
<!-- 串口支持(RTU模式需要) -->
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.9.2</version>
</dependency>
1.3 Gradle依赖配置
// build.gradle
dependencies {
implementation 'com.github.s7connector:easy-modbus:5.0.0'
implementation 'com.fazecast:jSerialComm:2.9.2'
}
二、TCP客户端基础操作
2.1 建立连接
import de.re.easymodbus.modbusclient.ModbusClient;
import de.re.easymodbus.exceptions.ModbusException;
import java.io.IOException;
public class ModbusTcpDemo {
public static void main(String[] args) {
// 创建Modbus客户端
ModbusClient client = new ModbusClient("192.168.1.100", 502);
try {
// 设置从站地址(Unit ID)
client.setUnitIdentifier((byte) 1);
// 设置连接超时(毫秒)
client.setConnectionTimeout(3000);
// 建立连接
client.Connect();
System.out.println("连接成功!");
// 业务逻辑...
// 关闭连接
client.Disconnect();
System.out.println("连接已关闭");
} catch (IOException e) {
System.err.println("连接失败:" + e.getMessage());
} catch (ModbusException e) {
System.err.println("Modbus异常:" + e.getMessage());
}
}
}
2.2 读线圈(功能码01)
// 读取从地址0开始的8个线圈状态
boolean[] coils = client.ReadCoils(0, 8);
System.out.println("线圈状态:");
for (int i = 0; i < coils.length; i++) {
System.out.println(" 线圈" + i + ":" + (coils[i] ? "吸合" : "断开"));
}
// 方法签名:
// boolean[] ReadCoils(int startingAddress, int quantity)
// startingAddress: 起始地址(0-based)
// quantity: 读取数量(1-2000)
2.3 读离散输入(功能码02)
// 读取从地址0开始的4个离散输入
boolean[] inputs = client.ReadDiscreteInputs(0, 4);
for (int i = 0; i < inputs.length; i++) {
System.out.println("输入" + i + ":" + (inputs[i] ? "高电平" : "低电平"));
}
2.4 读保持寄存器(功能码03)
// 读取从地址0开始的10个保持寄存器
int[] registers = client.ReadHoldingRegisters(0, 10);
System.out.println("保持寄存器:");
for (int i = 0; i < registers.length; i++) {
System.out.println(" 寄存器" + i + ":" + registers[i]);
}
// 返回值为int数组,每个元素为16位无符号整数(0-65535)
// 如需有符号整数:short value = (short) registers[i];
2.5 读输入寄存器(功能码04)
// 读取传感器数据(温度、湿度等)
int[] inputRegs = client.ReadInputRegisters(0, 2);
double temperature = inputRegs[0] / 10.0; // 温度放大10倍
double humidity = inputRegs[1] / 10.0; // 湿度放大10倍
System.out.printf("温度:%.1f℃,湿度:%.1f%%RH%n", temperature, humidity);
2.6 写单个线圈(功能码05)
// 控制地址0的线圈吸合
client.WriteSingleCoil(0, true);
System.out.println("线圈吸合成功");
// 控制地址0的线圈断开
client.WriteSingleCoil(0, false);
// 方法签名:
// void WriteSingleCoil(int address, boolean value)
2.7 写单个寄存器(功能码06)
// 向地址0写入数值1234
client.WriteSingleRegister(0, 1234);
System.out.println("寄存器写入成功");
// 写入有符号数(-100)
client.WriteSingleRegister(0, -100 & 0xFFFF);
2.8 写多个线圈(功能码15)
// 同时控制前4个线圈
boolean[] values = {true, false, true, false};
client.WriteMultipleCoils(0, values);
System.out.println("批量写线圈成功");
2.9 写多个寄存器(功能码16)
// 向地址0开始写入3个寄存器
int[] values = {100, 200, 300};
client.WriteMultipleRegisters(0, values);
System.out.println("批量写寄存器成功");
三、浮点数与多字节数据处理
EasyModbus提供了便捷的方法处理32位浮点数、32位整数和64位数据,支持多种字节序。
3.1 读取32位浮点数
import de.re.easymodbus.datatypes.DataType;
// 读取2个寄存器并转换为32位浮点数
// 方法1:使用ReadHoldingRegisters手动转换
int[] regs = client.ReadHoldingRegisters(10, 2);
float floatValue = client.ConvertRegistersToFloat(regs);
System.out.println("浮点数值:" + floatValue);
// 方法2:直接读取浮点数(v5.0+支持)
float temp = client.ReadFloat(10, DataType.FLOAT);
System.out.println("温度:" + temp);
// 设置字节序(默认大端ABCD)
client.setDataType(DataType.FLOAT); // 32位浮点数
// 可选:DataType.FLOAT_SWAP (CDAB), DataType.FLOAT_LOWWORD_FIRST (BADC)
3.2 写入32位浮点数
// 将浮点数25.5转换为2个寄存器并写入
float value = 25.5f;
int[] regs = client.ConvertFloatToRegisters(value);
client.WriteMultipleRegisters(10, regs);
System.out.println("写入浮点数:" + value + ",寄存器值:" + regs[0] + ", " + regs[1]);
3.3 读取32位整数
// 读取32位无符号整数
int[] regs = client.ReadHoldingRegisters(0, 2);
long uint32 = client.ConvertRegistersToLong(regs);
System.out.println("32位无符号整数:" + uint32);
// 读取32位有符号整数
int int32 = (int) uint32;
System.out.println("32位有符号整数:" + int32);
3.4 读取字符串
// 读取字符串(每个寄存器存2个字符,读取10个寄存器=20字符)
int[] regs = client.ReadHoldingRegisters(0, 10);
String str = client.ConvertRegistersToString(regs, 20);
System.out.println("字符串:" + str);
3.5 字节序说明
| DataType枚举 | 字节序 | 说明 |
|---|---|---|
| FLOAT | ABCD(大端) | 大多数PLC、Modbus设备 |
| FLOAT_SWAP | CDAB(字交换) | 部分西门子设备 |
| FLOAT_LOWWORD_FIRST | BADC(字节交换) | 较少见 |
| FLOAT_SWAP_LOWWORD_FIRST | DCBA(小端) | 部分嵌入式设备 |
四、Modbus RTU串口通讯
import de.re.easymodbus.modbusclient.ModbusClient;
import de.re.easymodbus.datatypes.Parity;
import de.re.easymodbus.datatypes.StopBits;
public class ModbusRtuDemo {
public static void main(String[] args) {
// 创建RTU客户端
ModbusClient client = new ModbusClient();
try {
// 设置串口参数
client.setSerialPort("/dev/ttyUSB0"); // 串口设备(Windows为"COM3")
client.setBaudRate(9600); // 波特率
client.setParity(Parity.None); // 校验位:None/Even/Odd
client.setStopBits(StopBits.One); // 停止位:One/Two
client.setDataBits(8); // 数据位
// 设置从站地址
client.setUnitIdentifier((byte) 1);
// 建立连接
client.Connect();
System.out.println("RTU连接成功");
// 读写操作与TCP完全相同
int[] regs = client.ReadHoldingRegisters(0, 10);
System.out.println("寄存器:" + java.util.Arrays.toString(regs));
client.Disconnect();
} catch (Exception e) {
System.err.println("错误:" + e.getMessage());
}
}
}
五、完整工具类封装
import de.re.easymodbus.modbusclient.ModbusClient;
import de.re.easymodbus.exceptions.ModbusException;
import java.io.IOException;
public class ModbusUtil {
private ModbusClient client;
private final String host;
private final int port;
private final byte slaveId;
public ModbusUtil(String host, int port, int slaveId) {
this.host = host;
this.port = port;
this.slaveId = (byte) slaveId;
}
/**
* 建立连接(带重试)
*/
public boolean connect() {
for (int attempt = 1; attempt <= 3; attempt++) {
try {
client = new ModbusClient(host, port);
client.setUnitIdentifier(slaveId);
client.setConnectionTimeout(3000);
client.Connect();
System.out.println("连接成功:" + host + ":" + port);
return true;
} catch (IOException e) {
System.err.println("连接失败(第" + attempt + "次):" + e.getMessage());
try { Thread.sleep(1000); } catch (InterruptedException ie) { }
}
}
return false;
}
/**
* 断开连接
*/
public void disconnect() {
if (client != null) {
try {
client.Disconnect();
System.out.println("连接已关闭");
} catch (IOException e) {
System.err.println("关闭连接失败:" + e.getMessage());
}
}
}
/**
* 读取保持寄存器(带重试)
*/
public int[] readRegisters(int addr, int count) {
for (int attempt = 1; attempt <= 3; attempt++) {
try {
return client.ReadHoldingRegisters(addr, count);
} catch (Exception e) {
System.err.println("读取失败(第" + attempt + "次):" + e.getMessage());
reconnect();
}
}
throw new RuntimeException("读取寄存器失败,地址:" + addr);
}
/**
* 读取32位浮点数
*/
public float readFloat(int addr) {
int[] regs = readRegisters(addr, 2);
return client.ConvertRegistersToFloat(regs);
}
/**
* 写入32位浮点数
*/
public void writeFloat(int addr, float value) {
int[] regs = client.ConvertFloatToRegisters(value);
writeRegisters(addr, regs);
}
/**
* 写入多个寄存器
*/
public void writeRegisters(int addr, int[] values) {
for (int attempt = 1; attempt <= 3; attempt++) {
try {
client.WriteMultipleRegisters(addr, values);
return;
} catch (Exception e) {
System.err.println("写入失败(第" + attempt + "次):" + e.getMessage());
reconnect();
}
}
throw new RuntimeException("写入寄存器失败,地址:" + addr);
}
/**
* 写单个线圈
*/
public void writeCoil(int addr, boolean value) {
try {
client.WriteSingleCoil(addr, value);
} catch (Exception e) {
throw new RuntimeException("写线圈失败:" + e.getMessage());
}
}
/**
* 读线圈
*/
public boolean[] readCoils(int addr, int count) {
try {
return client.ReadCoils(addr, count);
} catch (Exception e) {
throw new RuntimeException("读线圈失败:" + e.getMessage());
}
}
/**
* 重新连接
*/
private void reconnect() {
disconnect();
connect();
}
}
// 使用示例
class Demo {
public static void main(String[] args) {
ModbusUtil mb = new ModbusUtil("192.168.1.100", 502, 1);
if (mb.connect()) {
// 读取温度(浮点数)
float temp = mb.readFloat(10);
System.out.printf("温度:%.2f℃%n", temp);
// 读取10个寄存器
int[] regs = mb.readRegisters(0, 10);
System.out.println("寄存器:" + java.util.Arrays.toString(regs));
// 控制继电器
mb.writeCoil(0, true);
System.out.println("继电器1已吸合");
// 读取继电器状态
boolean[] coils = mb.readCoils(0, 8);
System.out.println("继电器状态:" + java.util.Arrays.toString(coils));
mb.disconnect();
}
}
}
六、Modbus服务端(从站)实现
import de.re.easymodbus.modbusserver.ModbusServer;
public class ModbusServerDemo {
public static void main(String[] args) {
// 创建Modbus TCP服务端
ModbusServer server = new ModbusServer();
try {
// 设置监听端口
server.setPort(502);
// 设置从站地址
server.setUnitIdentifier((byte) 1);
// 设置初始值
server.holdingRegisters[0] = 100; // 温度
server.holdingRegisters[1] = 50; // 湿度
server.coils[0] = true; // 继电器1
// 启动服务端
server.Listen();
System.out.println("Modbus TCP服务端已启动,监听端口:502");
// 模拟数据更新
while (true) {
Thread.sleep(1000);
server.holdingRegisters[2] = (int) (Math.random() * 100);
server.inputRegisters[0] = (int) (Math.random() * 4096);
}
} catch (Exception e) {
System.err.println("服务端错误:" + e.getMessage());
}
}
}
七、实际应用:继电器模块控制
public class RelayController {
private final ModbusUtil mb;
public RelayController(String host) {
mb = new ModbusUtil(host, 502, 1);
mb.connect();
}
/** 读取所有继电器状态 */
public boolean[] getAllStatus() {
return mb.readCoils(0, 8);
}
/** 打开指定通道(1-8) */
public void turnOn(int channel) {
mb.writeCoil(channel - 1, true);
}
/** 关闭指定通道 */
public void turnOff(int channel) {
mb.writeCoil(channel - 1, false);
}
/** 切换指定通道状态 */
public void toggle(int channel) {
boolean[] coils = mb.readCoils(channel - 1, 1);
mb.writeCoil(channel - 1, !coils[0]);
}
/** 跑马灯效果 */
public void marquee(int delayMs) {
for (int i = 1; i <= 8; i++) {
turnOn(i);
try { Thread.sleep(delayMs); } catch (InterruptedException e) { }
turnOff(i);
}
}
public void close() {
mb.disconnect();
}
public static void main(String[] args) {
RelayController relay = new RelayController("192.168.1.100");
// 读取当前状态
boolean[] status = relay.getAllStatus();
System.out.print("当前状态:");
for (boolean s : status) {
System.out.print(s ? "ON " : "OFF ");
}
System.out.println();
// 跑马灯测试
System.out.println("跑马灯测试...");
relay.marquee(300);
// 依次控制
relay.turnOn(1);
try { Thread.sleep(500); } catch (InterruptedException e) { }
relay.turnOn(2);
try { Thread.sleep(500); } catch (InterruptedException e) { }
relay.turnOff(1);
try { Thread.sleep(500); } catch (InterruptedException e) { }
relay.turnOff(2);
relay.close();
}
}
八、常见问题与最佳实践
8.1 常见问题
- 连接超时:检查IP地址、端口号、防火墙,确认设备在线且端口502开放
- 返回异常响应:检查从站地址(Unit ID)是否正确,功能码和地址是否在设备支持范围内
- 浮点数解析错误:尝试不同的DataType(FLOAT/FLOAT_SWAP等),确认设备的数据格式
- RTU串口打不开:检查串口设备名,确认权限(Linux需加入dialout组),关闭其他占用串口的程序
- 数据读取为0:确认寄存器地址正确,注意0-based和1-based地址的区别
- 频繁断连:增加超时时间,检查网络稳定性,添加自动重连机制
8.2 最佳实践
- 使用try-with-resources:确保连接正确关闭
- 批量读取:尽量一次读取多个寄存器,减少通讯次数
- 合理设置超时:局域网建议1-3秒,RTU建议3-5秒
- 添加重试机制:网络不稳定时自动重试2-3次
- 控制轮询频率:避免过快轮询,建议间隔不小于100ms
- 异常处理:捕获ModbusException和IOException,区分连接错误和协议错误
- 日志记录:记录每次读写的地址、数据和耗时,便于排查
- 多设备管理:每个设备使用独立的ModbusClient实例,避免共享连接
九、其他Java Modbus库对比
| 库名 | 协议支持 | 特点 | 适用场景 |
|---|---|---|---|
| EasyModbus | TCP/RTU/UDP | API简洁,文档完善,功能丰富 | 大多数项目首选 |
| j2mod | TCP/RTU/ASCII | j2mod的活跃分支,功能全面 | 需要ASCII协议 |
| Jamod | TCP/RTU | 经典库,稳定但更新慢 | 遗留项目维护 |
| Modbus4J | TCP/RTU | 功能强大,API较复杂 | 复杂项目 |
| modbus-master | TCP/RTU | 轻量级,仅主站 | 简单读写场景 |
EasyModbus以其简洁的API和丰富的功能,成为Java Modbus开发的首选库。本文提供的工具类可直接用于项目开发,建议先使用Modbus Poll等工具确认设备通讯正常,再将Java代码集成到实际项目中。
发表回复