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 시작ing주소, int 수량)
// 시작ing주소: 시작주소(0-based)
// 수량: 읽을 수량(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.ReadHolding회원가입s(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.ReadInput회원가입s(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 주소, boolean value)
2.7 写单个레지스터(기능 코드06)
// 向주소0쓰기数值1234
client.WriteSingle회원가입(0, 1234);
System.out.println("레지스터쓰기成功");
// 쓰기有符号数(-100)
client.WriteSingle회원가입(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.WriteMultiple회원가입s(0, values);
System.out.println("批量写레지스터成功");
三、浮点数与多바이트데이터处理
EasyModbus提供了便捷的方法处理32位浮点数、32位整数和64位데이터,支持多种바이트序。
3.1 읽기32位浮点数
import de.re.easymodbus.데이터types.데이터Type;
// 읽기2个레지스터并转换为32位浮点数
// 方法1:使用ReadHolding회원가입s手动转换
int[] regs = client.ReadHolding회원가입s(10, 2);
float floatValue = client.Convert회원가입sToFloat(regs);
System.out.println("浮点数值:" + floatValue);
// 方法2:直接읽기浮点数(v5.0+支持)
float temp = client.ReadFloat(10, 데이터Type.FLOAT);
System.out.println("온도:" + temp);
// 设置바이트序(默认大端ABCD)
client.set데이터Type(데이터Type.FLOAT); // 32位浮点数
// 可选:데이터Type.FLOAT_SWAP (CDAB), 데이터Type.FLOAT_LOWWORD_FIRST (BADC)
3.2 쓰기32位浮点数
// 将浮点数25.5转换为2个레지스터并쓰기
float value = 25.5f;
int[] regs = client.ConvertFloatTo회원가입s(value);
client.WriteMultiple회원가입s(10, regs);
System.out.println("쓰기浮点数:" + value + ",레지스터 값:" + regs[0] + ", " + regs[1]);
3.3 읽기32位整数
// 읽기32位无符号整数
int[] regs = client.ReadHolding회원가입s(0, 2);
long uint32 = client.Convert회원가입sToLong(regs);
System.out.println("32位无符号整数:" + uint32);
// 읽기32位有符号整数
int int32 = (int) uint32;
System.out.println("32位有符号整数:" + int32);
3.4 읽기문자串
// 읽기문자串(每个레지스터存2个문자,읽기10个레지스터=20문자)
int[] regs = client.ReadHolding회원가입s(0, 10);
String str = client.Convert회원가입sToString(regs, 20);
System.out.println("문자串:" + str);
3.5 바이트序설명
| 데이터Type枚举 | 바이트序 | 설명 |
|---|---|---|
| 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.데이터types.Parity;
import de.re.easymodbus.데이터types.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.set데이터Bits(8); // 데이터 비트
// 设置슬레이브 주소
client.setUnitIdentifier((byte) 1);
// 建立연결
client.Connect();
System.out.println("RTU연결成功");
// 读写操作与TCP完全相同
int[] regs = client.ReadHolding회원가입s(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());
}
}
}
/**
* 유지 레지스터 읽기 (FC03)(带重试)
*/
public int[] read회원가입s(int addr, int count) {
for (int attempt = 1; attempt <= 3; attempt++) {
try {
return client.ReadHolding회원가입s(addr, count);
} catch (Exception e) {
System.err.println("읽기失败(第" + attempt + "次):" + e.getMessage());
reconnect();
}
}
throw new RuntimeException("읽기레지스터失败,주소:" + addr);
}
/**
* 읽기32位浮点数
*/
public float readFloat(int addr) {
int[] regs = read회원가입s(addr, 2);
return client.Convert회원가입sToFloat(regs);
}
/**
* 쓰기32位浮点数
*/
public void writeFloat(int addr, float value) {
int[] regs = client.ConvertFloatTo회원가입s(value);
write회원가입s(addr, regs);
}
/**
* 다중 레지스터 쓰기 (FC16)
*/
public void write회원가입s(int addr, int[] values) {
for (int attempt = 1; attempt <= 3; attempt++) {
try {
client.WriteMultiple회원가입s(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.read회원가입s(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.holding회원가입s[0] = 100; // 온도
server.holding회원가입s[1] = 50; // 湿度
server.coils[0] = true; // 릴레이1
// 启动服务端
server.Listen();
System.out.println("Modbus TCP服务端已启动,监听포트:502");
// 模拟데이터更新
while (true) {
Thread.sleep(1000);
server.holding회원가입s[2] = (int) (Math.random() * 100);
server.input회원가입s[0] = (int) (Math.random() * 4096);
}
} catch (Exception e) {
System.err.println("服务端错误:" + e.getMessage());
}
}
}
七、实际적용:릴레이模块控制
public class Relay컨트롤러 {
private final ModbusUtil mb;
public Relay컨트롤러(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) {
Relay컨트롤러 relay = new Relay컨트롤러("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)是否正确,기능 코드和주소是否在设备支持范围内
- 浮点数분석错误:尝试不同的데이터Type(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代码통합到实际项目中。
답글 작성