Java作为跨平台编程语言,在工业自动化上位机开发中应用广泛。通过ModbusProtocol,Java程序可以与各种品牌的PLC(西门子、三菱、台达、欧姆龙等)进行高效可靠的数据交互。本文提供完整的Java与PLC通信示例代码,包括Modbus TCP客户端实现、多PLC管理、데이터 Acquisition、断线重连、批量读写、浮点数处理以及Spring Boot集成方案。
一、Java Modbus通信方案选型
1.1 常用Java Modbus库对比
| 库名 | 协议支持 | 主从支持 | Maven坐标 | 推荐场景 |
|---|---|---|---|---|
| EasyModbus | TCP/RTU/UDP | 主+从 | com.github.s7connector:easymodbus | 新项目首选,API简洁 |
| Jamod | TCP/RTU/ASCII | 主+从 | net.wimpi:jamod | 遗留项目,稳定 |
| j2mod | TCP/RTU/ASCII | 主+从 | com.ghgande:j2mod | 需要ASCIIProtocol |
| Modbus4J | TCP/RTU | 主+从 | com.infiniteautomation:modbus4j | SCADAsystem |
| modbus-tcp | TCP | 仅主 | com.digitalpetri.modbus:modbus-tcp | 高性能Netty实现 |
1.2 本文使用EasyModbus
EasyModbus是目前最活跃、API最简洁的Java Modbus库,支持TCP、RTU和UDPProtocol,既可作为客户端也可作为服务端,文档完善,适合大多数项目。
<!-- Maven依赖 -->
<dependency>
<groupId>com.github.s7connector</groupId>
<artifactId>easymodbus</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 串口支持(RTU模式需要) -->
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.9.2</version>
</dependency>
二、Modbus TCP客户端封装
package com.industrial.modbus;
import de.re.easymodbus.modbusclient.ModbusClient;
import de.re.easymodbus.데이터types.데이터Type;
import java.io.IOException;
import java.net.SocketException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Modbus TCP客户端封装
* 支持连接管理、自动重连、批量读写、数据类型转换
*/
public class PlcModbusClient {
private final String ip주소;
private final int port;
private final int unitId;
private ModbusClient client;
private final AtomicBoolean connected = new AtomicBoolean(false);
private Thread reconnectThread;
private volatile boolean running = true;
// 连接参数
private static final int CONNECT_TIMEOUT = 3000;
private static final int RECONNECT_INTERVAL = 5000;
private static final int MAX_RETRY = 3;
public PlcModbusClient(String ip주소, int port, int unitId) {
this.ip주소 = ip주소;
this.port = port;
this.unitId = unitId;
}
/**
* connectPLC
*/
public synchronized boolean connect() {
try {
if (client != null && client.isConnected()) {
return true;
}
client = new ModbusClient(ip주소, port);
client.setConnectionTimeout(CONNECT_TIMEOUT);
client.setUnitIdentifier(unitId);
client.Connect();
connected.set(true);
System.out.printf("[%s:%d] PLC连接成功,slave 주소:%d%n",
ip주소, port, unitId);
return true;
} catch (Exception e) {
connected.set(false);
System.err.printf("[%s:%d] 连接失败:%s%n", ip주소, e.getMessage());
return false;
}
}
/**
* 断开连接
*/
public synchronized void disconnect() {
running = false;
if (reconnectThread != null) {
reconnectThread.interrupt();
}
try {
if (client != null && client.isConnected()) {
client.Disconnect();
}
} catch (IOException e) {
// 忽略
}
connected.set(false);
System.out.printf("[%s:%d] 连接已关闭%n", ip주소);
}
/**
* 启动自动重连
*/
public void 시작AutoReconnect() {
reconnectThread = new Thread(() -> {
while (running) {
try {
if (!isConnected()) {
System.out.printf("[%s:%d] 检测到断开,尝试重连...%n", ip주소);
connect();
}
Thread.sleep(RECONNECT_INTERVAL);
} catch (InterruptedException e) {
break;
}
}
}, "Modbus-Reconnect-" + ip주소);
reconnectThread.setDaemon(true);
reconnectThread.시작();
}
/**
* 检查连接状态
*/
public boolean isConnected() {
if (client == null) return false;
try {
// 发送一个简单的读请求测试连接
client.ReadHolding회원가입s(0, 1);
return true;
} catch (Exception e) {
connected.set(false);
return false;
}
}
// ==================== 读操作 ====================
/**
* read holding registers(기능 코드03)
*/
public int[] readHolding회원가입s(int 시작Addr, int count) throws IOException {
for (int retry = 0; retry < MAX_RETRY; retry++) {
try {
ensureConnected();
return client.ReadHolding회원가입s(시작Addr, count);
} catch (SocketException e) {
connected.set(false);
if (retry < MAX_RETRY - 1) {
connect();
continue;
}
throw e;
}
}
throw new IOException("Read failed,重试次数超限");
}
/**
* read input registers(기능 코드04)
*/
public int[] readInput회원가입s(int 시작Addr, int count) throws IOException {
ensureConnected();
return client.ReadInput회원가입s(시작Addr, count);
}
/**
* 读线圈(기능 코드01)
*/
public boolean[] readCoils(int 시작Addr, int count) throws IOException {
ensureConnected();
return client.ReadCoils(시작Addr, count);
}
/**
* read discrete inputs(기능 코드02)
*/
public boolean[] readDiscreteInputs(int 시작Addr, int count) throws IOException {
ensureConnected();
return client.ReadDiscreteInputs(시작Addr, count);
}
// ==================== 写操作 ====================
/**
* write single register(기능 코드06)
*/
public void writeSingle회원가입(int addr, int value) throws IOException {
ensureConnected();
client.WriteSingle회원가입(addr, value);
}
/**
* write multiple registers(기능 코드16)
*/
public void writeMultiple회원가입s(int 시작Addr, int[] values) throws IOException {
ensureConnected();
client.WriteMultiple회원가입s(시작Addr, values);
}
/**
* write single coil(기능 코드05)
*/
public void writeSingleCoil(int addr, boolean value) throws IOException {
ensureConnected();
client.WriteSingleCoil(addr, value);
}
/**
* write multiple coils(기능 코드15)
*/
public void writeMultipleCoils(int 시작Addr, boolean[] values) throws IOException {
ensureConnected();
client.WriteMultipleCoils(시작Addr, values);
}
// ==================== 数据类型转换 ====================
/**
* read32位有符号整数(占用2个寄存器)
*/
public int readInt32(int 시작Addr) throws IOException {
int[] regs = readHolding회원가입s(시작Addr, 2);
return (regs[0] << 16) | (regs[1] & 0xFFFF);
}
/**
* read32位无符号整数
*/
public long readUInt32(int 시작Addr) throws IOException {
int[] regs = readHolding회원가입s(시작Addr, 2);
return ((long)(regs[0] & 0xFFFF) << 16) | (regs[1] & 0xFFFF);
}
/**
* read32位浮点数
*/
public float readFloat32(int 시작Addr) throws IOException {
int[] regs = readHolding회원가입s(시작Addr, 2);
int bits = (regs[0] << 16) | (regs[1] & 0xFFFF);
return Float.intBitsToFloat(bits);
}
/**
* read64位浮点数(占用4个寄存器)
*/
public double readFloat64(int 시작Addr) throws IOException {
int[] regs = readHolding회원가입s(시작Addr, 4);
long bits = ((long)(regs[0] & 0xFFFF) << 48) |
((long)(regs[1] & 0xFFFF) << 32) |
((long)(regs[2] & 0xFFFF) << 16) |
(regs[3] & 0xFFFF);
return Double.longBitsToDouble(bits);
}
/**
* 读取字符串(占用N레지스터s,每个寄存器存2个字符)
*/
public String readString(int 시작Addr, int registerCount) throws IOException {
int[] regs = readHolding회원가입s(시작Addr, registerCount);
StringBuilder sb = new StringBuilder();
for (int reg : regs) {
char high = (char)((reg >> 8) & 0xFF);
char low = (char)(reg & 0xFF);
if (high != 0) sb.append(high);
if (low != 0) sb.append(low);
}
return sb.toString().trim();
}
/**
* 写入32位浮点数
*/
public void writeFloat32(int 시작Addr, float value) throws IOException {
int bits = Float.floatToIntBits(value);
int[] regs = new int[2];
regs[0] = (bits >> 16) & 0xFFFF;
regs[1] = bits & 0xFFFF;
writeMultiple회원가입s(시작Addr, regs);
}
private void ensureConnected() throws IOException {
if (!connected.get() || client == null || !client.isConnected()) {
if (!connect()) {
throw new IOException("PLCNot connected:" + ip주소);
}
}
}
public String getIp주소() { return ip주소; }
public int getPort() { return port; }
public int getUnitId() { return unitId; }
}
三、多PLC管理
package com.industrial.modbus;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 多PLC连接管理器
* 支持多台PLC的统一管理、批量数据采集
*/
public class PlcConnectionManager {
private final Map<String, PlcModbusClient> connections = new ConcurrentHashMap<>();
/**
* 添加PLCconnect
*/
public void addPlc(String name, String ip, int port, int unitId) {
PlcModbusClient client = new PlcModbusClient(ip, port, unitId);
connections.put(name, client);
if (client.connect()) {
client.시작AutoReconnect();
}
}
/**
* 获取PLC客户端
*/
public PlcModbusClient getPlc(String name) {
return connections.get(name);
}
/**
* 批量读取所有PLC的指定寄存器
*/
public Map<String, int[]> batchReadHolding회원가입s(int 시작Addr, int count) {
Map<String, int[]> results = new ConcurrentHashMap<>();
connections.forEach((name, client) -> {
try {
int[] values = client.readHolding회원가입s(시작Addr, count);
results.put(name, values);
} catch (IOException e) {
System.err.printf("readPLC[%s]失败:%s%n", name, e.getMessage());
}
});
return results;
}
/**
* 断开所有连接
*/
public void disconnectAll() {
connections.values().forEach(PlcModbusClient::disconnect);
connections.clear();
}
/**
* 获取连接状态
*/
public Map<String, Boolean> getConnectionStatus() {
Map<String, Boolean> status = new ConcurrentHashMap<>();
connections.forEach((name, client) -> {
status.put(name, client.isConnected());
});
return status;
}
}
四、数据采集服务
package com.industrial.modbus;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
/**
* PLC数据采集服务
* 定时采集PLC데이터,支持数据缓存、变化检测、回调通知
*/
public class Plc데이터Collector {
private final PlcModbusClient client;
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Map<String, Object> 데이터Cache = new ConcurrentHashMap<>();
private final List<데이터ChangeListener> listeners = new 복사OnWriteArrayList<>();
private volatile boolean running = false;
// 采集配置
private final List<CollectItem> collectItems = new ArrayList<>();
public interface 데이터ChangeListener {
void on데이터Changed(String key, Object oldValue, Object newValue);
}
static class CollectItem {
String key;
int 주소;
String type; // INT16, INT32, FLOAT32, COIL
CollectItem(String key, int 주소, String type) {
this.key = key;
this.주소 = 주소;
this.type = type;
}
}
public Plc데이터Collector(PlcModbusClient client) {
this.client = client;
}
/**
* 添加采集项
*/
public void addCollectItem(String key, int 주소, String type) {
collectItems.add(new CollectItem(key, 주소, type));
}
/**
* 添加数据变化监听器
*/
public void add데이터ChangeListener(데이터ChangeListener listener) {
listeners.add(listener);
}
/**
* 启动定时采集
*/
public void 시작(long intervalMs) {
running = true;
scheduler.scheduleAtFixedRate(this::collectAll, 0, intervalMs, TimeUnit.MILLISECONDS);
System.out.println("数据采集服务已启动,间隔:" + intervalMs + "ms");
}
/**
* 停止采集
*/
public void stop() {
running = false;
scheduler.shutdown();
System.out.println("数据采集服务已停止");
}
/**
* 采集所有数据
*/
private void collectAll() {
if (!running) return;
for (CollectItem item : collectItems) {
try {
Object value = readValue(item);
Object oldValue = 데이터Cache.put(item.key, value);
// 检测变化并通知
if (oldValue == null || !oldValue.equals(value)) {
final Object finalOldValue = oldValue;
final Object finalValue = value;
listeners.forEach(l -> l.on데이터Changed(item.key, finalOldValue, finalValue));
}
} catch (Exception e) {
System.err.printf("采集[%s]失败:%s%n", item.key, e.getMessage());
}
}
}
private Object readValue(CollectItem item) throws IOException {
switch (item.type) {
case "INT16":
return client.readHolding회원가입s(item.주소, 1)[0];
case "INT32":
return client.readInt32(item.주소);
case "FLOAT32":
return client.readFloat32(item.주소);
case "COIL":
return client.readCoils(item.주소, 1)[0];
default:
throw new IllegalArgumentException("不支持的数据类型:" + item.type);
}
}
/**
* 获取缓存数据
*/
public Map<String, Object> getAll데이터() {
return new HashMap<>(데이터Cache);
}
/**
* 获取单个数据
*/
public Object get데이터(String key) {
return 데이터Cache.get(key);
}
}
五、完整使用示例
package com.industrial.modbus;
import java.util.Arrays;
import java.util.Map;
public class PlcCommunicationDemo {
public static void main(String[] args) {
// 1. 创建PLC客户端
PlcModbusClient plc = new PlcModbusClient("192.168.1.100", 502, 1);
try {
// 2. connectPLC
if (!plc.connect()) {
System.err.println("无法连接到PLC");
return;
}
plc.시작AutoReconnect();
System.out.println("PLC连接成功!\n");
// 3. Read and hold register
System.out.println("=== Read and hold register ===");
int[] regs = plc.readHolding회원가입s(0, 10);
System.out.println("D0-D9:" + Arrays.toString(regs));
// 4. 读取浮点数
System.out.println("\n=== 读取浮点数 ===");
float temperature = plc.readFloat32(10);
float humidity = plc.readFloat32(12);
System.out.printf("temperature:%.2f℃%n", temperature);
System.out.printf("湿度:%.2f%%%n", humidity);
// 5. read32位整数
System.out.println("\n=== read32位整数 ===");
int counter = plc.readInt32(20);
System.out.println("计数器值:" + counter);
// 6. 读取线圈
System.out.println("\n=== 读取线圈 ===");
boolean[] coils = plc.readCoils(0, 8);
for (int i = 0; i < coils.length; i++) {
System.out.printf("M%d:%s%n", i, coils[i] ? "ON" : "OFF");
}
// 7. Read discrete inputs
System.out.println("\n=== Read discrete inputs ===");
boolean[] inputs = plc.readDiscreteInputs(0, 4);
for (int i = 0; i < inputs.length; i++) {
System.out.printf("X%d:%s%n", i, inputs[i] ? "ON" : "OFF");
}
// 8. 写入寄存器
System.out.println("\n=== 写入寄存器 ===");
plc.writeSingle회원가입(100, 1234);
System.out.println("D100 写入 1234");
int check = plc.readHolding회원가입s(100, 1)[0];
System.out.println("D100 回读:" + check);
// 9. 批量写入
plc.writeMultiple회원가입s(200, new int[]{100, 200, 300, 400});
System.out.println("D200-D203 批量写入完成");
// 10. 写入浮点数
plc.writeFloat32(300, 25.5f);
System.out.println("D300 写入浮点数 25.5");
float readBack = plc.readFloat32(300);
System.out.printf("D300 回读:%.2f%n", readBack);
// 11. 控制线圈
System.out.println("\n=== 控制线圈 ===");
plc.writeSingleCoil(0, true);
System.out.println("M0 置为 ON");
Thread.sleep(500);
plc.writeSingleCoil(0, false);
System.out.println("M0 置为 OFF");
// 12. 批量控制线圈
plc.writeMultipleCoils(10, new boolean[]{true, false, true, false});
System.out.println("M10-M13 批量写入完成");
// 13. 读取字符串
System.out.println("\n=== 读取字符串 ===");
String device이름 = plc.readString(400, 10);
System.out.println("Device 이름:" + device이름);
} catch (Exception e) {
System.err.println("通讯异常:" + e.getMessage());
e.printStackTrace();
} finally {
plc.disconnect();
System.out.println("\nPLC连接已关闭");
}
}
}
六、数据采集服务使用示例
package com.industrial.modbus;
public class 데이터CollectorDemo {
public static void main(String[] args) throws InterruptedException {
// 创建PLC客户端
PlcModbusClient plc = new PlcModbusClient("192.168.1.100", 502, 1);
plc.connect();
plc.시작AutoReconnect();
// 创建数据采集服务
Plc데이터Collector collector = new Plc데이터Collector(plc);
// 添加采集项
collector.addCollectItem("temperature", 10, "FLOAT32");
collector.addCollectItem("humidity", 12, "FLOAT32");
collector.addCollectItem("pressure", 14, "FLOAT32");
collector.addCollectItem("counter", 20, "INT32");
collector.addCollectItem("status", 0, "COIL");
collector.addCollectItem("mode", 50, "INT16");
// 添加数据变化监听器
collector.add데이터ChangeListener((key, oldValue, newValue) -> {
System.out.printf("[数据变化] %s: %s -> %s%n", key, oldValue, newValue);
});
// 启动采集,每1秒采集一次
collector.시작(1000);
// run30秒
Thread.sleep(30000);
// 打印当前所有数据
System.out.println("\n=== 当前数据快照 ===");
collector.getAll데이터().forEach((k, v) -> System.out.printf("%s = %s%n", k, v));
// 停止采集
collector.stop();
plc.disconnect();
}
}
七、Spring Boot集成
package com.industrial.config;
import com.industrial.modbus.PlcModbusClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModbusConfig {
@Value("${modbus.plc.ip:192.168.1.100}")
private String plcIp;
@Value("${modbus.plc.port:502}")
private int plcPort;
@Value("${modbus.plc.unit-id:1}")
private int unitId;
@Bean(destroyMethod = "disconnect")
public PlcModbusClient plcModbusClient() {
PlcModbusClient client = new PlcModbusClient(plcIp, plcPort, unitId);
if (client.connect()) {
client.시작AutoReconnect();
}
return client;
}
}
// Service层
package com.industrial.service;
import com.industrial.modbus.PlcModbusClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class Plc데이터Service {
@Autowired
private PlcModbusClient plc;
/**
* 获取设备运行数据
*/
public Map<String, Object> getDevice데이터() throws IOException {
Map<String, Object> 데이터 = new HashMap<>();
데이터.put("temperature", plc.readFloat32(10));
데이터.put("humidity", plc.readFloat32(12));
데이터.put("pressure", plc.readFloat32(14));
데이터.put("runningHours", plc.readInt32(20));
데이터.put("status", plc.readCoils(0, 1)[0]);
return 데이터;
}
/**
* 设置温度设定值
*/
public void setTemperature(float value) throws IOException {
plc.writeFloat32(100, value);
}
/**
* 启动设备
*/
public void 시작Device() throws IOException {
plc.writeSingleCoil(0, true);
}
/**
* 停止设备
*/
public void stopDevice() throws IOException {
plc.writeSingleCoil(0, false);
}
}
// 컨트롤러层
package com.industrial.controller;
import com.industrial.service.Plc데이터Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Map;
@Rest컨트롤러
@RequestMapping("/api/plc")
public class Plc컨트롤러 {
@Autowired
private Plc데이터Service plcService;
@GetMapping("/데이터")
public Map<String, Object> get데이터() throws IOException {
return plcService.getDevice데이터();
}
@PostMapping("/temperature")
public String setTemperature(@RequestParam float value) throws IOException {
plcService.setTemperature(value);
return "温度设定值已更新为:" + value;
}
@PostMapping("/시작")
public String 시작() throws IOException {
plcService.시작Device();
return "设备已启动";
}
@PostMapping("/stop")
public String stop() throws IOException {
plcService.stopDevice();
return "设备已停止";
}
}
八、常见问题与最佳实践
: 4}8. 1 자주 묻는 질문
- 连接超时
- 返回异常码02
- 返回异常码03
- 浮点数解析错误
- 并发冲突
- 连接断开
- 中文乱码
8. 2 Best Pra cti ces
- 批量读取
- 合理轮询间隔
- 自动重连
- 超时设置
- 异常处理
- 数据缓存
- 日志记录
- 连接池
- 地址规划
- 测试验证
브랜드 별 P LC Mod bus 주소 참조
| 品牌 | ||||
|---|---|---|---|---|
| 西门子S7-200 | ||||
| 三菱FX | ||||
| 台达DVP | ||||
| 欧姆龙CP | ||||
| 施耐德M221 |
이 문서 에서 제공 되는 Java 코 드는 연결 관리 , 자동 재 연 결 , 대 량 읽기 및 쓰 기 , 다양한 데이터 형 식 변환 , 데이터 수집 서비스 및 Spring Boot 통합 을 포함한 P LC 와 의 완전한 Mod bus TCP 통신 기능을 구현 합니다 .이 코 드는 산업 자동 화 프로젝트 개발 에 직접 사용할 수 있으며 , Mod bus Poll 도구를 사용하여 P LC 통신 매 개 변 수 및 주소 매 핑 을 확인 한 다음 Java 코드를 실제 프로젝트에 통합 하는 것이 좋습니다 .
Leave a Reply