Commit 3bc1bfe1d738549c40f07438372c4842505d863f

Authored by YunaiV
1 parent 593455a0

feat:【iot】modbus-tcp 协议接入 40%:1)优化包结构;2)增加 ModbusTcpSlaveSimulatorTest 模拟从站设备

Showing 13 changed files with 222 additions and 14 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
... ... @@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.PostMapping;
22 22 import org.springframework.web.bind.annotation.RequestBody;
23 23 import org.springframework.web.bind.annotation.RestController;
24 24  
  25 +import java.math.BigDecimal;
25 26 import java.util.ArrayList;
26 27 import java.util.List;
27 28 import java.util.Map;
... ... @@ -74,6 +75,11 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
74 75 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/modbus/enabled-configs")
75 76 @PermitAll
76 77 public CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs() {
  78 + // TODO @芋艿:临时 mock 数据,用于测试 ModbusTcpSlaveSimulator
  79 + if (true) {
  80 + return success(buildMockModbusConfigs());
  81 + }
  82 +
77 83 // 1. 获取所有启用的 Modbus 连接配置
78 84 List<IotDeviceModbusConfigDO> configList = modbusConfigService.getEnabledDeviceModbusConfigList();
79 85 if (CollUtil.isEmpty(configList)) {
... ... @@ -105,4 +111,74 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
105 111 return success(result);
106 112 }
107 113  
  114 + /**
  115 + * 构建 Mock Modbus 配置,对接 ModbusTcpSlaveSimulator
  116 + *
  117 + * 设备:productKey=4aymZgOTOOCrDKRT, deviceName=small
  118 + * 物模型字段:width, height, oneThree
  119 + */
  120 + private List<IotModbusDeviceConfigRespDTO> buildMockModbusConfigs() {
  121 + List<IotModbusDeviceConfigRespDTO> configs = new ArrayList<>();
  122 +
  123 + // 设备配置
  124 + IotModbusDeviceConfigRespDTO config = new IotModbusDeviceConfigRespDTO();
  125 + config.setDeviceId(1L);
  126 + config.setProductKey("4aymZgOTOOCrDKRT");
  127 + config.setDeviceName("small");
  128 + config.setIp("127.0.0.1");
  129 + config.setPort(5020); // 对应 ModbusTcpSlaveSimulator 的端口
  130 + config.setSlaveId(1);
  131 + config.setTimeout(3000);
  132 + config.setRetryInterval(5000);
  133 +
  134 + // 点位配置(对应物模型字段:width, height, oneThree)
  135 + List<IotModbusPointRespDTO> points = new ArrayList<>();
  136 +
  137 + // 点位 1:width - 读取保持寄存器地址 0(功能码 03)
  138 + IotModbusPointRespDTO point1 = new IotModbusPointRespDTO();
  139 + point1.setId(1L);
  140 + point1.setIdentifier("width");
  141 + point1.setName("宽度");
  142 + point1.setFunctionCode(3); // 读保持寄存器
  143 + point1.setRegisterAddress(0);
  144 + point1.setRegisterCount(1);
  145 + point1.setByteOrder("AB");
  146 + point1.setRawDataType("INT16");
  147 + point1.setScale(BigDecimal.ONE);
  148 + point1.setPollInterval(3000); // 3 秒轮询
  149 + points.add(point1);
  150 +
  151 + // 点位 2:height - 读取保持寄存器地址 1(功能码 03)
  152 + IotModbusPointRespDTO point2 = new IotModbusPointRespDTO();
  153 + point2.setId(2L);
  154 + point2.setIdentifier("height");
  155 + point2.setName("高度");
  156 + point2.setFunctionCode(3); // 读保持寄存器
  157 + point2.setRegisterAddress(1);
  158 + point2.setRegisterCount(1);
  159 + point2.setByteOrder("AB");
  160 + point2.setRawDataType("INT16");
  161 + point2.setScale(BigDecimal.ONE);
  162 + point2.setPollInterval(3000); // 3 秒轮询
  163 + points.add(point2);
  164 +
  165 + // 点位 3:oneThree - 读取保持寄存器地址 2(功能码 03)
  166 + IotModbusPointRespDTO point3 = new IotModbusPointRespDTO();
  167 + point3.setId(3L);
  168 + point3.setIdentifier("oneThree");
  169 + point3.setName("一三");
  170 + point3.setFunctionCode(3); // 读保持寄存器
  171 + point3.setRegisterAddress(2);
  172 + point3.setRegisterCount(1);
  173 + point3.setByteOrder("AB");
  174 + point3.setRawDataType("INT16");
  175 + point3.setScale(BigDecimal.ONE);
  176 + point3.setPollInterval(3000); // 3 秒轮询
  177 + points.add(point3);
  178 +
  179 + config.setPoints(points);
  180 + configs.add(config);
  181 + return configs;
  182 + }
  183 +
108 184 }
109 185 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
... ... @@ -7,6 +7,13 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol
7 7 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;
8 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
9 9 import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.*;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.client.IotModbusTcpClient;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.codec.IotModbusDataConverter;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpConfigCacheService;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpConnectionManager;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpPollScheduler;
  15 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.router.IotModbusTcpDownstreamHandler;
  16 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.router.IotModbusTcpUpstreamHandler;
10 17 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber;
11 18 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
12 19 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpDownstreamSubscriber.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.router.IotModbusTcpDownstreamHandler;
7 8 import jakarta.annotation.PostConstruct;
8 9 import lombok.RequiredArgsConstructor;
9 10 import lombok.extern.slf4j.Slf4j;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpUpstreamProtocol.java
... ... @@ -2,6 +2,10 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
4 4 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpConfigCacheService;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpConnectionManager;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpPollScheduler;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.router.IotModbusTcpUpstreamHandler;
5 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
6 10 import io.vertx.core.Vertx;
7 11 import jakarta.annotation.PostConstruct;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpClient.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/client/IotModbusTcpClient.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.client;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
4 4 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpConnectionManager;
5 6 import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
6 7 import com.ghgande.j2mod.modbus.msg.*;
7 8 import com.ghgande.j2mod.modbus.procimg.InputRegister;
... ... @@ -12,7 +13,9 @@ import io.vertx.core.Future;
12 13 import lombok.extern.slf4j.Slf4j;
13 14  
14 15 /**
15   - * IoT Modbus TCP 客户端,负责:
  16 + * IoT Modbus TCP 客户端
  17 + * <p>
  18 + * 封装 Modbus 协议读写操作:
16 19 * 1. 封装 Modbus 读/写操作
17 20 * 2. 根据功能码,执行对应的 Modbus 请求
18 21 *
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusDataConverter.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/codec/IotModbusDataConverter.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.codec;
2 2  
3 3 import cn.hutool.core.util.ArrayUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
... ... @@ -14,7 +14,9 @@ import java.nio.ByteBuffer;
14 14 import java.nio.ByteOrder;
15 15  
16 16 /**
17   - * IoT Modbus 数据转换器,负责:
  17 + * IoT Modbus 数据转换器
  18 + * <p>
  19 + * 负责 Modbus 原始寄存器值与物模型属性值的相互转换:
18 20 * 1. 将 Modbus 原始寄存器值转换为物模型属性值
19 21 * 2. 将物模型属性值转换为 Modbus 原始寄存器值
20 22 *
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpConfigCacheService.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/manager/IotModbusTcpConfigCacheService.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpConnectionManager.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/manager/IotModbusTcpConnectionManager.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
4 4 import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
... ... @@ -16,7 +16,9 @@ import java.util.Map;
16 16 import java.util.concurrent.ConcurrentHashMap;
17 17  
18 18 /**
19   - * IoT Modbus TCP 连接管理器,负责:
  19 + * IoT Modbus TCP 连接管理器
  20 + * <p>
  21 + * 统一管理 Modbus TCP 连接:
20 22 * 1. 管理 TCP 连接(相同 ip:port 共用连接)
21 23 * 2. 分布式锁管理(连接级别),避免多节点重复创建连接
22 24 * 3. 连接重试和故障恢复
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpPollScheduler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/manager/IotModbusTcpPollScheduler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
5 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.router.IotModbusTcpUpstreamHandler;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.client.IotModbusTcpClient;
6 8 import io.vertx.core.Vertx;
7 9 import lombok.AllArgsConstructor;
8 10 import lombok.Data;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/package-info.java 0 → 100644
  1 +/**
  2 + * Modbus TCP 协议实现包
  3 + * <p>
  4 + * 提供基于 j2mod 的 Modbus TCP 主站(Master)功能,支持:
  5 + * 1. 定时轮询 Modbus 从站设备数据
  6 + * 2. 下发属性设置命令到从站设备
  7 + * 3. 数据格式转换(寄存器值 ↔ 物模型属性值)
  8 + */
  9 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
0 10 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpDownstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/router/IotModbusTcpDownstreamHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.router;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
5 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
6 6 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum;
7 7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.codec.IotModbusDataConverter;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.client.IotModbusTcpClient;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpConfigCacheService;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.manager.IotModbusTcpConnectionManager;
8 12 import lombok.RequiredArgsConstructor;
9 13 import lombok.extern.slf4j.Slf4j;
10 14  
... ... @@ -34,6 +38,7 @@ public class IotModbusTcpDownstreamHandler {
34 38 @SuppressWarnings("unchecked")
35 39 public void handle(IotDeviceMessage message) {
36 40 // 1.1 检查是否是属性设置消息
  41 + // TODO @AI:要使用枚举
37 42 if (!"thing.service.property.set".equals(message.getMethod())) {
38 43 log.debug("[handle][忽略非属性设置消息: {}]", message.getMethod());
39 44 return;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpUpstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/router/IotModbusTcpUpstreamHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.router;
2 2  
  3 +import cn.hutool.core.map.MapUtil;
3 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
4 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
  6 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
5 7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.codec.IotModbusDataConverter;
6 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
7 10 import lombok.Setter;
8 11 import lombok.extern.slf4j.Slf4j;
9 12  
10   -import java.util.HashMap;
11 13 import java.util.Map;
12 14  
13 15 /**
... ... @@ -46,9 +48,8 @@ public class IotModbusTcpUpstreamHandler {
46 48 log.debug("[handleReadResult][设备={}, 属性={}, 原始值={}, 转换值={}]",
47 49 config.getDeviceId(), point.getIdentifier(), rawValue, convertedValue);
48 50 // 1.2 构造属性上报消息
49   - Map<String, Object> params = new HashMap<>();
50   - params.put(point.getIdentifier(), convertedValue);
51   - IotDeviceMessage message = IotDeviceMessage.requestOf("thing.event.property.post", params);
  51 + Map<String, Object> params = MapUtil.of(point.getIdentifier(), convertedValue);
  52 + IotDeviceMessage message = IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), params);
52 53  
53 54 // 2. 发送到消息总线
54 55 messageService.sendDeviceMessage(message, config.getProductKey(),
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/ModbusTcpSlaveSimulatorTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
  2 +
  3 +import com.ghgande.j2mod.modbus.procimg.*;
  4 +import com.ghgande.j2mod.modbus.slave.ModbusSlave;
  5 +import com.ghgande.j2mod.modbus.slave.ModbusSlaveFactory;
  6 +
  7 +/**
  8 + * Modbus TCP 从站模拟器
  9 + *
  10 + * 用于测试 Modbus TCP 网关的连接和数据读写功能
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class ModbusTcpSlaveSimulatorTest {
  15 +
  16 + private static final int PORT = 5020;
  17 + private static final int SLAVE_ID = 1;
  18 +
  19 + @SuppressWarnings({"InfiniteLoopStatement", "BusyWait", "CommentedOutCode"})
  20 + public static void main(String[] args) throws Exception {
  21 + // 1. 创建进程映像(Process Image),存储寄存器数据
  22 + SimpleProcessImage spi = new SimpleProcessImage(SLAVE_ID);
  23 +
  24 + // 2.1 初始化线圈(Coil,功能码 01/05)- 离散输出,可读写
  25 + // 地址 0-9,共 10 个线圈
  26 + for (int i = 0; i < 10; i++) {
  27 + spi.addDigitalOut(new SimpleDigitalOut(i % 2 == 0)); // 交替 true/false
  28 + }
  29 +
  30 + // 2.2 初始化离散输入(Discrete Input,功能码 02)- 只读
  31 + // 地址 0-9,共 10 个离散输入
  32 + for (int i = 0; i < 10; i++) {
  33 + spi.addDigitalIn(new SimpleDigitalIn(i % 3 == 0)); // 每 3 个一个 true
  34 + }
  35 +
  36 + // 2.3 初始化保持寄存器(Holding Register,功能码 03/06/16)- 可读写
  37 + // 地址 0-19,共 20 个寄存器
  38 + for (int i = 0; i < 20; i++) {
  39 + spi.addRegister(new SimpleRegister(i * 100)); // 值为 0, 100, 200, ...
  40 + }
  41 +
  42 + // 2.4 初始化输入寄存器(Input Register,功能码 04)- 只读
  43 + // 地址 0-19,共 20 个寄存器
  44 + SimpleInputRegister[] inputRegisters = new SimpleInputRegister[20];
  45 + for (int i = 0; i < 20; i++) {
  46 + inputRegisters[i] = new SimpleInputRegister(i * 10 + 1); // 值为 1, 11, 21, ...
  47 + spi.addInputRegister(inputRegisters[i]);
  48 + }
  49 +
  50 + // 3.1 创建 Modbus TCP 从站
  51 + ModbusSlave slave = ModbusSlaveFactory.createTCPSlave(PORT, 5);
  52 + slave.addProcessImage(SLAVE_ID, spi);
  53 + // 3.2 启动从站
  54 + slave.open();
  55 +
  56 + System.out.println("===================================================");
  57 + System.out.println("Modbus TCP 从站模拟器已启动");
  58 + System.out.println("端口: " + PORT);
  59 + System.out.println("从站地址 (Slave ID): " + SLAVE_ID);
  60 + System.out.println("===================================================");
  61 + System.out.println("可用寄存器:");
  62 + System.out.println(" - 线圈 (Coil, 功能码 01/05): 地址 0-9");
  63 + System.out.println(" - 离散输入 (Discrete Input, 功能码 02): 地址 0-9");
  64 + System.out.println(" - 保持寄存器 (Holding Register, 功能码 03/06/16): 地址 0-19");
  65 + System.out.println(" - 输入寄存器 (Input Register, 功能码 04): 地址 0-19");
  66 + System.out.println("===================================================");
  67 + System.out.println("按 Ctrl+C 停止模拟器");
  68 +
  69 + // 4. 添加关闭钩子
  70 + Runtime.getRuntime().addShutdownHook(new Thread(() -> {
  71 + System.out.println("\n正在关闭模拟器...");
  72 + slave.close();
  73 + System.out.println("模拟器已关闭");
  74 + }));
  75 +
  76 + // 5. 保持运行,定时更新输入寄存器模拟数据变化
  77 + int counter = 0;
  78 + while (true) {
  79 + Thread.sleep(5000); // 每 5 秒更新一次
  80 + counter++;
  81 +
  82 + // 更新输入寄存器的值,模拟传感器数据变化
  83 + for (int i = 0; i < 20; i++) {
  84 + int newValue = (i * 10 + 1) + counter;
  85 + inputRegisters[i].setValue(newValue);
  86 + }
  87 +
  88 + // 更新保持寄存器的第一个值
  89 + spi.getRegister(0).setValue(counter * 100);
  90 +// System.out.println("[" + java.time.LocalTime.now() + "] 数据已更新, counter=" + counter
  91 +// + ", 保持寄存器[0]=" + (counter * 100)
  92 +// + ", 输入寄存器[0]=" + (1 + counter));
  93 + }
  94 + }
  95 +
  96 +}
... ...