|
|
@@ -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);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|