Commit 5bf63fbd authored by wanghao's avatar wanghao

1 使用 modbus4j + juc 实现 读取层 72个点位的 设备数据测试中。

parent 804418ce
...@@ -4,14 +4,12 @@ import com.serotonin.modbus4j.ModbusMaster; ...@@ -4,14 +4,12 @@ import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException; import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException; import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException; import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.framework.modbus4j.Modbus4jUtils;
import com.zehong.system.domain.TEquipmentAlarmData; import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TEquipmentInfo; import com.zehong.system.domain.TEquipmentInfo;
import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.domain.modbus.ModbusDeviceData; import com.zehong.system.domain.modbus.ModbusDeviceData;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService; import com.zehong.system.service.ITEquipmentAlarmDataService;
import com.zehong.system.service.ITEquipmentInfoService; import com.zehong.system.service.ITEquipmentInfoService;
import com.zehong.system.service.ITStoreyInfoService;
import com.zehong.web.task.AgingCabinetInspectionAndPowerCheckTask; import com.zehong.web.task.AgingCabinetInspectionAndPowerCheckTask;
import com.zehong.web.task.CheckPowerOnCommandEvent; import com.zehong.web.task.CheckPowerOnCommandEvent;
import com.zehong.web.task.PowerOffCommandEvent; import com.zehong.web.task.PowerOffCommandEvent;
...@@ -73,21 +71,6 @@ public class TestTaskController { ...@@ -73,21 +71,6 @@ public class TestTaskController {
* 五分钟一次 * 五分钟一次
* 1.老化柜、标定柜巡查 * 1.老化柜、标定柜巡查
* 2.老化层断电 * 2.老化层断电
* 这种方式先注释掉
* // for (TEquipmentInfo equipmentInfo : equipmentInfos) {
* // Future<Map<Integer, Object>> future = executor.submit(new ModbusTcpTask(equipmentInfo, registerOffset));
* // futures.add(future);
* // }
* // List<ModbusDeviceData> results = new ArrayList<>();
* //
* // for (int i = 0; i < futures.size(); i++) {
* // Map<Integer, Object> data = futures.get(i).get();
* // ModbusDeviceData deviceData = new ModbusDeviceData();
* // deviceData.setDeviceId(equipmentInfos.get(i).getfEquipmentId().toString());
* // deviceData.setRegisterValues(data.entrySet().stream()
* // .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString())));
* // results.add(deviceData);
* // }
*/ */
@GetMapping("/getAgingCabinetAndPowerCheck") @GetMapping("/getAgingCabinetAndPowerCheck")
public DeferredResult<List<ModbusDeviceData>> getAgingCabinetAndPowerCheck() { public DeferredResult<List<ModbusDeviceData>> getAgingCabinetAndPowerCheck() {
......
package com.zehong.web.task; package com.zehong.web.task;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.framework.modbus4j.Modbus4jUtils;
import com.zehong.system.domain.TEquipmentInfo; import com.zehong.system.domain.TEquipmentInfo;
import com.zehong.system.domain.modbus.ModbusDeviceData; import com.zehong.system.domain.modbus.ModbusDeviceData;
import com.zehong.system.service.ITEquipmentInfoService; import com.zehong.system.service.ITEquipmentInfoService;
import com.zehong.web.controller.equipment.EquipmentDataCollection;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/** /**
* @author lenovo * @author lenovo
......
package com.zehong.web.task; package com.zehong.web.task;
import com.serotonin.modbus4j.ModbusMaster;
import com.zehong.framework.modbus4j.Modbus4jUtils;
import com.zehong.system.domain.TEquipmentAlarmData; import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TStoreyInfo; import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.mapper.TStoreyInfoMapper; import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.modbus.business.DeviceStatusReaderAndTimeSetter;
import com.zehong.system.modbus.handler.ModbusResultHandler;
import com.zehong.system.service.ITEquipmentAlarmDataService; import com.zehong.system.service.ITEquipmentAlarmDataService;
import com.zehong.system.service.ITStoreyInfoService;
import org.quartz.Job; import org.quartz.Job;
import org.quartz.JobDataMap; import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author lenovo * @author lenovo
* @date 2025/6/25 * @date 2025/6/25
* @description TODO * @description 上电以后 两分钟执行一次的逻辑
*/ */
@Component @Component
public class DeviceCommunicationJob implements Job { public class DeviceCommunicationJob implements Job {
...@@ -34,7 +27,7 @@ public class DeviceCommunicationJob implements Job { ...@@ -34,7 +27,7 @@ public class DeviceCommunicationJob implements Job {
@Resource @Resource
private ITEquipmentAlarmDataService alarmDataService; private ITEquipmentAlarmDataService alarmDataService;
@Resource @Resource
private ITStoreyInfoService tStoreyInfoService; private DeviceStatusReaderAndTimeSetter deviceStatusReaderAndTimeSetter;
@Resource @Resource
private TStoreyInfoMapper tStoreyInfoMapper; private TStoreyInfoMapper tStoreyInfoMapper;
...@@ -49,35 +42,22 @@ public class DeviceCommunicationJob implements Job { ...@@ -49,35 +42,22 @@ public class DeviceCommunicationJob implements Job {
// 1. 执行Modbus通信 // 1. 执行Modbus通信
String s = tStoreyInfo.getfPort(); String s = tStoreyInfo.getfPort();
String ip = tStoreyInfo.getfIp(); String ip = tStoreyInfo.getfIp();
// 三个端口号,对应三组Modbus通信 ModbusResultHandler resultHandler = new ModbusResultHandler();
String[] split = s.split(","); // 501 的 27个 设备id
List<Integer> registerOffsetsOne = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27);
List<Integer> registerOffsetsOne = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26); // 501 对应 27个设备读取状态 并设置时间
deviceStatusReaderAndTimeSetter.startMultiDeviceMonitoring(ip,501, registerOffsetsOne, resultHandler, ModbusResultHandler.createDefaultStopCondition());
log.info("registerOffsetsOne.ip:{}, port{}", ip, Integer.parseInt(split[0]));
Map<Integer, Object> integerObjectMapOne = Modbus4jUtils.batchReadAgingCabinetStatus(Modbus4jUtils.getMaster(ip, Integer.parseInt(split[0])), registerOffsetsOne); // 502 端口号的 27个 设备id
for (Map.Entry<Integer, Object> integerObjectEntry : integerObjectMapOne.entrySet()) { List<Integer> registerOffsetsTwo = Arrays.asList(28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,54);
log.info("integerObjectMapOne 的 key是:{} value: {}", integerObjectEntry.getKey(), integerObjectEntry.getValue()); // 502 对应 27个设备读取状态 并设置时间
} deviceStatusReaderAndTimeSetter.startMultiDeviceMonitoring(ip, 502, registerOffsetsTwo, resultHandler, ModbusResultHandler.createDefaultStopCondition());
List<Integer> registerOffsetsTwo = Arrays.asList(27,28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53); // 503 端口号的 剩下的设备
List<Integer> registerOffsetsThree = Arrays.asList(55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,72);
log.info("integerObjectMapTwo.ip:{}, port{}", ip, Integer.parseInt(split[1])); // 503 对应 剩下设备读取状态 并设置时间
Map<Integer, Object> integerObjectMapTwo = Modbus4jUtils.batchReadAgingCabinetStatus(Modbus4jUtils.getMaster(ip, Integer.parseInt(split[1])), registerOffsetsTwo); deviceStatusReaderAndTimeSetter.startMultiDeviceMonitoring(ip, 503, registerOffsetsThree, resultHandler, ModbusResultHandler.createDefaultStopCondition());
for (Map.Entry<Integer, Object> integerObjectEntry : integerObjectMapTwo.entrySet()) {
log.info("integerObjectMapTwo 的 key是:{} value: {}", integerObjectEntry.getKey(), integerObjectEntry.getValue());
}
List<Integer> registerOffsetsThree = Arrays.asList(54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71);
Map<Integer, Object> integerObjectMapThree = Modbus4jUtils.batchReadAgingCabinetStatus(Modbus4jUtils.getMaster(ip, Integer.parseInt(split[2])), registerOffsetsThree);
log.info("integerObjectMapThree.ip:{}, port{}", ip, Integer.parseInt(split[2]));
for (Map.Entry<Integer, Object> integerObjectEntry : integerObjectMapThree.entrySet()) {
log.info("integerObjectMapThree 的 key是:{} value: {}", integerObjectEntry.getKey(), integerObjectEntry.getValue());
}
// 2. 检查是否到达特殊时间点
//checkSpecialTimePoints(tStoreyInfo);
} catch (Exception e) { } catch (Exception e) {
// 记录异常 // 记录异常
TEquipmentAlarmData alarmData = new TEquipmentAlarmData(); TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
...@@ -87,24 +67,4 @@ public class DeviceCommunicationJob implements Job { ...@@ -87,24 +67,4 @@ public class DeviceCommunicationJob implements Job {
alarmDataService.insertTEquipmentAlarmData(alarmData); alarmDataService.insertTEquipmentAlarmData(alarmData);
} }
} }
private void checkSpecialTimePoints(TStoreyInfo tStoreyInfo) {
if (tStoreyInfo == null || "COMPLETED".equals(tStoreyInfo.getfStatus())) return;
// 计算已运行小时数
long hours = Duration.between(
tStoreyInfo.getfAgingStartTime().toInstant(), Instant.now()).toHours();
// 特殊时间点处理 (24小时和48小时)
if (hours == 24 || hours == 48) {
log.info("设备[{}]到达特殊时间点: {}小时", tStoreyInfo.getfStoreyCode(), hours);
// 执行特殊操作
performSpecialOperation(tStoreyInfo);
}
}
// ... 其他辅助方法
private void performSpecialOperation(TStoreyInfo tStoreyInfo) {
}
} }
...@@ -4,10 +4,10 @@ import com.serotonin.modbus4j.ModbusMaster; ...@@ -4,10 +4,10 @@ import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException; import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException; import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.common.utils.StringUtils; import com.zehong.common.utils.StringUtils;
import com.zehong.framework.modbus4j.Modbus4jUtils;
import com.zehong.system.domain.TEquipmentAlarmData; import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TStoreyInfo; import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.mapper.TStoreyInfoMapper; import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService; import com.zehong.system.service.ITEquipmentAlarmDataService;
import com.zehong.system.service.ITStoreyInfoService; import com.zehong.system.service.ITStoreyInfoService;
import org.quartz.*; import org.quartz.*;
...@@ -17,9 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -17,9 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/** /**
* @author lenovo * @author lenovo
......
package com.zehong.system.modbus.business;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.msg.ModbusResponse;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.modbus.dto.DeviceStatusReaderDto;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* @author lenovo
* @date 2025/6/27
* @description modbus4j 单独提取出来的 读设备状态并设置时间
*/
@Component
public class DeviceStatusReaderAndTimeSetter {
// 常量改为public以便外部访问
public static final int START_ADDRESS = 0;
public static final int REGISTER_COUNT = 10;
public static final int TARGET_VALUE = 1;
public static final int MAX_RETRIES = 3;
public static final int RETRY_DELAY = 500;
public static final int TIMEOUT_MINUTES = 5;
// 工厂
private static final ModbusFactory modbusFactory = new ModbusFactory();
@Resource
private ITEquipmentAlarmDataService alarmDataService;
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DeviceStatusReaderAndTimeSetter.class);
/**
* 读取设备寄存器(线程安全版)
*/
private int[] readDeviceRegisters(ModbusMaster master, int deviceId )
throws ModbusTransportException {
// 创建读取请求
ReadHoldingRegistersRequest request = Modbus4jUtils.getReadHoldingRegistersRequest(deviceId,START_ADDRESS, REGISTER_COUNT);
// 发送请求并获取响应
ModbusResponse response = master.send(request);
// 检查响应类型
if (!(response instanceof ReadHoldingRegistersResponse)) {
throw new IllegalArgumentException("Invalid response type: " + response.getClass().getName());
}
ReadHoldingRegistersResponse regResponse = (ReadHoldingRegistersResponse) response;
short[] signedValues = regResponse.getShortData();
// 转换为无符号整数
int[] unsignedValues = new int[signedValues.length];
for (int i = 0; i < signedValues.length; i++) {
unsignedValues[i] = signedValues[i] & 0xFFFF;
}
return unsignedValues;
}
/**
* 带自定义条件的重试读取(线程安全版)
*/
private int[] readWithConditionalRetry(ModbusMaster threadMaster,String ip, int port, int deviceId,
Predicate<int[]> conditionChecker)
throws InterruptedException {
TEquipmentAlarmData alarmData;
int[] result = null;
int attempt = 0;
boolean conditionMet = false;
if(threadMaster != null) {
while (attempt <= MAX_RETRIES && !conditionMet) {
attempt++;
try {
log.info("当前 - 尝试第:{}次 读取设备ip:{} port:{} device:{}的数据", attempt, ip,port,deviceId);
// 读取数据
result = readDeviceRegisters(threadMaster, deviceId);
// 使用自定义条件检查
if (conditionChecker.test(result)) {
log.info("当前 设备ip:{} port:{} device:{}的数据条件满足", ip,port,deviceId);
conditionMet = true;
} else if (attempt <= MAX_RETRIES) {
log.info("当前 设备ip:{} port:{} device:{}的数据条件未满足,等待重试...", ip,port,deviceId);
Thread.sleep(RETRY_DELAY);
}
} catch (Exception e) {
alarmData = new TEquipmentAlarmData();
alarmData.setfAlarmType("04"); //01.老化柜 02.机械臂 03.老化层 04.点位
alarmData.setfEquipmentCode("ip:" + ip + ",port:" + port + ",deviceId:" + deviceId);
alarmData.setfAlarmData("读取失败");
alarmDataService.insertTEquipmentAlarmData(alarmData);
if (attempt <= MAX_RETRIES) {
Thread.sleep(RETRY_DELAY);
}
}
}
// 如果达到最大重试次数仍未满足条件
if (!conditionMet) {
log.info("当前 设备ip:{} port:{} device:{}的尝试次数达到最大:{}", ip,port,deviceId,MAX_RETRIES);
}
}
return result != null ? result : new int[REGISTER_COUNT];
}
/**
* 创建新的Modbus连接
*/
private static ModbusMaster createModbusMaster(String ip, int port) throws ModbusInitException {
IpParameters params = new IpParameters();
params.setHost(ip);
params.setPort(port);
ModbusMaster master = modbusFactory.createTcpMaster(params, true);
master.setTimeout(3000);
master.setRetries(1);
master.init();
return master;
}
/**
* 启动多设备监控(核心方法)
*/
public void startMultiDeviceMonitoring(
String ip,
int port,
List<Integer> deviceIds,
Consumer<DeviceStatusReaderDto> resultHandler,
Predicate<int[]> stopCondition ) {
if (deviceIds == null || deviceIds.isEmpty()) {
System.out.println("⚠️ 警告: 设备ID列表为空,不执行监控");
return;
}
final CountDownLatch latch = new CountDownLatch(deviceIds.size());
ExecutorService executor = Executors.newFixedThreadPool(deviceIds.size());
for (int deviceId : deviceIds) {
final int devId = deviceId;
executor.submit(() -> {
ModbusMaster threadMaster = null;
try {
// 1 获取 线程专有的Modbus连接
threadMaster = createModbusMaster(ip, port);
// 2 初始化线程专有的Modbus连接 并尝试读取数据
// 为什么传了 master 还传 ip 和 port ,因为 master 要读完后才释放,而 ip 和 port 是为了log用
int[] result = readWithConditionalRetry(threadMaster,ip, port, devId, stopCondition);
// 创建包含完整信息的结果对象
DeviceStatusReaderDto deviceStatusReaderDto = new DeviceStatusReaderDto(ip, port, devId, result);
// 3 设置回调数据
resultHandler.accept(deviceStatusReaderDto);
} catch (ModbusInitException e) {
TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
alarmData.setfAlarmType("03"); //01.老化柜 02.机械臂 03.老化层 04.点位
alarmData.setfEquipmentCode("ip:" + ip + ",port:" + port);
alarmData.setfAlarmData("定时任务巡检:Modbus初始化失败");
alarmDataService.insertTEquipmentAlarmData(alarmData);
} catch (Exception e) {
TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
alarmData.setfAlarmType("03"); //01.老化柜 02.机械臂 03.老化层 04.点位
alarmData.setfEquipmentCode("ip:" + ip + ",port:" + port);
alarmData.setfAlarmData("监控任务异常:" + e.getMessage());
alarmDataService.insertTEquipmentAlarmData(alarmData);
} finally {
if (threadMaster != null) threadMaster.destroy();
latch.countDown();
log.info("当前 - 设备ip:{} port:{} device:{}的监控任务完成", ip,port,deviceId);
}
});
}
executor.shutdown();
try {
if (!executor.awaitTermination(TIMEOUT_MINUTES, TimeUnit.MINUTES)) {
TEquipmentAlarmData alarmData = new TEquipmentAlarmData();
alarmData.setfAlarmType("03"); //01.老化柜 02.机械臂 03.老化层 04.点位
alarmData.setfEquipmentCode("ip:" + ip + ",port:" + port);
alarmData.setfAlarmData("警告: 部分设备监控任务超时未完成");
alarmDataService.insertTEquipmentAlarmData(alarmData);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
package com.zehong.system.modbus.dto;
import java.util.Arrays;
/**
* @author lenovo
* @date 2025/6/27
* @description TODO
*/
public class DeviceStatusReaderDto {
private String ip;
private int port;
private int deviceId;
private int[] registerData;
public DeviceStatusReaderDto(String ip, int port, int deviceId, int[] registerData) {
this.ip = ip;
this.port = port;
this.deviceId = deviceId;
this.registerData = registerData;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getDeviceId() {
return deviceId;
}
public void setDeviceId(int deviceId) {
this.deviceId = deviceId;
}
public int[] getRegisterData() {
return registerData;
}
public void setRegisterData(int[] registerData) {
this.registerData = registerData;
}
@Override
public String toString() {
return "ModbusResult{" +
"ip='" + ip + '\'' +
", port=" + port +
", deviceId=" + deviceId +
", registerData=" + Arrays.toString(registerData) +
'}';
}
}
package com.zehong.system.modbus.handler;
import com.zehong.system.modbus.business.DeviceStatusReaderAndTimeSetter;
import com.zehong.system.modbus.dto.DeviceStatusReaderDto;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* @author lenovo
* @date 2025/6/27
* @description TODO
*/
public class ModbusResultHandler implements Consumer<DeviceStatusReaderDto> {
@Override
public void accept(DeviceStatusReaderDto deviceStatusReaderDto) {
int deviceId = deviceStatusReaderDto.getDeviceId();
int[] data = deviceStatusReaderDto.getRegisterData();
String ip = deviceStatusReaderDto.getIp();
int port = deviceStatusReaderDto.getPort();
System.out.println(">>> 回调处理: 接收到新数据");
System.out.println(" 数据: " + Arrays.toString(data));
if (data.length >= 2 && data[1] == DeviceStatusReaderAndTimeSetter.TARGET_VALUE) {
System.out.println(" >>> 注意: 第二个寄存器值为1!");
}
}
// 创建通用的停止条件(可选)
public static Predicate<int[]> createDefaultStopCondition() {
return values -> values.length >= 2 && values[1] == DeviceStatusReaderAndTimeSetter.TARGET_VALUE;
}
}
package com.zehong.framework.modbus4j; package com.zehong.system.modbus.util;
import com.serotonin.modbus4j.BatchRead; import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults; import com.serotonin.modbus4j.BatchResults;
...@@ -14,10 +14,8 @@ import com.serotonin.modbus4j.msg.*; ...@@ -14,10 +14,8 @@ import com.serotonin.modbus4j.msg.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Arrays; import java.util.*;
import java.util.HashMap; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.List;
import java.util.Map;
/** /**
* modbus通讯工具类,采用modbus4j实现 * modbus通讯工具类,采用modbus4j实现
...@@ -27,6 +25,19 @@ import java.util.Map; ...@@ -27,6 +25,19 @@ import java.util.Map;
public class Modbus4jUtils { public class Modbus4jUtils {
private static final Logger log = LoggerFactory.getLogger(Modbus4jUtils.class); private static final Logger log = LoggerFactory.getLogger(Modbus4jUtils.class);
private static ModbusMaster master;
private static final int START_ADDRESS = 0; // 对应40001
private static final int REGISTER_COUNT = 10; // 读取10个寄存器
private static final int TARGET_VALUE = 1; // 目标值(第二个寄存器的期望值)
private static final int MAX_RETRIES = 3; // 最大重试次数
private static final int RETRY_DELAY = 500; // 重试延迟(ms)// 监控参数
private static final int MONITOR_INTERVAL = 5000; // 监控间隔(ms)
// 监控控制标志
private static final AtomicBoolean monitoring = new AtomicBoolean(false);
/** /**
* 工厂。 * 工厂。
*/ */
...@@ -45,13 +56,15 @@ public class Modbus4jUtils { ...@@ -45,13 +56,15 @@ public class Modbus4jUtils {
*/ */
public static ModbusMaster getMaster() throws ModbusInitException { public static ModbusMaster getMaster() throws ModbusInitException {
IpParameters params = new IpParameters(); IpParameters params = new IpParameters();
params.setHost("localhost"); params.setHost("192.168.1.101");
params.setPort(502); params.setPort(501);
// //
// modbusFactory.createRtuMaster(wapper); //RTU 协议 // modbusFactory.createRtuMaster(wapper); //RTU 协议
// modbusFactory.createUdpMaster(params);//UDP 协议 // modbusFactory.createUdpMaster(params);//UDP 协议
// modbusFactory.createAsciiMaster(wrapper);//ASCII 协议 // modbusFactory.createAsciiMaster(wrapper);//ASCII 协议
ModbusMaster master = modbusFactory.createTcpMaster(params, false);// TCP 协议 master = modbusFactory.createTcpMaster(params, false);// TCP 协议
master.setTimeout(3000); // 设置超时时间
master.setRetries(3); // 设置重试次数
master.init(); master.init();
return master; return master;
...@@ -69,6 +82,8 @@ public class Modbus4jUtils { ...@@ -69,6 +82,8 @@ public class Modbus4jUtils {
params.setHost(ip); params.setHost(ip);
params.setPort(port); params.setPort(port);
ModbusMaster master = modbusFactory.createTcpMaster(params, false);// TCP 协议 ModbusMaster master = modbusFactory.createTcpMaster(params, false);// TCP 协议
master.setTimeout(3000); // 设置超时时间
master.setRetries(1); // 设置重试次数
master.init(); master.init();
return master; return master;
} }
...@@ -264,6 +279,25 @@ public class Modbus4jUtils { ...@@ -264,6 +279,25 @@ public class Modbus4jUtils {
System.out.println(results.getValue(1)); System.out.println(results.getValue(1));
} }
public static Map<Integer,Object> batchReadHoldingRegister(ModbusMaster master,List<Integer> registerOffsets) throws ModbusTransportException, ErrorResponseException {
BatchRead<Integer> batch = new BatchRead<Integer>();
for (Integer registerOffset : registerOffsets) {
batch.addLocator(registerOffset, BaseLocator.holdingRegister(1, registerOffset,DataType.EIGHT_BYTE_INT_UNSIGNED));
}
// 非连续地址也支持
batch.setContiguousRequests(true);
BatchResults<Integer> send = master.send(batch);
//
Map<Integer, Object> result = new HashMap<>();
for (Integer registerOffset : registerOffsets) {
result.put(registerOffset, send.getValue(registerOffset));
}
return result;
}
/** /**
* 批量读取使用方法 * 批量读取使用方法
* *
...@@ -290,6 +324,62 @@ public class Modbus4jUtils { ...@@ -290,6 +324,62 @@ public class Modbus4jUtils {
return result; return result;
} }
/**
* 读取保持寄存器(16位无符号整数)
*
* @param slaveId 从站ID
* @param offset 寄存器起始地址(0-based)
* @param count 读取的寄存器数量
* @return 无符号整数值数组
* 请求格式解析:
* 00 00 - 事务ID (0)
* 00 00 - 协议ID (0)
* 00 06 - 长度 (6字节)
* 01 - 从站ID (1)
* 03 - 功能码 (读保持寄存器)
* 00 03 - 起始地址 (3)
* 00 0A - 寄存器数量 (10)
* 响应格式解析:
* 00 00 00 00 00 17 - Modbus TCP头
* 01 - 从站ID
* 03 - 功能码
* 14 - 数据字节数 (20字节 = 10寄存器 × 2字节)
* 后续20字节为10个寄存器的数据
* 关键处理逻辑:
* 16位无符号转换:register.getValue() & 0xFFFF
* 寄存器地址处理:modbus4j使用0-based地址,所以地址3对应设备手册中的40004寄存器
* TCP连接管理:使用长连接提高效率
*/
public static int[] readHoldingRegisters(int slaveId, int offset, int count)
throws ModbusTransportException, ErrorResponseException {
// 创建读取请求
ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(
slaveId, offset, count
);
// 发送请求并获取响应
ModbusResponse response = master.send(request);
// 检查响应类型
if (!(response instanceof ReadHoldingRegistersResponse)) {
throw new IllegalArgumentException("Invalid response type: " + response.getClass().getName());
}
// 获取寄存器列表
ReadHoldingRegistersResponse regResponse = (ReadHoldingRegistersResponse) response;
short[] signedValues = regResponse.getShortData();
// 转换为无符号整数
int[] results = new int[signedValues.length];
// 转为无符号整数并打印
for (int i = 0; i < signedValues.length; i++) {
int unsignedValue = signedValues[i] & 0xFFFF;
results[i] = signedValues[i] & 0xFFFF;
System.out.printf("寄存器 %d: %d (无符号) %n", i, unsignedValue);
}
return results;
}
/** /**
* 写 [01 Coil Status(0x)]写一个 function ID = 5 * 写 [01 Coil Status(0x)]写一个 function ID = 5
* *
...@@ -445,6 +535,59 @@ public class Modbus4jUtils { ...@@ -445,6 +535,59 @@ public class Modbus4jUtils {
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType); BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType);
tcpMaster.setValue(locator, value); tcpMaster.setValue(locator, value);
} }
/**
* 关闭Modbus连接
*/
public static void destroyMaster() {
if (master != null) {
master.destroy();
System.out.println("Modbus连接已关闭");
master = null;
}
}
/**
* 获取读取Holding Register请求对象
*
* @param slaveId
* @param offset
* @param count
* @return
* @throws ModbusTransportException
*/
public static ReadHoldingRegistersRequest getReadHoldingRegistersRequest(int slaveId, int offset, int count) throws ModbusTransportException {
return new ReadHoldingRegistersRequest(slaveId, offset, count);
}
/**
* 读取设备寄存器(线程安全版)
*/
private static int[] readDeviceRegisters(int deviceId )
throws ModbusTransportException, ModbusInitException {
ModbusMaster master = getMaster();
// 创建读取请求
ReadHoldingRegistersRequest request = Modbus4jUtils.getReadHoldingRegistersRequest(deviceId,START_ADDRESS, REGISTER_COUNT);
// 发送请求并获取响应
ModbusResponse response = master.send(request);
// 检查响应类型
if (!(response instanceof ReadHoldingRegistersResponse)) {
throw new IllegalArgumentException("Invalid response type: " + response.getClass().getName());
}
ReadHoldingRegistersResponse regResponse = (ReadHoldingRegistersResponse) response;
short[] signedValues = regResponse.getShortData();
// 转换为无符号整数
int[] unsignedValues = new int[signedValues.length];
for (int i = 0; i < signedValues.length; i++) {
unsignedValues[i] = signedValues[i] & 0xFFFF;
}
return unsignedValues;
}
/** /**
* 测试 * 测试
* *
...@@ -452,37 +595,66 @@ public class Modbus4jUtils { ...@@ -452,37 +595,66 @@ public class Modbus4jUtils {
*/ */
public static void main(String[] args) { public static void main(String[] args) {
try { try {
// 01测试
Boolean v011 = readCoilStatus(1, 0); /* 5.写入时间,年、月、日、时、分 */
Boolean v012 = readCoilStatus(1, 1); // 示例:000100000006 设备地址01写时间06 寄存器地址0004(年04月05日06时07分08) 年/月/日/时/分数值07E9(2025)
Boolean v013 = readCoilStatus(1, 6); Calendar cal = Calendar.getInstance();
System.out.println("v011:" + v011); // 当前年
System.out.println("v012:" + v012); int y = cal.get(Calendar.YEAR);
System.out.println("v013:" + v013); // 当前月
// 02测试 int m = cal.get(Calendar.MONTH) + 1;
Boolean v021 = readInputStatus(1, 0); // 当前日
Boolean v022 = readInputStatus(1, 1); int d = cal.get(Calendar.DATE);
Boolean v023 = readInputStatus(1, 2); // 当前小时
System.out.println("v021:" + v021); int h = cal.get(Calendar.HOUR_OF_DAY);
System.out.println("v022:" + v022); // 当前分钟
System.out.println("v023:" + v023); int mm = cal.get(Calendar.MINUTE);
// 03测试 boolean yearResult = writeRegister(1, 0, (short) y);
Number v031 = readHoldingRegister(1, 1, DataType.FOUR_BYTE_FLOAT);// 注意,float boolean mResult = writeRegister(1, 0, (short) m);
Number v032 = readHoldingRegister(1, 3, DataType.FOUR_BYTE_FLOAT);// 同上 boolean dResult = writeRegister(1, 0, (short) d);
System.out.println("v031:" + v031); boolean hResult = writeRegister(1, 0, (short) h);
System.out.println("v032:" + v032); boolean mmResult = writeRegister(1, 0, (short) mm);
// 04测试 ModbusMaster master1 = getMaster();
Number v041 = readInputRegisters(1, 0, DataType.FOUR_BYTE_FLOAT);//
Number v042 = readInputRegisters(1, 2, DataType.FOUR_BYTE_FLOAT);// int[] ints = readDeviceRegisters(1);
System.out.println("v041:" + v041); for (int i = 0; i < ints.length; i++) {
System.out.println("v042:" + v042); System.out.println(ints[i]);
// 批量读取 }
batchRead(); //// 01测试
// Boolean v011 = readCoilStatus(1, 0);
// Boolean v012 = readCoilStatus(1, 1);
// Boolean v013 = readCoilStatus(1, 6);
// System.out.println("v011:" + v011);
// System.out.println("v012:" + v012);
// System.out.println("v013:" + v013);
// // 02测试
// Boolean v021 = readInputStatus(1, 0);
// Boolean v022 = readInputStatus(1, 1);
// Boolean v023 = readInputStatus(1, 2);
// System.out.println("v021:" + v021);
// System.out.println("v022:" + v022);
// System.out.println("v023:" + v023);
//
//// 03测试
// Number v031 = readHoldingRegister(1, 1, DataType.FOUR_BYTE_FLOAT);// 注意,float
// Number v032 = readHoldingRegister(1, 3, DataType.FOUR_BYTE_FLOAT);// 同上
// System.out.println("v031:" + v031);
// System.out.println("v032:" + v032);
//
// // 04测试
// Number v041 = readInputRegisters(1, 0, DataType.FOUR_BYTE_FLOAT);//
// Number v042 = readInputRegisters(1, 2, DataType.FOUR_BYTE_FLOAT);//
// System.out.println("v041:" + v041);
// System.out.println("v042:" + v042);
// // 批量读取
// batchRead();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally {
destroyMaster();
} }
} }
} }
package com.zehong.framework.modbus4j; package com.zehong.system.modbus.util;
import com.serotonin.modbus4j.ModbusMaster; import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException; import com.serotonin.modbus4j.exception.ModbusInitException;
......
package com.zehong.system.modbus.util;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
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.msg.ModbusResponse;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* 独立的 多线程处理 多设备Modbus监控
*/
public class MultiDeviceModbusMonitor {
private static final ModbusFactory modbusFactory = new ModbusFactory();
// 配置参数
private static final String HOST = "192.168.1.101";
private static final int PORT = 501;
private static final int START_ADDRESS = 0; // 对应40001
private static final int REGISTER_COUNT = 10; // 每个设备读取10个寄存器
private static final int MAX_RETRIES = 3; // 最大重试次数
private static final int RETRY_DELAY = 500; // 重试延迟(ms)
private static final int MONITOR_INTERVAL = 2000; // 监控间隔(ms)
private static final int DEVICE_COUNT = 4; // 监控27个设备
// 监控控制标志
private static final AtomicBoolean monitoring = new AtomicBoolean(false);
private static final AtomicInteger activeDevices = new AtomicInteger(0);
// 设备统计信息
private static final Map<Integer, DeviceStats> deviceStats = new HashMap<>();
public static void main(String[] args) {
try {
System.out.println("===== 启动27设备监控系统 =====");
System.out.printf("监控配置: %d设备 | %d寄存器/设备 | %dms间隔%n",
DEVICE_COUNT, REGISTER_COUNT, MONITOR_INTERVAL);
// 初始化设备统计
for (int i = 1; i <= DEVICE_COUNT; i++) {
deviceStats.put(i, new DeviceStats());
}
// 创建结果处理器
Consumer<int[]> resultHandler = data -> {
// 实际应用中可添加业务逻辑
// 这里只做简单演示
};
// 启动监控
monitoring.set(true);
startMultiDeviceMonitoring(resultHandler, values ->
values.length >= 2 && values[1] == 1);
// 主线程等待监控结束
System.out.println("\n主线程运行中...输入命令控制监控:");
System.out.println(" status - 显示设备状态");
System.out.println(" stop - 停止所有监控");
System.out.println(" exit - 退出程序");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (monitoring.get()) {
System.out.print("\n> ");
String command = reader.readLine().trim().toLowerCase();
switch (command) {
case "status":
printDeviceStatus();
break;
case "stop":
System.out.println("停止所有设备监控...");
monitoring.set(false);
break;
case "exit":
monitoring.set(false);
break;
default:
System.out.println("未知命令: " + command);
}
}
// 等待所有监控线程结束
waitForDevicesToStop();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("监控系统已关闭");
}
}
/**
* 启动多设备监控
*/
public static void startMultiDeviceMonitoring(Consumer<int[]> resultHandler,
Predicate<int[]> stopCondition) {
System.out.println("启动设备监控线程...");
// 使用线程池管理27个设备线程
ExecutorService executor = Executors.newFixedThreadPool(DEVICE_COUNT);
for (int deviceId = 1; deviceId <= DEVICE_COUNT; deviceId++) {
final int devId = deviceId;
executor.submit(() -> {
activeDevices.incrementAndGet();
try {
startDeviceMonitor(devId, resultHandler, stopCondition);
} finally {
activeDevices.decrementAndGet();
}
});
}
// 关闭线程池接受新任务,但不影响已提交任务
executor.shutdown();
}
/**
* 启动单个设备监控
*/
public static void startDeviceMonitor(int deviceId, Consumer<int[]> resultHandler,
Predicate<int[]> stopCondition) {
ModbusMaster threadMaster = null;
try {
// 1. 创建线程专用的Modbus连接
threadMaster = createModbusMaster();
DeviceStats stats = deviceStats.get(deviceId);
stats.setStatus("运行中");
System.out.printf("设备 %02d: 监控启动 [%s]%n",
deviceId, Thread.currentThread().getName());
int cycle = 0;
while (monitoring.get()) {
cycle++;
stats.incrementReadCount();
// 2. 读取数据(带重试)
int[] result = readWithConditionalRetry(threadMaster, deviceId, stats);
// 3. 更新统计信息
if (result != null && result.length >= 2) {
stats.updateLastValue(result[1]);
}
// 4. 回调处理结果
resultHandler.accept(result);
// 5. 检查是否应该停止监控
if (stopCondition.test(result)) {
stats.setStatus("条件满足停止");
stats.setConditionMet(true);
System.out.printf("设备 %02d: ✅ 停止条件满足%n", deviceId);
break;
}
// 6. 监控间隔
try {
Thread.sleep(MONITOR_INTERVAL);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
} catch (Exception e) {
System.err.printf("设备 %02d 监控异常: %s%n", deviceId, e.getMessage());
deviceStats.get(deviceId).setStatus("异常: " + e.getClass().getSimpleName());
} finally {
// 7. 关闭线程专用的Modbus连接
if (threadMaster != null) {
threadMaster.destroy();
}
if (!deviceStats.get(deviceId).isConditionMet()) {
deviceStats.get(deviceId).setStatus("手动停止");
}
System.out.printf("设备 %02d: 监控结束%n", deviceId);
}
}
/**
* 打印设备状态
*/
private static void printDeviceStatus() {
System.out.println("\n设备状态概览:");
System.out.println("+-------+-----------+-----------+-----------+-----------------+---------+");
System.out.println("| 设备ID| 读取次数 | 成功次数 | 失败次数 | 最后值(寄存器2) | 状态 |");
System.out.println("+-------+-----------+-----------+-----------+-----------------+---------+");
for (int i = 1; i <= DEVICE_COUNT; i++) {
DeviceStats stats = deviceStats.get(i);
System.out.printf("| %5d | %9d | %9d | %9d | %15d | %-7s |%n",
i,
stats.getReadCount(),
stats.getSuccessCount(),
stats.getErrorCount(),
stats.getLastValue(),
stats.getStatus());
}
System.out.println("+-------+-----------+-----------+-----------+-----------------+---------+");
System.out.printf("活动设备: %d/%d%n", activeDevices.get(), DEVICE_COUNT);
}
/**
* 等待所有设备停止
*/
private static void waitForDevicesToStop() {
System.out.print("等待设备停止");
int waitCount = 0;
while (activeDevices.get() > 0 && waitCount < 30) {
System.out.print(".");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
waitCount++;
}
System.out.println();
if (activeDevices.get() > 0) {
System.out.printf("警告: %d个设备未正常停止%n", activeDevices.get());
}
}
/**
* 创建新的Modbus连接
*/
private static ModbusMaster createModbusMaster() throws ModbusInitException {
IpParameters params = new IpParameters();
params.setHost(HOST);
params.setPort(PORT);
ModbusMaster master = modbusFactory.createTcpMaster(params, true);
master.setTimeout(3000);
master.setRetries(1);
master.init();
return master;
}
/**
* 带自定义条件的重试读取
*/
private static int[] readWithConditionalRetry(ModbusMaster master, int deviceId,
DeviceStats stats) {
int[] result = null;
int attempt = 0;
boolean success = false;
while (attempt <= MAX_RETRIES && monitoring.get()) {
attempt++;
try {
// 读取数据
result = readDeviceRegisters(master, deviceId, START_ADDRESS, REGISTER_COUNT);
stats.incrementSuccessCount();
success = true;
break;
} catch (Exception e) {
stats.incrementErrorCount();
System.err.printf("设备 %02d 尝试 %d/%d 失败: %s%n",
deviceId, attempt, MAX_RETRIES, e.getMessage());
if (attempt <= MAX_RETRIES) {
try {
Thread.sleep(RETRY_DELAY);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
if (!success) {
System.err.printf("设备 %02d: ⚠️ 达到最大重试次数(%d)%n", deviceId, MAX_RETRIES);
}
return result != null ? result : new int[REGISTER_COUNT];
}
/**
* 读取设备寄存器
*/
private static int[] readDeviceRegisters(ModbusMaster master, int deviceId,
int startAddress, int count)
throws ModbusTransportException, ErrorResponseException {
// 创建读取请求
ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(
deviceId, startAddress, count
);
// 发送请求并获取响应
ModbusResponse response = master.send(request);
// 检查响应类型
if (!(response instanceof ReadHoldingRegistersResponse)) {
throw new IllegalArgumentException("Invalid response type: " + response.getClass().getName());
}
ReadHoldingRegistersResponse regResponse = (ReadHoldingRegistersResponse) response;
short[] signedValues = regResponse.getShortData();
// 转换为无符号整数
int[] unsignedValues = new int[signedValues.length];
for (int i = 0; i < signedValues.length; i++) {
unsignedValues[i] = signedValues[i] & 0xFFFF;
}
return unsignedValues;
}
/**
* 设备统计信息类
*/
static class DeviceStats {
private int readCount = 0;
private int successCount = 0;
private int errorCount = 0;
private int lastValue = -1;
private String status = "未启动";
private boolean conditionMet = false;
public void incrementReadCount() {
readCount++;
}
public void incrementSuccessCount() {
successCount++;
}
public void incrementErrorCount() {
errorCount++;
}
public void updateLastValue(int value) {
this.lastValue = value;
}
// Getter 和 Setter 方法
public int getReadCount() { return readCount; }
public int getSuccessCount() { return successCount; }
public int getErrorCount() { return errorCount; }
public int getLastValue() { return lastValue; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public boolean isConditionMet() { return conditionMet; }
public void setConditionMet(boolean conditionMet) {
this.conditionMet = conditionMet;
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment