xiaoguohua 1 місяць тому
батько
коміт
7705709230

+ 69 - 0
gateway/forecast-modbus-rtu/pom.xml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>forecast-modbus-rtu</artifactId>
+    <description>modbus rtu模块</description>
+
+    <parent>
+        <artifactId>forecast-gateway</artifactId>
+        <groupId>cn.oleauto</groupId>
+        <version>${revision}</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+<!--            <version>2.15.2</version>-->
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+<!--            <version>2.15.2</version>-->
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.infiniteautomation</groupId>
+            <artifactId>modbus4j</artifactId>
+            <version>3.1.1-SNAPSHOT</version>
+        </dependency>
+        <!-- Source: https://mvnrepository.com/artifact/com.fazecast/jSerialComm -->
+        <dependency>
+            <groupId>com.fazecast</groupId>
+            <artifactId>jSerialComm</artifactId>
+            <version>2.10.4</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-buffer</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.oleauto</groupId>
+            <artifactId>forecast-protocol-common</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 69 - 0
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ConfigValidator.java

@@ -0,0 +1,69 @@
+//package cn.oleauto.modbus.tcp.uitl;
+//
+//import cn.oleauto.modbus.tcp.dto.ModbusConfig;
+//import cn.oleauto.modbus.tcp.dto.RegisterConfig;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//import java.util.*;
+//
+//public class ConfigValidator {
+//    private static final Logger logger = LoggerFactory.getLogger(ConfigValidator.class);
+//
+//    /**
+//     * 验证配置完整性
+//     */
+//    public static List<String> validate(ModbusConfig config) {
+//        List<String> errors = new ArrayList<>();
+//
+//        if (config.getDevice() == null) {
+//            errors.add("设备信息不能为空");
+//        } else {
+//            if (config.getDevice().getSlaveId() < 0 || config.getDevice().getSlaveId() > 255) {
+//                errors.add("从站ID必须在0-255之间");
+//            }
+//            if (config.getDevice().getPort() < 1 || config.getDevice().getPort() > 65535) {
+//                errors.add("端口号必须在1-65535之间");
+//            }
+//        }
+//
+//        if (config.getRegisters() == null || config.getRegisters().isEmpty()) {
+//            errors.add("寄存器配置列表不能为空");
+//        } else {
+//            // 检查地址和名称重复
+//            Set<Integer> addresses = new HashSet<>();
+//            Set<String> names = new HashSet<>();
+//
+//            for (RegisterConfig reg : config.getRegisters()) {
+//                if (reg.getName() == null || reg.getName().trim().isEmpty()) {
+//                    errors.add("寄存器名称不能为空");
+//                } else if (!names.add(reg.getName())) {
+//                    errors.add("寄存器名称重复: " + reg.getName());
+//                }
+//
+//                if (reg.getAddress() < 0) {
+//                    errors.add("寄存器地址不能为负数: " + reg.getName());
+//                } else if (!addresses.add(reg.getAddress())) {
+//                    errors.add("寄存器地址重复: " + reg.getName() + " (地址: " + reg.getAddress() + ")");
+//                }
+//
+//                if (reg.getArea() == null) {
+//                    errors.add("寄存器区域不能为空: " + reg.getName());
+//                }
+//
+//                if (reg.getType() == null) {
+//                    errors.add("数据类型不能为空: " + reg.getName());
+//                }
+//            }
+//        }
+//
+//        if (!errors.isEmpty()) {
+//            logger.warn("配置验证发现{}个问题", errors.size());
+//            errors.forEach(logger::warn);
+//        } else {
+//            logger.info("配置验证通过");
+//        }
+//
+//        return errors;
+//    }
+//}

+ 514 - 0
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusDataReader.java

@@ -0,0 +1,514 @@
+//package cn.oleauto.modbus.tcp.uitl;
+//
+//import cn.oleauto.modbus.tcp.dto.*;
+//import cn.oleauto.modbus.tcp.exception.ModbusDataException;
+//import cn.oleauto.modbus.tcp.service.DataParser;
+//import com.serotonin.modbus4j.ModbusMaster;
+//import com.serotonin.modbus4j.exception.ErrorResponseException;
+//import com.serotonin.modbus4j.exception.ModbusTransportException;
+//import com.serotonin.modbus4j.locator.BaseLocator;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//import java.util.*;
+//import java.util.concurrent.ConcurrentHashMap;
+//import java.util.concurrent.ExecutorService;
+//import java.util.concurrent.Executors;
+//import java.util.stream.Collectors;
+//
+//public class ModbusDataReader {
+//    private static final Logger logger = LoggerFactory.getLogger(ModbusDataReader.class);
+//
+//    private final ModbusMasterManager masterManager;
+//    private final Map<String, ParseResult> cacheMap = new ConcurrentHashMap<>();
+//    private final Map<AreaType, List<RegisterConfig>> areaGroupMap = new ConcurrentHashMap<>();
+//    private final Map<String, RegisterConfig> configMap = new ConcurrentHashMap<>();
+//
+//    // 异步读取线程池
+//    private final ExecutorService readExecutor = Executors.newFixedThreadPool(4);
+//
+//    // 统计数据
+//    private long totalReadCount = 0;
+//    private long successReadCount = 0;
+//    private long failedReadCount = 0;
+//
+//    public ModbusDataReader(ModbusMasterManager masterManager) {
+//        this.masterManager = masterManager;
+//    }
+//
+//    /**
+//     * 构建索引
+//     */
+//    public void buildIndex(List<RegisterConfig> registers) {
+//        areaGroupMap.clear();
+//        configMap.clear();
+//
+//        for (RegisterConfig config : registers) {
+//            if (!config.isActive()) continue;
+//
+//            configMap.put(config.getName(), config);
+//            AreaType area = config.getArea();
+//            areaGroupMap.computeIfAbsent(area, k -> new ArrayList<>()).add(config);
+//        }
+//
+//        // 按地址排序
+//        for (List<RegisterConfig> list : areaGroupMap.values()) {
+//            list.sort(Comparator.comparingInt(RegisterConfig::getAddress));
+//        }
+//
+//        logger.info("索引构建完成,共{}个区域分组,{}个配置项",
+//                areaGroupMap.size(), configMap.size());
+//    }
+//
+//    /**
+//     * 读取所有有效数据(同步)
+//     */
+//    public Map<String, ParseResult> readAll() {
+//        Map<String, ParseResult> results = new LinkedHashMap<>();
+//
+//        for (Map.Entry<AreaType, List<RegisterConfig>> entry : areaGroupMap.entrySet()) {
+//            AreaType area = entry.getKey();
+//            List<RegisterConfig> configs = entry.getValue();
+//
+//            try {
+//                Map<String, ParseResult> areaResults = readByArea(area, configs);
+//                results.putAll(areaResults);
+//            } catch (Exception e) {
+//                logger.error("读取区域失败: {}", area, e);
+//                // 为每个配置创建错误结果
+//                for (RegisterConfig config : configs) {
+//                    ParseResult errorResult = new ParseResult();
+//                    errorResult.setName(config.getName());
+//                    errorResult.setAddress(config.getAddress());
+//                    errorResult.setArea(config.getArea());
+//                    errorResult.setType(config.getType());
+//                    errorResult.setSuccess(false);
+//                    errorResult.setErrorMessage("读取失败: " + e.getMessage());
+//                    results.put(config.getName(), errorResult);
+//                }
+//            }
+//        }
+//
+//        // 更新缓存
+//        cacheMap.putAll(results);
+//        updateStatistics(results);
+//
+//        return results;
+//    }
+//
+//    /**
+//     * 异步读取所有数据
+//     */
+//    public void readAllAsync(DataReadCallback callback) {
+//        readExecutor.submit(() -> {
+//            try {
+//                Map<String, ParseResult> results = readAll();
+//                if (callback != null) {
+//                    callback.onSuccess(results);
+//                }
+//            } catch (Exception e) {
+//                logger.error("异步读取失败", e);
+//                if (callback != null) {
+//                    callback.onError(e);
+//                }
+//            }
+//        });
+//    }
+//
+//    /**
+//     * 按区域读取数据
+//     */
+//    private Map<String, ParseResult> readByArea(AreaType area, List<RegisterConfig> configs)
+//            throws ModbusTransportException, ErrorResponseException {
+//
+//        Map<String, ParseResult> results = new LinkedHashMap<>();
+//
+//        if (configs == null || configs.isEmpty()) {
+//            return results;
+//        }
+//
+//        ModbusMaster master = masterManager.getMaster();
+//        int slaveId = getSlaveId();
+//
+//        // 过滤出可读的配置
+//        List<RegisterConfig> readableConfigs = configs.stream()
+//                .filter(RegisterConfig::isReadable)
+//                .collect(Collectors.toList());
+//
+//        if (readableConfigs.isEmpty()) {
+//            return results;
+//        }
+//
+//        // 按连续地址分组
+//        List<List<RegisterConfig>> groups = groupContinuousAddresses(readableConfigs);
+//
+//        for (List<RegisterConfig> group : groups) {
+//            try {
+//                Map<String, ParseResult> groupResults = readGroup(master, slaveId, area, group);
+//                results.putAll(groupResults);
+//            } catch (Exception e) {
+//                logger.error("批量读取组失败: area={}, startAddress={}",
+//                        area, group.get(0).getAddress(), e);
+//
+//                // 回退到单个读取
+//                for (RegisterConfig config : group) {
+//                    try {
+//                        ParseResult result = readSingle(master, slaveId, config);
+//                        if (result != null) {
+//                            results.put(config.getName(), result);
+//                        }
+//                    } catch (Exception ex) {
+//                        logger.error("单个读取失败: {}", config.getName(), ex);
+//                        ParseResult errorResult = createErrorResult(config, ex.getMessage());
+//                        results.put(config.getName(), errorResult);
+//                    }
+//                }
+//            }
+//        }
+//
+//        return results;
+//    }
+//
+//    /**
+//     * 按连续地址分组
+//     */
+//    private List<List<RegisterConfig>> groupContinuousAddresses(List<RegisterConfig> configs) {
+//        List<List<RegisterConfig>> groups = new ArrayList<>();
+//
+//        if (configs == null || configs.isEmpty()) {
+//            return groups;
+//        }
+//
+//        // 按地址排序
+//        List<RegisterConfig> sorted = new ArrayList<>(configs);
+//        sorted.sort(Comparator.comparingInt(RegisterConfig::getAddress));
+//
+//        List<RegisterConfig> currentGroup = new ArrayList<>();
+//
+//        for (int i = 0; i < sorted.size(); i++) {
+//            RegisterConfig current = sorted.get(i);
+//
+//            if (currentGroup.isEmpty()) {
+//                currentGroup.add(current);
+//                continue;
+//            }
+//
+//            RegisterConfig previous = sorted.get(i - 1);
+//            int previousEnd = previous.getAddress() + previous.getType().getRegisterCount();
+//
+//            // 检查地址是否连续(且类型相同)
+//            if (current.getAddress() == previousEnd &&
+//                    current.getType() == previous.getType() &&
+//                    current.getArea() == previous.getArea()) {
+//                currentGroup.add(current);
+//            } else {
+//                if (!currentGroup.isEmpty()) {
+//                    groups.add(new ArrayList<>(currentGroup));
+//                }
+//                currentGroup.clear();
+//                currentGroup.add(current);
+//            }
+//        }
+//
+//        if (!currentGroup.isEmpty()) {
+//            groups.add(currentGroup);
+//        }
+//
+//        return groups;
+//    }
+//
+//    /**
+//     * 批量读取一组配置
+//     */
+//    private Map<String, ParseResult> readGroup(ModbusMaster master, int slaveId,
+//                                               AreaType area, List<RegisterConfig> group)
+//            throws ModbusTransportException, ErrorResponseException {
+//
+//        Map<String, ParseResult> results = new LinkedHashMap<>();
+//        int startAddress = group.get(0).getAddress();
+//
+//        if (area.isBitType()) {
+//            // 读取位类型(线圈/离散输入)
+//            int count = group.size();
+//            boolean[] bitValues;
+//
+//            if (area == AreaType.COIL) {
+//                bitValues = master.getCoils(slaveId, startAddress, count);
+//            } else {
+//                bitValues = master.getDiscreteInputs(slaveId, startAddress, count);
+//            }
+//
+//            for (int i = 0; i < group.size(); i++) {
+//                RegisterConfig config = group.get(i);
+//                boolean value = (i < bitValues.length) ? bitValues[i] : false;
+//                ParseResult result = DataParser.parseBitValue(config, value);
+//                results.put(config.getName(), result);
+//            }
+//
+//        } else {
+//            // 读取寄存器类型
+//            int count = calculateGroupCount(group);
+//            short[] rawData;
+//
+//            if (area == AreaType.HOLDING) {
+//                rawData = master.getHoldingRegisters(slaveId, startAddress, count);
+//            } else {
+//                rawData = master.getInputRegisters(slaveId, startAddress, count);
+//            }
+//
+//            // 解析每个寄存器
+//            for (RegisterConfig config : group) {
+//                ParseResult result = DataParser.parseRegister(config, rawData, startAddress);
+//                if (result != null) {
+//                    results.put(config.getName(), result);
+//                }
+//            }
+//        }
+//
+//        return results;
+//    }
+//
+//    /**
+//     * 计算组需要读取的寄存器数量
+//     */
+//    private int calculateGroupCount(List<RegisterConfig> group) {
+//        if (group.isEmpty()) return 0;
+//        RegisterConfig last = group.get(group.size() - 1);
+//        RegisterConfig first = group.get(0);
+//        return last.getAddress() + last.getType().getRegisterCount() - first.getAddress();
+//    }
+//
+//    /**
+//     * 读取单个数据点
+//     */
+//    private ParseResult readSingle(ModbusMaster master, int slaveId, RegisterConfig config)
+//            throws ModbusTransportException, ErrorResponseException {
+//
+//        if (!config.isReadable()) {
+//            return createErrorResult(config, "配置为只写,不可读");
+//        }
+//
+//        if (config.isBitType()) {
+//            boolean value;
+//            if (config.getArea() == AreaType.COIL) {
+//                value = master.getCoil(slaveId, config.getAddress());
+//            } else {
+//                value = master.getDiscreteInput(slaveId, config.getAddress());
+//            }
+//            return DataParser.parseBitValue(config, value);
+//        } else {
+//            // 寄存器类型
+//            if (config.getType() == DataTypeEnum.BINARY) {
+//                throw new IllegalArgumentException("寄存器不能使用BINARY类型: " + config.getName());
+//            }
+//
+//            BaseLocator<Number> locator = createLocator(slaveId, config);
+//            Number rawValue = master.getValue(locator);
+//            return DataParser.parseNumberValue(config, rawValue);
+//        }
+//    }
+//
+//    /**
+//     * 创建BaseLocator
+//     */
+//    private BaseLocator<Number> createLocator(int slaveId, RegisterConfig config) {
+//        if (config.getArea() == AreaType.HOLDING) {
+//            return BaseLocator.holdingRegister(slaveId, config.getAddress(),
+//                    config.getType().getModbus4jDataType());
+//        } else if (config.getArea() == AreaType.INPUT) {
+//            return BaseLocator.inputRegister(slaveId, config.getAddress(),
+//                    config.getType().getModbus4jDataType());
+//        } else {
+//            throw new IllegalArgumentException("不支持的寄存器区域: " + config.getArea());
+//        }
+//    }
+//
+//    /**
+//     * 读取单个寄存器(公开方法)
+//     */
+//    public ParseResult readRegister(String name) throws ModbusDataException {
+//        RegisterConfig config = configMap.get(name);
+//        if (config == null) {
+//            throw new ModbusDataException("未找到寄存器配置: " + name);
+//        }
+//
+//        try {
+//            ModbusMaster master = masterManager.getMaster();
+//            int slaveId = getSlaveId();
+//            return readSingle(master, slaveId, config);
+//        } catch (ModbusTransportException | ErrorResponseException e) {
+//            throw new ModbusDataException("读取失败: " + name, e, name, config.getAddress());
+//        } catch (Exception e) {
+//            throw new ModbusDataException("读取异常: " + name, e);
+//        }
+//    }
+//
+//    /**
+//     * 读取单个寄存器(带配置对象)
+//     */
+//    public ParseResult readRegister(RegisterConfig config) throws ModbusDataException {
+//        try {
+//            ModbusMaster master = masterManager.getMaster();
+//            int slaveId = getSlaveId();
+//            return readSingle(master, slaveId, config);
+//        } catch (ModbusTransportException | ErrorResponseException e) {
+//            throw new ModbusDataException("读取失败: " + config.getName(), e,
+//                    config.getName(), config.getAddress());
+//        } catch (Exception e) {
+//            throw new ModbusDataException("读取异常: " + config.getName(), e);
+//        }
+//    }
+//
+//    /**
+//     * 读取指定区域的多个寄存器
+//     */
+//    public Map<String, ParseResult> readRegistersByArea(AreaType area) {
+//        List<RegisterConfig> configs = areaGroupMap.get(area);
+//        if (configs == null || configs.isEmpty()) {
+//            return Collections.emptyMap();
+//        }
+//
+//        try {
+//            return readByArea(area, configs);
+//        } catch (Exception e) {
+//            logger.error("读取区域失败: {}", area, e);
+//            Map<String, ParseResult> errorResults = new LinkedHashMap<>();
+//            for (RegisterConfig config : configs) {
+//                errorResults.put(config.getName(), createErrorResult(config, e.getMessage()));
+//            }
+//            return errorResults;
+//        }
+//    }
+//
+//    /**
+//     * 获取缓存的读取结果
+//     */
+//    public ParseResult getCachedResult(String name) {
+//        return cacheMap.get(name);
+//    }
+//
+//    /**
+//     * 获取所有缓存结果
+//     */
+//    public Map<String, ParseResult> getAllCachedResults() {
+//        return new LinkedHashMap<>(cacheMap);
+//    }
+//
+//    /**
+//     * 清除缓存
+//     */
+//    public void clearCache() {
+//        cacheMap.clear();
+//    }
+//
+//    /**
+//     * 获取从站ID
+//     */
+//    private int getSlaveId() {
+//        ModbusConfig config = masterManager.getCurrentConfig();
+//        return config != null && config.getDevice() != null ?
+//                config.getDevice().getSlaveId() : 1;
+//    }
+//
+//    /**
+//     * 创建错误结果
+//     */
+//    private ParseResult createErrorResult(RegisterConfig config, String errorMessage) {
+//        ParseResult result = new ParseResult();
+//        result.setName(config.getName());
+//        result.setAddress(config.getAddress());
+//        result.setArea(config.getArea());
+//        result.setType(config.getType());
+//        result.setUnit(config.getUnit());
+//        result.setSuccess(false);
+//        result.setErrorMessage(errorMessage);
+//        return result;
+//    }
+//
+//    /**
+//     * 更新统计数据
+//     */
+//    private void updateStatistics(Map<String, ParseResult> results) {
+//        totalReadCount++;
+//        long success = results.values().stream().filter(ParseResult::isSuccess).count();
+//        long failed = results.size() - success;
+//
+//        successReadCount += success;
+//        failedReadCount += failed;
+//    }
+//
+//    /**
+//     * 获取统计信息
+//     */
+//    public Map<String, Object> getStatistics() {
+//        Map<String, Object> stats = new HashMap<>();
+//        stats.put("totalReadCount", totalReadCount);
+//        stats.put("successReadCount", successReadCount);
+//        stats.put("failedReadCount", failedReadCount);
+//        stats.put("successRate", totalReadCount > 0 ?
+//                (double) successReadCount / totalReadCount * 100 : 0);
+//        stats.put("cacheSize", cacheMap.size());
+//        stats.put("configCount", configMap.size());
+//        return stats;
+//    }
+//
+//    /**
+//     * 获取所有配置
+//     */
+//    public List<RegisterConfig> getAllConfigs() {
+//        return new ArrayList<>(configMap.values());
+//    }
+//
+//    /**
+//     * 根据名称获取配置
+//     */
+//    public RegisterConfig getConfig(String name) {
+//        return configMap.get(name);
+//    }
+//
+//    /**
+//     * 根据区域获取配置列表
+//     */
+//    public List<RegisterConfig> getConfigsByArea(AreaType area) {
+//        return areaGroupMap.getOrDefault(area, Collections.emptyList());
+//    }
+//
+//    /**
+//     * 检查是否包含指定名称的配置
+//     */
+//    public boolean hasConfig(String name) {
+//        return configMap.containsKey(name);
+//    }
+//
+//    /**
+//     * 获取配置数量
+//     */
+//    public int getConfigCount() {
+//        return configMap.size();
+//    }
+//
+//    /**
+//     * 关闭资源
+//     */
+//    public void shutdown() {
+//        readExecutor.shutdown();
+//        try {
+//            if (!readExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
+//                readExecutor.shutdownNow();
+//            }
+//        } catch (InterruptedException e) {
+//            readExecutor.shutdownNow();
+//            Thread.currentThread().interrupt();
+//        }
+//        clearCache();
+//        logger.info("ModbusDataReader已关闭");
+//    }
+//
+//    /**
+//     * 数据读取回调接口
+//     */
+//    public interface DataReadCallback {
+//        void onSuccess(Map<String, ParseResult> results);
+//        void onError(Exception e);
+//    }
+//}

+ 339 - 0
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusMasterManager.java

@@ -0,0 +1,339 @@
+//package cn.oleauto.modbus.tcp.uitl;
+//
+//import cn.oleauto.modbus.tcp.dto.ModbusConfig;
+//import cn.oleauto.modbus.tcp.exception.ModbusConfigException;
+//import com.serotonin.modbus4j.ModbusFactory;
+//import com.serotonin.modbus4j.ModbusMaster;
+//import com.serotonin.modbus4j.exception.ModbusInitException;
+//import com.serotonin.modbus4j.ip.IpParameters;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//import java.util.Map;
+//import java.util.concurrent.atomic.AtomicBoolean;
+//import java.util.concurrent.atomic.AtomicReference;
+//
+//public class ModbusMasterManager {
+//    private static final Logger logger = LoggerFactory.getLogger(ModbusMasterManager.class);
+//
+//    private final ModbusFactory modbusFactory = new ModbusFactory();
+//    private final AtomicReference<ModbusMaster> masterRef = new AtomicReference<>();
+//    private final AtomicBoolean connected = new AtomicBoolean(false);
+//
+//    // 当前配置
+//    private ModbusConfig currentConfig;
+//
+//    // 连接参数
+//    private String connectionType = "tcp"; // tcp 或 rtu
+//    private String host;
+//    private int port = 502;
+//    private String serialPort;
+//    private int baudRate = 9600;
+//    private int dataBits = 8;
+//    private int stopBits = 1;
+//    private int parity = 0; // 0:无, 1:奇, 2:偶
+//
+//    // 协议参数
+//    private int timeout = 1000;
+//    private int retries = 3;
+//
+//    /**
+//     * 初始化并连接(从配置加载)
+//     */
+//    public void connect(ModbusConfig config) throws ModbusInitException {
+//        this.currentConfig = config;
+//
+//        // 从配置中提取连接参数
+//        if (config.getDevice() != null) {
+//            this.host = config.getDevice().getIpAddress();
+//            this.port = config.getDevice().getPort();
+//        }
+//
+//        if (config.getProtocol() != null) {
+//            this.timeout = config.getProtocol().getTimeout();
+//            this.retries = config.getProtocol().getRetry();
+//        }
+//
+//        ModbusMaster master = createMaster();
+//        master.init();
+//
+//        // 关闭旧的连接
+//        disconnect();
+//
+//        masterRef.set(master);
+//        connected.set(true);
+//
+//        logger.info("Modbus连接成功: {}:{} (从站ID: {})",
+//                host, port, config.getDevice() != null ? config.getDevice().getSlaveId() : 1);
+//    }
+//
+//    /**
+//     * 连接(TCP/IP)
+//     */
+//    public void connectTcp(String host, int port, int slaveId) throws ModbusInitException {
+//        this.host = host;
+//        this.port = port;
+//        this.connectionType = "tcp";
+//
+//        // 创建临时配置
+//        ModbusConfig config = new ModbusConfig();
+//        ModbusConfig.DeviceInfo device = new ModbusConfig.DeviceInfo();
+//        device.setSlaveId(slaveId);
+//        config.setDevice(device);
+//        this.currentConfig = config;
+//
+//        connect(currentConfig);
+//    }
+//
+//    /**
+//     * 连接(RTU串口)
+//     */
+//    public void connectRtu(String serialPort, int baudRate, int slaveId) throws ModbusInitException {
+//        this.serialPort = serialPort;
+//        this.baudRate = baudRate;
+//        this.connectionType = "rtu";
+//
+//        // 创建临时配置
+//        ModbusConfig config = new ModbusConfig();
+//        ModbusConfig.DeviceInfo device = new ModbusConfig.DeviceInfo();
+//        device.setSlaveId(slaveId);
+//        device.setIpAddress(serialPort); // 复用字段存储串口名
+//        config.setDevice(device);
+//        this.currentConfig = config;
+//
+//        ModbusMaster master = createRtuMaster();
+//        master.init();
+//
+//        disconnect();
+//        masterRef.set(master);
+//        connected.set(true);
+//
+//        logger.info("Modbus RTU连接成功: {} (从站ID: {})", serialPort, slaveId);
+//    }
+//
+//    /**
+//     * 创建ModbusMaster实例(TCP/IP)
+//     */
+//    private ModbusMaster createMaster() {
+//        if (host == null || host.isEmpty()) {
+//            throw new ModbusConfigException("主机地址不能为空");
+//        }
+//
+//        IpParameters ipParams = new IpParameters();
+//        ipParams.setHost(host);
+//        ipParams.setPort(port);
+//
+//        ModbusMaster master = modbusFactory.createTcpMaster(ipParams, true);
+//        master.setTimeout(timeout);
+//        master.setRetries(retries);
+//
+//        logger.debug("创建TCP Master: {}:{}", host, port);
+//        return master;
+//    }
+//
+//    /**
+//     * 创建ModbusMaster实例(RTU串口)
+//     */
+//    private ModbusMaster createRtuMaster() {
+////        if (serialPort == null || serialPort.isEmpty()) {
+////            throw new ModbusConfigException("串口名称不能为空");
+////        }
+////
+////        RtuParameters rtuParams = new RtuParameters();
+////        rtuParams.setPort(serialPort);
+////        rtuParams.setBaudRate(baudRate);
+////        rtuParams.setDataBits(dataBits);
+////        rtuParams.setStopBits(stopBits);
+////        rtuParams.setParity(parity);
+////
+////        ModbusMaster master = modbusFactory.createRtuMaster(rtuParams);
+////        master.setTimeout(timeout);
+////        master.setRetries(retries);
+////
+////        logger.debug("创建RTU Master: {} {}bps", serialPort, baudRate);
+////        return master;
+//        return null;
+//    }
+//
+//    /**
+//     * 重新连接
+//     */
+//    public void reconnect() throws ModbusInitException {
+//        if (currentConfig == null) {
+//            throw new IllegalStateException("配置未初始化,请先调用connect()");
+//        }
+//
+//        logger.info("正在重新连接...");
+//        disconnect();
+//        connect(currentConfig);
+//        logger.info("重新连接成功");
+//    }
+//
+//    /**
+//     * 断开连接
+//     */
+//    public void disconnect() {
+//        ModbusMaster master = masterRef.get();
+//        if (master != null) {
+//            try {
+//                master.destroy();
+//                logger.debug("Modbus连接已断开");
+//            } catch (Exception e) {
+//                logger.warn("断开连接时发生错误", e);
+//            }
+//        }
+//        masterRef.set(null);
+//        connected.set(false);
+//    }
+//
+//    /**
+//     * 获取ModbusMaster实例
+//     */
+//    public ModbusMaster getMaster() {
+//        ModbusMaster master = masterRef.get();
+//        if (master == null || !connected.get()) {
+//            throw new IllegalStateException("ModbusMaster未连接,请先调用connect()");
+//        }
+//        return master;
+//    }
+//
+//    /**
+//     * 检查是否已连接
+//     */
+//    public boolean isConnected() {
+//        return connected.get() && masterRef.get() != null;
+//    }
+//
+//    /**
+//     * 获取当前配置
+//     */
+//    public ModbusConfig getCurrentConfig() {
+//        return currentConfig;
+//    }
+//
+//    /**
+//     * 更新配置
+//     */
+//    public void updateConfig(ModbusConfig newConfig) {
+//        this.currentConfig = newConfig;
+//        if (newConfig.getDevice() != null) {
+//            this.host = newConfig.getDevice().getIpAddress();
+//            this.port = newConfig.getDevice().getPort();
+//        }
+//        if (newConfig.getProtocol() != null) {
+//            this.timeout = newConfig.getProtocol().getTimeout();
+//            this.retries = newConfig.getProtocol().getRetry();
+//        }
+//        logger.info("配置已更新");
+//    }
+//
+//    /**
+//     * 设置超时时间
+//     */
+//    public void setTimeout(int timeout) {
+//        this.timeout = timeout;
+//        ModbusMaster master = masterRef.get();
+//        if (master != null) {
+//            master.setTimeout(timeout);
+//        }
+//    }
+//
+//    /**
+//     * 设置重试次数
+//     */
+//    public void setRetries(int retries) {
+//        this.retries = retries;
+//        ModbusMaster master = masterRef.get();
+//        if (master != null) {
+//            master.setRetries(retries);
+//        }
+//    }
+//
+//    /**
+//     * 获取从站ID
+//     */
+//    public int getSlaveId() {
+//        if (currentConfig != null && currentConfig.getDevice() != null) {
+//            return currentConfig.getDevice().getSlaveId();
+//        }
+//        return 1;
+//    }
+//
+//    /**
+//     * 获取连接信息
+//     */
+//    public Map<String, Object> getConnectionInfo() {
+//        Map<String, Object> info = new java.util.HashMap<>();
+//        info.put("connected", isConnected());
+//        info.put("connectionType", connectionType);
+//        info.put("host", host);
+//        info.put("port", port);
+//        info.put("slaveId", getSlaveId());
+//        info.put("timeout", timeout);
+//        info.put("retries", retries);
+//        if ("rtu".equals(connectionType)) {
+//            info.put("serialPort", serialPort);
+//            info.put("baudRate", baudRate);
+//        }
+//        return info;
+//    }
+//
+//    /**
+//     * 测试连接
+//     */
+//    public boolean testConnection() {
+//        try {
+//            ModbusMaster master = getMaster();
+//            int slaveId = getSlaveId();
+//            // 尝试读取一个寄存器测试连接
+//            // master.getHoldingRegisters(slaveId, 0, 1);
+//            return true;
+//        } catch (Exception e) {
+//            logger.warn("连接测试失败", e);
+//            return false;
+//        }
+//    }
+//
+//    /**
+//     * 设置RTU串口参数
+//     */
+//    public void setRtuParameters(String serialPort, int baudRate, int dataBits,
+//                                 int stopBits, int parity) {
+//        this.serialPort = serialPort;
+//        this.baudRate = baudRate;
+//        this.dataBits = dataBits;
+//        this.stopBits = stopBits;
+//        this.parity = parity;
+//        this.connectionType = "rtu";
+//    }
+//
+//    /**
+//     * 设置TCP参数
+//     */
+//    public void setTcpParameters(String host, int port) {
+//        this.host = host;
+//        this.port = port;
+//        this.connectionType = "tcp";
+//    }
+//
+//    /**
+//     * 关闭资源
+//     */
+//    public void destroy() {
+//        disconnect();
+//        logger.info("ModbusMasterManager已销毁");
+//    }
+//
+//    /**
+//     * 获取ModbusFactory实例
+//     */
+//    public ModbusFactory getModbusFactory() {
+//        return modbusFactory;
+//    }
+//
+//    @Override
+//    public String toString() {
+//        return String.format("ModbusMasterManager{connected=%s, host=%s, port=%d, slaveId=%d}",
+//                isConnected(), host, port, getSlaveId());
+//    }
+//}

+ 95 - 0
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusMasterUtil.java

@@ -0,0 +1,95 @@
+package cn.oleauto.modbus.rtu.uitl;
+
+import cn.hutool.core.lang.Pair;
+import cn.hutool.core.lang.Tuple;
+import com.serotonin.modbus4j.BatchRead;
+import com.serotonin.modbus4j.BatchResults;
+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.locator.BaseLocator;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * author:oleauto
+ * date:
+ */
+public final class ModbusMasterUtil {
+
+    private static final ModbusFactory MODBUS_FACTORY = new ModbusFactory();
+
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    public static ModbusMaster createTcpMaster(String host, int port, int timeout, int retries, boolean encapsulated) throws ModbusInitException {
+        IpParameters params = new IpParameters();
+        params.setHost(host);
+        params.setPort(port);
+
+        ModbusMaster master = MODBUS_FACTORY.createTcpMaster(params, encapsulated);
+        master.init();
+        master.setTimeout(timeout);
+        master.setRetries(retries);
+        return master;
+    }
+
+    private static <T> CompletableFuture<T> getValue(ModbusMaster master, BaseLocator<T> loc){
+        return CompletableFuture.supplyAsync(()->{
+            try {
+                return master.getValue(loc);
+            } catch (ModbusTransportException e) {
+                throw new RuntimeException(e);
+            } catch (ErrorResponseException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    public static CompletableFuture<Boolean> readCoil(ModbusMaster master, int slaveId, int offset ){
+        BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
+        return getValue(master, loc);
+    }
+
+    public static CompletableFuture<Boolean> readDiscrete(ModbusMaster master, int slaveId, int offset){
+        BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, offset);
+        return getValue(master, loc);
+    }
+
+    public static CompletableFuture<Number> readInputRegister(ModbusMaster master, int slaveId, int offset, int dataType){
+        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
+        return getValue(master, loc);
+    }
+
+    public static CompletableFuture<Number> readHoldingRegister(ModbusMaster master, int slaveId, int offset, int dataType)
+            throws ModbusTransportException, ErrorResponseException, InterruptedException, TimeoutException {
+        // 03 Holding Register类型数据读取
+        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
+        return getValue(master, loc);
+    }
+
+    public static CompletableFuture<Pair<BatchRead<Integer>, BatchResults<Integer>>> readBatch(ModbusMaster master, BatchRead<Integer> batchRead){
+        return CompletableFuture.supplyAsync(()->{
+            try{
+                BatchResults<Integer> results = master.send(batchRead);
+                return new Pair<>(batchRead, results);
+            } catch (ModbusTransportException e) {
+                throw new RuntimeException(e);
+            } catch (ErrorResponseException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+    public static CompletableFuture<Pair<BatchRead<Integer>, BatchResults<Integer>>> readHoldingRegisters(ModbusMaster master, int slaveId, int offset, int amount, int dataType)
+            throws ModbusTransportException, ErrorResponseException, InterruptedException, TimeoutException{
+        BatchRead<Integer> batchRead = new BatchRead<Integer>();
+        for(int i = 0; i < amount; ++i){
+            batchRead.addLocator(offset + i, BaseLocator.holdingRegister(slaveId, offset+i, dataType));
+        }
+        return readBatch(master, batchRead);
+    }
+}

+ 564 - 0
gateway/forecast-modbus-rtu/src/main/java/cn/oleauto/modbus/rtu/uitl/ModbusUtil.java

@@ -0,0 +1,564 @@
+package cn.oleauto.modbus.rtu.uitl;
+
+import com.serotonin.modbus4j.BatchRead;
+import com.serotonin.modbus4j.BatchResults;
+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.locator.BaseLocator;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Modbus通讯工具类(生产级改进版)
+ * <p>
+ * 核心特性:
+ * 1. 线程安全的连接池管理
+ * 2. 引用计数机制防止提前释放连接
+ * 3. 细粒度的超时控制(连接获取/操作执行)
+ * 4. 自动重试和指数退避策略
+ * 5. 定期清理空闲连接
+ * <p>
+ * 设计原则:
+ * - 每个物理连接对应一个ModbusMaster实例
+ * - 读写操作自动管理连接生命周期
+ * - 强制超时限制防止阻塞
+ * - 异常处理区分网络错误和业务错误
+ *
+ * @author jyy
+ * @data 2025-07-01
+ */
+@Slf4j
+public class ModbusUtil {
+
+    /**
+     * Modbus4j工厂实例(线程安全)
+     */
+    private static final ModbusFactory MODBUS_FACTORY = new ModbusFactory();
+
+    /**
+     * 连接池:host:port -> ModbusMaster实例
+     */
+    private static final Map<String, ModbusMaster> MASTER_MAP = new ConcurrentHashMap<>();
+
+    /**
+     * 引用计数器:host:port -> 当前引用数
+     */
+    private static final Map<String, AtomicInteger> CONNECTION_REF_COUNTS = new ConcurrentHashMap<>();
+
+    /**
+     * 连接锁:host:port -> 专用锁对象(解决惊群效应)
+     */
+    private static final Map<String, ReentrantLock> CONNECTION_LOCKS = new ConcurrentHashMap<>();
+
+    /**
+     * 默认TCP超时3秒
+     */
+    private static final int DEFAULT_TIMEOUT = 3000;
+
+    /**
+     * 默认重试次数
+     */
+    private static final int DEFAULT_RETRIES = 3;
+
+    /**
+     * 默认使用封装模式
+     */
+    private static final boolean DEFAULT_ENCAPSULATED = true;
+
+    /**
+     * 默认操作超时5秒
+     */
+    private static final long DEFAULT_OPERATION_TIMEOUT = 5000;
+
+    /**
+     * 连接等待超时10秒
+     */
+    private static final long CONNECTION_WAIT_TIMEOUT = 10000;
+
+    /**
+     * 连接清理线程(单例)
+     */
+    private static final ScheduledExecutorService CLEANUP_EXECUTOR = Executors.newSingleThreadScheduledExecutor();
+
+    static {
+        /*
+         * 启动定期清理任务(每5分钟执行一次)
+         * 清理策略:
+         * 1. 引用计数为0的空闲连接
+         * 2. 初始化失败或已关闭的连接
+         */
+        CLEANUP_EXECUTOR.scheduleAtFixedRate(
+                ModbusUtil::cleanupIdleConnections,
+                5, 5, TimeUnit.MINUTES);
+
+        // 添加JVM关闭钩子确保资源释放
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            destroyAll();
+            CLEANUP_EXECUTOR.shutdownNow();
+        }));
+    }
+
+
+    // ----------------------------------------------- 内部方法 ---------------------------------------------------------
+
+    /**
+     * 获取ModbusMaster连接(线程安全 + 超时控制)
+     * <p>
+     * 实现要点:
+     * 1. 使用双重检查锁确保单例
+     * 2. 引用计数自动递增
+     * 3. 带超时的锁获取防止死锁
+     *
+     * @param host 设备IP地址
+     * @param port 设备端口
+     * @return 可用的ModbusMaster实例
+     * @throws ModbusTransportException 当连接获取超时或初始化失败时抛出
+     */
+    public static ModbusMaster getMaster(String host, int port) throws ModbusTransportException {
+        String key = getConnectionKey(host, port);
+        ReentrantLock lock = CONNECTION_LOCKS.computeIfAbsent(key, k -> new ReentrantLock(true));
+
+        try {
+            // 尝试获取锁(带超时)
+            if (!lock.tryLock(CONNECTION_WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
+                throw new ModbusTransportException("获取Modbus连接超时: " + key);
+            }
+
+            try {
+                // 增加引用计数(原子操作)
+                CONNECTION_REF_COUNTS.computeIfAbsent(key, k -> new AtomicInteger(0)).incrementAndGet();
+
+                // 双重检查锁创建连接
+                return MASTER_MAP.computeIfAbsent(key, k -> {
+                    try {
+                        ModbusMaster master = createTcpMaster(host, port,
+                                DEFAULT_TIMEOUT, DEFAULT_RETRIES, DEFAULT_ENCAPSULATED);
+                        master.init();
+                        log.info("ModbusMaster连接已创建: {}", key);
+                        return master;
+                    } catch (ModbusInitException e) {
+                        log.error("ModbusMaster初始化失败: {}", key, e);
+                        throw new RuntimeException("ModbusMaster初始化失败", e);
+                    }
+                });
+            } finally {
+                // 释放锁
+                lock.unlock();
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new ModbusTransportException("获取Modbus连接被中断: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 释放连接引用
+     * <p>
+     * 注意:
+     * - 只有引用计数降为0时才实际销毁连接
+     * - 线程安全的递减操作
+     *
+     * @param host host
+     * @param port 端口
+     */
+    public static void release(String host, int port) {
+        String key = getConnectionKey(host, port);
+        AtomicInteger refCount = CONNECTION_REF_COUNTS.get(key);
+
+        if (refCount != null && refCount.decrementAndGet() <= 0) {
+            Object lock = CONNECTION_LOCKS.get(key);
+            if (lock != null) {
+                synchronized (lock) {
+                    // 双重检查防止竞态条件
+                    if (refCount.get() <= 0) {
+                        destroyInternal(host, port);
+                        CONNECTION_REF_COUNTS.remove(key);
+                        CONNECTION_LOCKS.remove(key);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 带超时的任务执行
+     * <p>
+     * 技术要点:
+     * 1. 使用独立线程池执行任务
+     * 2. Future.get()实现超时控制
+     * 3. 异常转换(ExecutionException -> 业务异常)
+     *
+     * @param task    要执行的任务
+     * @param timeout 超时时间
+     * @param unit    时间单位
+     * @return 任务执行结果
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    private static <T> T executeWithTimeout(Callable<T> task, long timeout, TimeUnit unit)
+            throws InterruptedException, TimeoutException, ErrorResponseException, ModbusTransportException {
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        Future<T> future = executor.submit(task);
+
+        try {
+            return future.get(timeout, unit);
+        } catch (ExecutionException e) {
+            // 异常类型转换
+            Throwable cause = e.getCause();
+            if (cause instanceof ModbusTransportException) {
+                throw (ModbusTransportException) cause;
+            }
+            if (cause instanceof ErrorResponseException) {
+                throw (ErrorResponseException) cause;
+            }
+            throw new RuntimeException("Modbus操作执行异常", cause);
+        } finally {
+            // 中断任务
+            future.cancel(true);
+            // 立即释放资源
+            executor.shutdownNow();
+        }
+    }
+
+    /**
+     * 清理空闲连接
+     * <p>
+     * 策略:
+     * 1. 遍历所有连接
+     * 2. 移除引用计数为0且未初始化的连接
+     * 3. 线程安全的移除操作
+     */
+    public static void cleanupIdleConnections() {
+        MASTER_MAP.entrySet().removeIf(entry -> {
+            String key = entry.getKey();
+            if (CONNECTION_REF_COUNTS.getOrDefault(key, new AtomicInteger(0)).get() <= 0) {
+                try {
+                    if (!entry.getValue().isInitialized()) {
+                        entry.getValue().destroy();
+                        log.info("清理空闲Modbus连接: {}", key);
+                        CONNECTION_REF_COUNTS.remove(key);
+                        CONNECTION_LOCKS.remove(key);
+                        return true;
+                    }
+                } catch (Exception e) {
+                    log.warn("清理Modbus连接失败: {}", key, e);
+                }
+            }
+            return false;
+        });
+    }
+
+    /**
+     * 销毁所有连接(系统关闭时调用)
+     */
+    public static void destroyAll() {
+        MASTER_MAP.forEach((key, master) -> {
+            try {
+                master.destroy();
+                log.info("Modbus连接已关闭: {}", key);
+            } catch (Exception e) {
+                log.warn("关闭Modbus连接失败: {}", key, e);
+            }
+        });
+        MASTER_MAP.clear();
+        CONNECTION_REF_COUNTS.clear();
+        CONNECTION_LOCKS.clear();
+    }
+
+    /**
+     * 创建TCP Master连接
+     *
+     * @param host         host
+     * @param port         端口
+     * @param timeout      超时时间
+     * @param retries      重试次数
+     * @param encapsulated 是否封装
+     * @return ModbusMaster
+     */
+    private static ModbusMaster createTcpMaster(String host, int port,
+                                                int timeout, int retries, boolean encapsulated) {
+        IpParameters params = new IpParameters();
+        params.setHost(host);
+        params.setPort(port);
+
+        ModbusMaster master = MODBUS_FACTORY.createTcpMaster(params, encapsulated);
+        master.setTimeout(timeout);
+        master.setRetries(retries);
+        return master;
+    }
+
+    /**
+     * 生成连接键(host:port格式)
+     *
+     * @param host host
+     * @param port 端口
+     * @return 连接键
+     */
+    private static String getConnectionKey(String host, int port) {
+        return host + ":" + port;
+    }
+
+    /**
+     * 内部销毁方法(无锁版本)
+     *
+     * @param host host
+     * @param port 端口
+     */
+    private static void destroyInternal(String host, int port) {
+        String key = getConnectionKey(host, port);
+        ModbusMaster master = MASTER_MAP.remove(key);
+
+        if (master != null) {
+            try {
+                master.destroy();
+                log.info("Modbus连接已关闭: {}", key);
+            } catch (Exception e) {
+                log.warn("关闭Modbus连接失败: {}", key, e);
+            }
+        }
+    }
+
+
+    // ----------------------------------------------- 操作方法 ---------------------------------------------------------
+
+    /**
+     * 带超时和重试的读取操作
+     * <p>
+     * 特性:
+     * 1. 自动管理连接生命周期(try-with-resources模式)
+     * 2. 指数退避重试策略
+     * 3. 精确的超时控制
+     *
+     * @param host       设备IP
+     * @param port       设备端口
+     * @param locator    数据定位器
+     * @param maxRetries 最大重试次数
+     * @param timeoutMs  超时时间(毫秒)
+     * @return 读取到的数据
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    public static <T> T readWithRetry(String host, int port, BaseLocator<T> locator, int maxRetries, long timeoutMs)
+            throws ModbusTransportException,
+            ErrorResponseException, InterruptedException, TimeoutException {
+
+        ModbusMaster master = getMaster(host, port);
+        try {
+            return executeWithTimeout(() -> {
+                int retries = 0;
+                while (true) {
+                    try {
+                        return master.getValue(locator);
+                    } catch (ModbusTransportException e) {
+                        if (retries++ >= maxRetries) {
+                            throw e;
+                        }
+                        log.warn("Modbus读取失败,第{}次重试...错误: {}", retries, e.getMessage());
+                        // 指数退避(最大不超过1秒)
+                        TimeUnit.MILLISECONDS.sleep(Math.min(1000, timeoutMs / maxRetries));
+                    }
+                }
+            }, timeoutMs, TimeUnit.MILLISECONDS);
+        } finally {
+            // 确保释放连接
+            release(host, port);
+        }
+    }
+
+    /**
+     * 带超时和重试的批量读取操作
+     *
+     * @param host      设备IP
+     * @param port      设备端口
+     * @param batchRead 批量读取对象
+     * @param <T>       泛型
+     * @return 批量读取结果
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    public static <T> BatchResults<T> batchReadWithRetry(String host, int port, BatchRead<T> batchRead)
+            throws ModbusTransportException, ErrorResponseException, InterruptedException, TimeoutException {
+        ModbusMaster master = getMaster(host, port);
+        try {
+            return executeWithTimeout(() -> {
+                int retries = 0;
+                while (true) {
+                    try {
+                        return master.send(batchRead);
+                    } catch (ModbusTransportException e) {
+                        if (retries++ >= DEFAULT_RETRIES) {
+                            throw e;
+                        }
+                        log.warn("Modbus读取失败,第{}次重试...错误: {}", retries, e.getMessage());
+                        // 指数退避(最大不超过1秒)
+                        TimeUnit.MILLISECONDS.sleep(Math.min(1000, DEFAULT_OPERATION_TIMEOUT / DEFAULT_RETRIES));
+                    }
+                }
+            }, DEFAULT_OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);
+        } finally {
+            // 确保释放连接
+            release(host, port);
+        }
+    }
+
+    /**
+     * 带超时和重试的写入操作
+     * <p>
+     * 实现逻辑与readWithRetry类似,区别在于:
+     * 1. 使用setValue而非getValue
+     * 2. 返回void类型
+     *
+     * @param host       host
+     * @param port       端口
+     * @param locator    数据定位器
+     * @param value      写入的值
+     * @param maxRetries 最大重试次数
+     * @param timeoutMs  超时时间(毫秒)
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    public static <T> void writeWithRetry(String host, int port, BaseLocator<T> locator, T value, int maxRetries, long timeoutMs)
+            throws ModbusTransportException,
+            ErrorResponseException, InterruptedException, TimeoutException {
+
+        ModbusMaster master = getMaster(host, port);
+        try {
+            executeWithTimeout(() -> {
+                int retries = 0;
+                while (true) {
+                    try {
+                        master.setValue(locator, value);
+                        return null;
+                    } catch (ModbusTransportException e) {
+                        if (retries++ >= maxRetries) {
+                            throw e;
+                        }
+                        log.warn("Modbus写入失败,第{}次重试...错误: {}", retries, e.getMessage());
+                        TimeUnit.MILLISECONDS.sleep(Math.min(1000, timeoutMs / maxRetries));
+                    }
+                }
+            }, timeoutMs, TimeUnit.MILLISECONDS);
+        } finally {
+            release(host, port);
+        }
+    }
+
+    /**
+     * 读取[01 Coil Status 0x]类型 开关数据
+     * 读取线圈状态(简化版,使用默认配置)
+     *
+     * @param slaveId slaveId
+     * @param offset  位置
+     * @return 读取值
+     * @throws ModbusTransportException 异常
+     * @throws ErrorResponseException   异常
+     */
+    public static Boolean readCoilStatus(String host, int port, int slaveId, int offset)
+            throws ModbusTransportException, ErrorResponseException,
+            InterruptedException, TimeoutException {
+        // 01 Coil Status
+        BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
+
+        return readWithRetry(host, port, loc, DEFAULT_RETRIES, DEFAULT_OPERATION_TIMEOUT);
+    }
+
+
+    /**
+     * 读取[02 Input Status 1x]类型 开关数据
+     *
+     * @param slaveId slaveId
+     * @param offset  偏移量
+     * @return Boolean
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    public static Boolean readInputStatus(String host, int port, int slaveId, int offset)
+            throws ModbusTransportException, ErrorResponseException, InterruptedException, TimeoutException {
+        // 02 Input Status
+        BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, offset);
+
+        return readWithRetry(host, port, loc, DEFAULT_RETRIES, DEFAULT_OPERATION_TIMEOUT);
+    }
+
+    /**
+     * 读取[03 Holding Register类型 2x]模拟量数据
+     *
+     * @param slaveId  slave Id
+     * @param offset   位置
+     * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
+     * @return Number
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    public static Number readHoldingRegister(String host, int port, int slaveId, int offset, int dataType)
+            throws ModbusTransportException, ErrorResponseException, InterruptedException, TimeoutException {
+        // 03 Holding Register类型数据读取
+        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
+
+        return readWithRetry(host, port, loc, DEFAULT_RETRIES, DEFAULT_OPERATION_TIMEOUT);
+    }
+
+    /**
+     * 读取[04 Input Registers 3x]类型 模拟量数据
+     *
+     * @param slaveId  slaveId
+     * @param offset   位置
+     * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
+     * @return 返回结果
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    public static Number readInputRegisters(String host, int port, int slaveId, int offset, int dataType)
+            throws ModbusTransportException, ErrorResponseException, InterruptedException, TimeoutException {
+        // 04 Input Registers类型数据读取
+        BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, offset, dataType);
+
+        return readWithRetry(host, port, loc, DEFAULT_RETRIES, DEFAULT_OPERATION_TIMEOUT);
+    }
+
+    /**
+     * 写入数字类型的模拟量(如:写入Float类型的模拟量、Double类型模拟量、整数类型Short、Integer、Long)
+     *
+     * @param host     host
+     * @param port     端口
+     * @param slaveId  设备id
+     * @param offset   偏移量
+     * @param value    写入值,Number的子类,例如写入Float浮点类型,Double双精度类型,以及整型short,int,long
+     * @param dataType com.serotonin.modbus4j.code.DataType
+     * @throws ModbusTransportException Modbus传输异常
+     * @throws ErrorResponseException   错误响应异常
+     * @throws InterruptedException     中断异常
+     * @throws TimeoutException         超时异常
+     */
+    public static void writeHoldingRegister(String host, int port, int slaveId, int offset, Number value, int dataType)
+            throws ModbusTransportException, ErrorResponseException, InterruptedException, TimeoutException {
+
+        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
+
+        writeWithRetry(host, port, loc, value, DEFAULT_RETRIES, DEFAULT_OPERATION_TIMEOUT);
+    }
+}
+
+

+ 8 - 0
spark.txt

@@ -0,0 +1,8 @@
+For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
+Tomcat initialized with port 8080 (http)
+Starting service [Tomcat]
+Starting Servlet engine: [Apache Tomcat/11.0.22]
+Root WebApplicationContext: initialization completed in 812 ms
+Failed to set up a Bean Validation provider: jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
+Tomcat started on port 8080 (http) with context path '/'
+Started SparkStudyApplication in 1.656 seconds (process running f