Commit 3bc1bfe1d738549c40f07438372c4842505d863f
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
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
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 | +} | ... | ... |