Commit ed78834eaf5ca092acdc0c56c72484e3b99578df

Authored by YunaiV
1 parent cece79d0

feat:【iot】modbus-tcp 协议接入:30% 初始化:基于 crystalline-giggling-whisper.md 规划

Showing 22 changed files with 155 additions and 275 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusConfigController.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.device;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO;
7 5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigRespVO;
8 6 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
9 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
... ... @@ -62,12 +60,4 @@ public class IotDeviceModbusConfigController {
62 60 return success(BeanUtils.toBean(modbusConfig, IotDeviceModbusConfigRespVO.class));
63 61 }
64 62  
65   - @GetMapping("/page")
66   - @Operation(summary = "获得设备 Modbus 连接配置分页")
67   - @PreAuthorize("@ss.hasPermission('iot:device-modbus-config:query')")
68   - public CommonResult<PageResult<IotDeviceModbusConfigRespVO>> getDeviceModbusConfigPage(@Valid IotDeviceModbusConfigPageReqVO pageReqVO) {
69   - PageResult<IotDeviceModbusConfigDO> pageResult = modbusConfigService.getDeviceModbusConfigPage(pageReqVO);
70   - return success(BeanUtils.toBean(pageResult, IotDeviceModbusConfigRespVO.class));
71   - }
72   -
73 63 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigPageReqVO.java deleted
1   -package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4   -import io.swagger.v3.oas.annotations.media.Schema;
5   -import lombok.Data;
6   -import lombok.EqualsAndHashCode;
7   -import lombok.ToString;
8   -
9   -// TODO @AI:不需要分页接口;
10   -@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置分页 Request VO")
11   -@Data
12   -@EqualsAndHashCode(callSuper = true)
13   -@ToString(callSuper = true)
14   -public class IotDeviceModbusConfigPageReqVO extends PageParam {
15   -
16   - @Schema(description = "设备编号", example = "1024")
17   - private Long deviceId;
18   -
19   - @Schema(description = "Modbus 服务器 IP 地址", example = "192.168.1.100")
20   - private String ip;
21   -
22   - @Schema(description = "状态", example = "0")
23   - private Integer status;
24   -
25   -}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java
... ... @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device;
2 2  
3 3 import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
4 4 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
5   -import cn.iocoder.yudao.module.iot.enums.device.IotModbusByteOrderEnum;
6   -import cn.iocoder.yudao.module.iot.enums.device.IotModbusFunctionCodeEnum;
7   -import cn.iocoder.yudao.module.iot.enums.device.IotModbusRawDataTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusByteOrderEnum;
  6 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum;
  7 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusRawDataTypeEnum;
8 8 import com.baomidou.mybatisplus.annotation.KeySequence;
9 9 import com.baomidou.mybatisplus.annotation.TableId;
10 10 import com.baomidou.mybatisplus.annotation.TableName;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java
1 1 package cn.iocoder.yudao.module.iot.dal.mysql.device;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO;
7 4 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
8 5 import org.apache.ibatis.annotations.Mapper;
9 6  
... ... @@ -17,14 +14,6 @@ import java.util.List;
17 14 @Mapper
18 15 public interface IotDeviceModbusConfigMapper extends BaseMapperX<IotDeviceModbusConfigDO> {
19 16  
20   - default PageResult<IotDeviceModbusConfigDO> selectPage(IotDeviceModbusConfigPageReqVO reqVO) {
21   - return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceModbusConfigDO>()
22   - .eqIfPresent(IotDeviceModbusConfigDO::getDeviceId, reqVO.getDeviceId())
23   - .likeIfPresent(IotDeviceModbusConfigDO::getIp, reqVO.getIp())
24   - .eqIfPresent(IotDeviceModbusConfigDO::getStatus, reqVO.getStatus())
25   - .orderByDesc(IotDeviceModbusConfigDO::getId));
26   - }
27   -
28 17 default IotDeviceModbusConfigDO selectByDeviceId(Long deviceId) {
29 18 return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId);
30 19 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigService.java
1 1 package cn.iocoder.yudao.module.iot.service.device;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO;
5 3 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
6 4 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
7 5 import jakarta.validation.Valid;
... ... @@ -46,14 +44,6 @@ public interface IotDeviceModbusConfigService {
46 44 IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId);
47 45  
48 46 /**
49   - * 获得设备 Modbus 连接配置分页
50   - *
51   - * @param pageReqVO 分页查询
52   - * @return 设备 Modbus 连接配置分页
53   - */
54   - PageResult<IotDeviceModbusConfigDO> getDeviceModbusConfigPage(IotDeviceModbusConfigPageReqVO pageReqVO);
55   -
56   - /**
57 47 * 获得所有启用的 Modbus 连接配置列表
58 48 *
59 49 * @return 启用的 Modbus 连接配置列表
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.service.device;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO;
7 5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
8 6 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
9 7 import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusConfigMapper;
... ... @@ -73,11 +71,6 @@ public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigSe
73 71 }
74 72  
75 73 @Override
76   - public PageResult<IotDeviceModbusConfigDO> getDeviceModbusConfigPage(IotDeviceModbusConfigPageReqVO pageReqVO) {
77   - return modbusConfigMapper.selectPage(pageReqVO);
78   - }
79   -
80   - @Override
81 74 public List<IotDeviceModbusConfigDO> getEnabledDeviceModbusConfigList() {
82 75 return modbusConfigMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
83 76 }
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java
1 1 package cn.iocoder.yudao.module.iot.core.biz.dto;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusByteOrderEnum;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusRawDataTypeEnum;
3 6 import lombok.Data;
4 7  
5 8 import java.math.BigDecimal;
... ... @@ -27,11 +30,10 @@ public class IotModbusPointRespDTO {
27 30  
28 31 // ========== Modbus 协议配置 ==========
29 32  
30   - // TODO @AI:所有的枚举,通过 @,不要写上去;
31 33 /**
32 34 * Modbus 功能码
33 35 *
34   - * 1-ReadCoils 2-ReadDiscreteInputs 3-ReadHoldingRegisters 4-ReadInputRegisters
  36 + * 枚举 {@link IotModbusFunctionCodeEnum}
35 37 */
36 38 private Integer functionCode;
37 39 /**
... ... @@ -45,13 +47,13 @@ public class IotModbusPointRespDTO {
45 47 /**
46 48 * 字节序
47 49 *
48   - * AB/BA/ABCD/CDAB/DCBA/BADC
  50 + * 枚举 {@link IotModbusByteOrderEnum}
49 51 */
50 52 private String byteOrder;
51 53 /**
52 54 * 原始数据类型
53 55 *
54   - * INT16/UINT16/INT32/UINT32/FLOAT/DOUBLE/BOOLEAN/STRING
  56 + * 枚举 {@link IotModbusRawDataTypeEnum}
55 57 */
56 58 private String rawDataType;
57 59 /**
... ... @@ -63,12 +65,4 @@ public class IotModbusPointRespDTO {
63 65 */
64 66 private Integer pollInterval;
65 67  
66   - // ========== 物模型相关字段 ==========
67   -
68   - // TODO @AI:分析一下,是否有必要返回
69   - /**
70   - * 数据类型(来自物模型)
71   - */
72   - private String dataType;
73   -
74 68 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotModbusByteOrderEnum.java renamed to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusByteOrderEnum.java
1   -package cn.iocoder.yudao.module.iot.enums.device;
  1 +package cn.iocoder.yudao.module.iot.core.enums;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4 4 import lombok.Getter;
... ... @@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor;
6 6  
7 7 import java.util.Arrays;
8 8  
9   -// TODO @AI:如果枚举需要共享,可以拿到 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums 里
10 9 /**
11 10 * IoT Modbus 字节序枚举
12 11 *
... ... @@ -45,13 +44,6 @@ public enum IotModbusByteOrderEnum implements ArrayValuable&lt;String&gt; {
45 44 return ARRAYS;
46 45 }
47 46  
48   - // TODO @AI:如果不需要,可以删除掉这个方法;
49   - /**
50   - * 根据字节序获取枚举
51   - *
52   - * @param order 字节序
53   - * @return 枚举
54   - */
55 47 public static IotModbusByteOrderEnum getByOrder(String order) {
56 48 return Arrays.stream(values())
57 49 .filter(e -> e.getOrder().equals(order))
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotModbusFunctionCodeEnum.java renamed to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusFunctionCodeEnum.java
1   -package cn.iocoder.yudao.module.iot.enums.device;
  1 +package cn.iocoder.yudao.module.iot.core.enums;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4 4 import lombok.Getter;
... ... @@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor;
6 6  
7 7 import java.util.Arrays;
8 8  
9   -// TODO @AI:如果枚举需要共享,可以拿到 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums 里
10 9 /**
11 10 * IoT Modbus 功能码枚举
12 11 *
... ... @@ -51,12 +50,6 @@ public enum IotModbusFunctionCodeEnum implements ArrayValuable&lt;Integer&gt; {
51 50 return ARRAYS;
52 51 }
53 52  
54   - /**
55   - * 根据功能码获取枚举
56   - *
57   - * @param code 功能码
58   - * @return 枚举
59   - */
60 53 public static IotModbusFunctionCodeEnum valueOf(Integer code) {
61 54 return Arrays.stream(values())
62 55 .filter(e -> e.getCode().equals(code))
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotModbusRawDataTypeEnum.java renamed to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusRawDataTypeEnum.java
1   -package cn.iocoder.yudao.module.iot.enums.device;
  1 +package cn.iocoder.yudao.module.iot.core.enums;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4 4 import lombok.Getter;
... ... @@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor;
6 6  
7 7 import java.util.Arrays;
8 8  
9   -// TODO @AI:如果枚举需要共享,可以拿到 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums 里
10 9 /**
11 10 * IoT Modbus 原始数据类型枚举
12 11 *
... ... @@ -23,7 +22,7 @@ public enum IotModbusRawDataTypeEnum implements ArrayValuable&lt;String&gt; {
23 22 FLOAT("FLOAT", "32 位浮点数", 2),
24 23 DOUBLE("DOUBLE", "64 位浮点数", 4),
25 24 BOOLEAN("BOOLEAN", "布尔值(用于线圈)", 1),
26   - STRING("STRING", "字符串", -1); // -1 表示可变长度
  25 + STRING("STRING", "字符串", null); // null 表示可变长度
27 26  
28 27 public static final String[] ARRAYS = Arrays.stream(values())
29 28 .map(IotModbusRawDataTypeEnum::getType)
... ... @@ -37,24 +36,16 @@ public enum IotModbusRawDataTypeEnum implements ArrayValuable&lt;String&gt; {
37 36 * 名称
38 37 */
39 38 private final String name;
40   - // TODO @AI:去掉 default 会好点。null 表示可变;
41 39 /**
42   - * 默认寄存器数量(-1 表示可变)
  40 + * 寄存器数量(null 表示可变)
43 41 */
44   - private final Integer defaultRegisterCount;
  42 + private final Integer registerCount;
45 43  
46 44 @Override
47 45 public String[] array() {
48 46 return ARRAYS;
49 47 }
50 48  
51   - // TODO @AI:如果不用,可以删除掉
52   - /**
53   - * 根据类型获取枚举
54   - *
55   - * @param type 类型
56   - * @return 枚举
57   - */
58 49 public static IotModbusRawDataTypeEnum getByType(String type) {
59 50 return Arrays.stream(values())
60 51 .filter(e -> e.getType().equals(type))
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
... ... @@ -520,7 +520,7 @@ public class IotGatewayProperties {
520 520 /**
521 521 * 配置刷新间隔(秒)
522 522 */
523   - // TODO @AI:需要校验下非空;
  523 + @NotNull(message = "配置刷新间隔不能为空")
524 524 private Integer configRefreshInterval = 30;
525 525  
526 526 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusDataConverter.java
... ... @@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
2 2  
3 3 import cn.hutool.core.util.ArrayUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
5 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
  7 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusByteOrderEnum;
  8 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusRawDataTypeEnum;
6 9 import lombok.extern.slf4j.Slf4j;
7 10  
8 11 import java.math.BigDecimal;
... ... @@ -79,21 +82,25 @@ public class IotModbusDataConverter {
79 82 */
80 83 @SuppressWarnings("EnhancedSwitchMigration")
81 84 private Number parseRawValue(int[] rawValues, String rawDataType, String byteOrder) {
82   - // TODO @AI:是不是可以用枚举?复用 IotModbusRawDataTypeEnum 里的;(保留现有实现,字符串比较已足够清晰)
83   - switch (rawDataType.toUpperCase()) {
84   - case "BOOLEAN":
  85 + IotModbusRawDataTypeEnum dataTypeEnum = IotModbusRawDataTypeEnum.getByType(rawDataType);
  86 + if (dataTypeEnum == null) {
  87 + log.warn("[parseRawValue][不支持的数据类型: {}]", rawDataType);
  88 + return rawValues[0];
  89 + }
  90 + switch (dataTypeEnum) {
  91 + case BOOLEAN:
85 92 return rawValues[0] != 0 ? 1 : 0;
86   - case "INT16":
  93 + case INT16:
87 94 return (short) rawValues[0];
88   - case "UINT16":
  95 + case UINT16:
89 96 return rawValues[0] & 0xFFFF;
90   - case "INT32":
  97 + case INT32:
91 98 return parseInt32(rawValues, byteOrder);
92   - case "UINT32":
  99 + case UINT32:
93 100 return parseUint32(rawValues, byteOrder);
94   - case "FLOAT":
  101 + case FLOAT:
95 102 return parseFloat(rawValues, byteOrder);
96   - case "DOUBLE":
  103 + case DOUBLE:
97 104 return parseDouble(rawValues, byteOrder);
98 105 default:
99 106 log.warn("[parseRawValue][不支持的数据类型: {}]", rawDataType);
... ... @@ -148,24 +155,24 @@ public class IotModbusDataConverter {
148 155 /**
149 156 * 根据字节序重排字节
150 157 */
  158 + @SuppressWarnings("EnhancedSwitchMigration")
151 159 private byte[] reorderBytes(byte[] bytes, String byteOrder) {
152   - // 大端序,不需要调整
153   - // TODO @AI:StrUtil.equals;null 要抛出异常;(保留 null 默认为大端序的兼容逻辑)
154   - if (byteOrder == null || "ABCD".equals(byteOrder) || "AB".equals(byteOrder)) {
  160 + IotModbusByteOrderEnum byteOrderEnum = IotModbusByteOrderEnum.getByOrder(byteOrder);
  161 + // null 或者大端序,不需要调整
  162 + if (ObjectUtils.equalsAny(byteOrderEnum, null, IotModbusByteOrderEnum.ABCD, IotModbusByteOrderEnum.AB)) {
155 163 return bytes;
156 164 }
157 165  
158 166 // 其他字节序调整
159 167 byte[] result = new byte[bytes.length];
160   - switch (byteOrder.toUpperCase()) {
161   - // TODO @AI:走枚举;sortOrder;(参考 IotModbusByteOrderEnum 枚举定义)
162   - case "BA": // 小端序(16 位)
  168 + switch (byteOrderEnum) {
  169 + case BA: // 小端序(16 位)
163 170 if (bytes.length >= 2) {
164 171 result[0] = bytes[1];
165 172 result[1] = bytes[0];
166 173 }
167 174 break;
168   - case "CDAB": // 大端字交换(32 位)
  175 + case CDAB: // 大端字交换(32 位)
169 176 if (bytes.length >= 4) {
170 177 result[0] = bytes[2];
171 178 result[1] = bytes[3];
... ... @@ -173,7 +180,7 @@ public class IotModbusDataConverter {
173 180 result[3] = bytes[1];
174 181 }
175 182 break;
176   - case "DCBA": // 小端序(32 位)
  183 + case DCBA: // 小端序(32 位)
177 184 if (bytes.length >= 4) {
178 185 result[0] = bytes[3];
179 186 result[1] = bytes[2];
... ... @@ -181,7 +188,7 @@ public class IotModbusDataConverter {
181 188 result[3] = bytes[0];
182 189 }
183 190 break;
184   - case "BADC": // 小端字交换(32 位)
  191 + case BADC: // 小端字交换(32 位)
185 192 if (bytes.length >= 4) {
186 193 result[0] = bytes[1];
187 194 result[1] = bytes[0];
... ... @@ -198,20 +205,24 @@ public class IotModbusDataConverter {
198 205 /**
199 206 * 编码为寄存器值
200 207 */
  208 + @SuppressWarnings("EnhancedSwitchMigration")
201 209 private int[] encodeToRegisters(BigDecimal rawValue, String rawDataType, String byteOrder, int registerCount) {
202   - // TODO @AI:是不是可以用枚举?复用 IotModbusRawDataTypeEnum 里的;(保留现有实现,字符串比较已足够清晰)
203   - switch (rawDataType.toUpperCase()) {
204   - case "BOOLEAN":
  210 + IotModbusRawDataTypeEnum dataTypeEnum = IotModbusRawDataTypeEnum.getByType(rawDataType);
  211 + if (dataTypeEnum == null) {
  212 + return new int[]{rawValue.intValue()};
  213 + }
  214 + switch (dataTypeEnum) {
  215 + case BOOLEAN:
205 216 return new int[]{rawValue.intValue() != 0 ? 1 : 0};
206   - case "INT16":
207   - case "UINT16":
  217 + case INT16:
  218 + case UINT16:
208 219 return new int[]{rawValue.intValue() & 0xFFFF};
209   - case "INT32":
210   - case "UINT32":
  220 + case INT32:
  221 + case UINT32:
211 222 return encodeInt32(rawValue.intValue(), byteOrder);
212   - case "FLOAT":
  223 + case FLOAT:
213 224 return encodeFloat(rawValue.floatValue(), byteOrder);
214   - case "DOUBLE":
  225 + case DOUBLE:
215 226 return encodeDouble(rawValue.doubleValue(), byteOrder);
216 227 default:
217 228 return new int[]{rawValue.intValue()};
... ... @@ -247,20 +258,24 @@ public class IotModbusDataConverter {
247 258 /**
248 259 * 格式化返回值
249 260 */
  261 + @SuppressWarnings("EnhancedSwitchMigration")
250 262 private Object formatValue(BigDecimal value, String rawDataType) {
251   - // TODO @AI:是不是可以用枚举?复用 IotModbusRawDataTypeEnum 里的;(保留现有实现,字符串比较已足够清晰)
252   - switch (rawDataType.toUpperCase()) {
253   - case "BOOLEAN":
  263 + IotModbusRawDataTypeEnum dataTypeEnum = IotModbusRawDataTypeEnum.getByType(rawDataType);
  264 + if (dataTypeEnum == null) {
  265 + return value;
  266 + }
  267 + switch (dataTypeEnum) {
  268 + case BOOLEAN:
254 269 return value.intValue() != 0;
255   - case "INT16":
256   - case "INT32":
  270 + case INT16:
  271 + case INT32:
257 272 return value.intValue();
258   - case "UINT16":
259   - case "UINT32":
  273 + case UINT16:
  274 + case UINT32:
260 275 return value.longValue();
261   - case "FLOAT":
  276 + case FLOAT:
262 277 return value.floatValue();
263   - case "DOUBLE":
  278 + case DOUBLE:
264 279 return value.doubleValue();
265 280 default:
266 281 return value;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpClient.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum;
4 5 import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
5 6 import com.ghgande.j2mod.modbus.msg.*;
6 7 import com.ghgande.j2mod.modbus.procimg.InputRegister;
... ... @@ -91,16 +92,20 @@ public class IotModbusTcpClient {
91 92 /**
92 93 * 创建读取请求
93 94 */
  95 + @SuppressWarnings("EnhancedSwitchMigration")
94 96 private ModbusRequest createReadRequest(Integer functionCode, Integer address, Integer count) {
95   - // TODO @AI:1、2、3、4 能不能有枚举哈?这样 1、2、3、4 那的注释就不用写;
96   - switch (functionCode) {
97   - case 1:
  97 + IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode);
  98 + if (functionCodeEnum == null) {
  99 + throw new IllegalArgumentException("不支持的功能码: " + functionCode);
  100 + }
  101 + switch (functionCodeEnum) {
  102 + case READ_COILS:
98 103 return new ReadCoilsRequest(address, count);
99   - case 2: // ReadDiscreteInputs
  104 + case READ_DISCRETE_INPUTS:
100 105 return new ReadInputDiscretesRequest(address, count);
101   - case 3: // ReadHoldingRegisters
  106 + case READ_HOLDING_REGISTERS:
102 107 return new ReadMultipleRegistersRequest(address, count);
103   - case 4: // ReadInputRegisters
  108 + case READ_INPUT_REGISTERS:
104 109 return new ReadInputRegistersRequest(address, count);
105 110 default:
106 111 throw new IllegalArgumentException("不支持的功能码: " + functionCode);
... ... @@ -111,31 +116,33 @@ public class IotModbusTcpClient {
111 116 * 创建写入请求
112 117 */
113 118 private ModbusRequest createWriteRequest(Integer functionCode, Integer address, Integer count, int[] values) {
114   - // TODO @AI:5、6、15、16 能不能有枚举哈?这样 5、6、15、16 那的注释就不用写;
115   - switch (functionCode) {
116   - case 1: // WriteCoils (使用 5 或 15)
  119 + IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode);
  120 + if (functionCodeEnum == null) {
  121 + throw new IllegalArgumentException("不支持的功能码: " + functionCode);
  122 + }
  123 + switch (functionCodeEnum) {
  124 + case READ_COILS: // 写线圈(使用功能码 5 或 15)
117 125 if (count == 1) {
118 126 return new WriteCoilRequest(address, values[0] != 0);
119 127 } else {
120   - // 多线圈写入
121 128 BitVector bv = new BitVector(count);
122 129 for (int i = 0; i < Math.min(values.length, count); i++) {
123 130 bv.setBit(i, values[i] != 0);
124 131 }
125 132 return new WriteMultipleCoilsRequest(address, bv);
126 133 }
127   - case 3: // WriteHoldingRegisters (使用 6 或 16)
  134 + case READ_HOLDING_REGISTERS: // 写保持寄存器(使用功能码 6 或 16)
128 135 if (count == 1) {
129 136 return new WriteSingleRegisterRequest(address, new SimpleRegister(values[0]));
130 137 } else {
131   - Register[] registers = new com.ghgande.j2mod.modbus.procimg.SimpleRegister[count];
  138 + Register[] registers = new SimpleRegister[count];
132 139 for (int i = 0; i < count; i++) {
133 140 registers[i] = new SimpleRegister(i < values.length ? values[i] : 0);
134 141 }
135 142 return new WriteMultipleRegistersRequest(address, registers);
136 143 }
137   - case 2: // ReadDiscreteInputs - 只读
138   - case 4: // ReadInputRegisters - 只读
  144 + case READ_DISCRETE_INPUTS: // 只读
  145 + case READ_INPUT_REGISTERS: // 只读
139 146 return null;
140 147 default:
141 148 throw new IllegalArgumentException("不支持的功能码: " + functionCode);
... ... @@ -146,9 +153,12 @@ public class IotModbusTcpClient {
146 153 * 从响应中提取值
147 154 */
148 155 private int[] extractValues(ModbusResponse response, Integer functionCode) {
149   - // TODO @AI:1、2、3、4 能不能有枚举哈?这样 1、2、3、4 那的注释就不用写;
150   - switch (functionCode) {
151   - case 1:
  156 + IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode);
  157 + if (functionCodeEnum == null) {
  158 + throw new IllegalArgumentException("不支持的功能码: " + functionCode);
  159 + }
  160 + switch (functionCodeEnum) {
  161 + case READ_COILS:
152 162 ReadCoilsResponse coilsResponse = (ReadCoilsResponse) response;
153 163 int bitCount = coilsResponse.getBitCount();
154 164 int[] coilValues = new int[bitCount];
... ... @@ -156,7 +166,7 @@ public class IotModbusTcpClient {
156 166 coilValues[i] = coilsResponse.getCoilStatus(i) ? 1 : 0;
157 167 }
158 168 return coilValues;
159   - case 2: // ReadDiscreteInputs
  169 + case READ_DISCRETE_INPUTS:
160 170 ReadInputDiscretesResponse discretesResponse = (ReadInputDiscretesResponse) response;
161 171 int discreteCount = discretesResponse.getBitCount();
162 172 int[] discreteValues = new int[discreteCount];
... ... @@ -164,7 +174,7 @@ public class IotModbusTcpClient {
164 174 discreteValues[i] = discretesResponse.getDiscreteStatus(i) ? 1 : 0;
165 175 }
166 176 return discreteValues;
167   - case 3: // ReadHoldingRegisters
  177 + case READ_HOLDING_REGISTERS:
168 178 ReadMultipleRegistersResponse holdingResponse = (ReadMultipleRegistersResponse) response;
169 179 InputRegister[] holdingRegisters = holdingResponse.getRegisters();
170 180 int[] holdingValues = new int[holdingRegisters.length];
... ... @@ -172,7 +182,7 @@ public class IotModbusTcpClient {
172 182 holdingValues[i] = holdingRegisters[i].getValue();
173 183 }
174 184 return holdingValues;
175   - case 4: // ReadInputRegisters
  185 + case READ_INPUT_REGISTERS:
176 186 ReadInputRegistersResponse inputResponse = (ReadInputRegistersResponse) response;
177 187 InputRegister[] inputRegisters = inputResponse.getRegisters();
178 188 int[] inputValues = new int[inputRegisters.length];
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpConfigCacheService.java
... ... @@ -29,7 +29,9 @@ public class IotModbusTcpConfigCacheService {
29 29 private final Map<Long, IotModbusDeviceConfigRespDTO> configCache = new ConcurrentHashMap<>();
30 30  
31 31 /**
32   - * 已知的设备 ID 集合
  32 + * 已知的设备 ID 集合(作用:用于检测已删除的设备)
  33 + *
  34 + * @see #cleanupRemovedDevices(List, Consumer)
33 35 */
34 36 private final Set<Long> knownDeviceIds = ConcurrentHashMap.newKeySet();
35 37  
... ... @@ -48,10 +50,9 @@ public class IotModbusTcpConfigCacheService {
48 50 }
49 51 List<IotModbusDeviceConfigRespDTO> configs = result.getData();
50 52  
51   - // 2. 更新缓存
  53 + // 2. 更新缓存(注意:不在这里更新 knownDeviceIds,由 cleanupRemovedDevices 统一管理)
52 54 for (IotModbusDeviceConfigRespDTO config : configs) {
53 55 configCache.put(config.getDeviceId(), config);
54   - knownDeviceIds.add(config.getDeviceId());
55 56 }
56 57 return configs;
57 58 } catch (Exception e) {
... ... @@ -70,9 +71,8 @@ public class IotModbusTcpConfigCacheService {
70 71 return configCache.get(deviceId);
71 72 }
72 73  
73   - // TODO @AI:怎么感觉 cleanupRemovedDevices 的时候,knownDeviceIds 已经在 refreshConfig 里更新了???
74 74 /**
75   - * 清理已删除设备的资源
  75 + * 清理已删除设备的资源,并更新已知设备 ID 集合
76 76 *
77 77 * @param currentConfigs 当前有效的配置列表
78 78 * @param cleanupAction 清理动作
... ... @@ -80,7 +80,7 @@ public class IotModbusTcpConfigCacheService {
80 80 public void cleanupRemovedDevices(List<IotModbusDeviceConfigRespDTO> currentConfigs, Consumer<Long> cleanupAction) {
81 81 // 1.1 获取当前有效的设备 ID
82 82 Set<Long> currentDeviceIds = convertSet(currentConfigs, IotModbusDeviceConfigRespDTO::getDeviceId);
83   - // 1.2 找出已删除的设备
  83 + // 1.2 找出已删除的设备(基于旧的 knownDeviceIds)
84 84 Set<Long> removedDeviceIds = new HashSet<>(knownDeviceIds);
85 85 removedDeviceIds.removeAll(currentDeviceIds);
86 86  
... ... @@ -88,9 +88,12 @@ public class IotModbusTcpConfigCacheService {
88 88 for (Long deviceId : removedDeviceIds) {
89 89 log.info("[cleanupRemovedDevices][清理已删除设备: {}]", deviceId);
90 90 configCache.remove(deviceId);
91   - knownDeviceIds.remove(deviceId);
92 91 cleanupAction.accept(deviceId);
93 92 }
  93 +
  94 + // 3. 更新已知设备 ID 集合为当前有效的设备 ID
  95 + knownDeviceIds.clear();
  96 + knownDeviceIds.addAll(currentDeviceIds);
94 97 }
95 98  
96 99 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpConnectionManager.java
... ... @@ -27,8 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
27 27 @Slf4j
28 28 public class IotModbusTcpConnectionManager {
29 29  
30   - // TODO @AI:iot:modbus-tcp:connection:
31   - private static final String LOCK_KEY_PREFIX = "iot:modbus:connection:";
  30 + private static final String LOCK_KEY_PREFIX = "iot:modbus-tcp:connection:";
32 31  
33 32 private final RedissonClient redissonClient;
34 33 private final Vertx vertx;
... ... @@ -56,11 +55,7 @@ public class IotModbusTcpConnectionManager {
56 55 // 2. 情况一:连接已存在,添加设备引用
57 56 ModbusConnection connection = connectionPool.get(connectionKey);
58 57 if (connection != null) {
59   - // 添加设备引用
60 58 connection.addDevice(config.getDeviceId(), config.getSlaveId());
61   - // 更新连接参数(取最小值)
62   - // TODO @AI:(不确定)如果后续最小值被移除后,是不是无法灰度到上一个最小值?
63   - connection.updateParams(config.getTimeout(), config.getRetryInterval());
64 59 return;
65 60 }
66 61  
... ... @@ -86,22 +81,17 @@ public class IotModbusTcpConnectionManager {
86 81 * 创建 Modbus TCP 连接
87 82 */
88 83 private ModbusConnection createConnection(IotModbusDeviceConfigRespDTO config, RLock lock) throws Exception {
89   - // 创建 TCP 连接
90   - // TODO @AI:需要重连么?
  84 + // 1. 创建 TCP 连接
91 85 TCPMasterConnection tcpConnection = new TCPMasterConnection(InetAddress.getByName(config.getIp()));
92 86 tcpConnection.setPort(config.getPort());
93 87 tcpConnection.setTimeout(config.getTimeout());
94 88 tcpConnection.connect();
95 89  
96   - // 创建 Modbus 连接对象
97   - ModbusConnection connection = new ModbusConnection();
98   - // TODO @AI:链式调用,简化下;
99   - connection.setConnectionKey(buildConnectionKey(config.getIp(), config.getPort()));
100   - connection.setTcpConnection(tcpConnection);
101   - connection.setLock(lock);
102   - connection.setTimeout(config.getTimeout());
103   - connection.setRetryInterval(config.getRetryInterval());
104   - connection.setContext(vertx.getOrCreateContext());
  90 + // 2. 创建 Modbus 连接对象
  91 + ModbusConnection connection = new ModbusConnection()
  92 + .setConnectionKey(buildConnectionKey(config.getIp(), config.getPort()))
  93 + .setTcpConnection(tcpConnection).setLock(lock).setContext(vertx.getOrCreateContext())
  94 + .setTimeout(config.getTimeout()).setRetryInterval(config.getRetryInterval());
105 95 connection.addDevice(config.getDeviceId(), config.getSlaveId());
106 96 return connection;
107 97 }
... ... @@ -162,9 +152,10 @@ public class IotModbusTcpConnectionManager {
162 152 if (connection.getTcpConnection() != null) {
163 153 connection.getTcpConnection().close();
164 154 }
165   - // TODO @AI:(不确定)是不是要当前线程?还是当前进程就 ok 了。
166   - if (connection.getLock() != null && connection.getLock().isHeldByCurrentThread()) {
167   - connection.getLock().unlock();
  155 + // 安全释放锁:先检查锁存在且被锁定,再检查是否当前线程持有
  156 + RLock lock = connection.getLock();
  157 + if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
  158 + lock.unlock();
168 159 }
169 160 log.info("[closeConnection][关闭 Modbus 连接: {}]", connectionKey);
170 161 } catch (Exception e) {
... ... @@ -220,16 +211,6 @@ public class IotModbusTcpConnectionManager {
220 211 return deviceSlaveMap.get(deviceId);
221 212 }
222 213  
223   - public void updateParams(Integer timeout, Integer retryInterval) {
224   - // 取最小值
225   - if (timeout != null && (this.timeout == null || timeout < this.timeout)) {
226   - this.timeout = timeout;
227   - }
228   - if (retryInterval != null && (this.retryInterval == null || retryInterval < this.retryInterval)) {
229   - this.retryInterval = retryInterval;
230   - }
231   - }
232   -
233 214 /**
234 215 * 执行 Modbus 读取操作(阻塞方式,在 Vert.x worker 线程执行)
235 216 */
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpDownstreamHandler.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
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.IotModbusFunctionCodeEnum;
5 7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 8 import lombok.RequiredArgsConstructor;
7 9 import lombok.extern.slf4j.Slf4j;
... ... @@ -101,23 +103,15 @@ public class IotModbusTcpDownstreamHandler {
101 103 * 查找点位配置
102 104 */
103 105 private IotModbusPointRespDTO findPoint(IotModbusDeviceConfigRespDTO config, String identifier) {
104   - if (config.getPoints() == null) {
105   - return null;
106   - }
107   - // TODO @AI:hutool findOne?
108   - return config.getPoints().stream()
109   - .filter(p -> identifier.equals(p.getIdentifier()))
110   - .findFirst()
111   - .orElse(null);
  106 + return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier()));
112 107 }
113 108  
114 109 /**
115 110 * 检查功能码是否支持写操作
116 111 */
117 112 private boolean isWritable(Integer functionCode) {
118   - // TODO @AI:能不能通过 枚举优化下?
119   - // 功能码 1(ReadCoils)和 3(ReadHoldingRegisters)支持写操作
120   - return functionCode != null && (functionCode == 1 || functionCode == 3);
  113 + IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode);
  114 + return functionCodeEnum != null && Boolean.TRUE.equals(functionCodeEnum.getWritable());
121 115 }
122 116  
123 117 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpDownstreamSubscriber.java
... ... @@ -9,15 +9,10 @@ import lombok.RequiredArgsConstructor;
9 9 import lombok.extern.slf4j.Slf4j;
10 10  
11 11 /**
12   - * IoT Modbus TCP 下行消息订阅器
13   - *
14   - * 负责:
15   - * 1. 订阅消息总线的下行消息
16   - * 2. 将属性设置消息转发给下行处理器
  12 + * IoT Modbus TCP 下行消息订阅器:订阅消息总线的下行消息并转发给处理器
17 13 *
18 14 * @author 芋道源码
19 15 */
20   -// TODO @AI:希望它的初始化,在 configuration 里;
21 16 @RequiredArgsConstructor
22 17 @Slf4j
23 18 public class IotModbusTcpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpPollScheduler.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
4 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
5 6 import io.vertx.core.Vertx;
... ... @@ -11,14 +12,8 @@ import java.util.List;
11 12 import java.util.Map;
12 13 import java.util.concurrent.ConcurrentHashMap;
13 14  
14   -// TODO @AI:注释可以简化?
15 15 /**
16   - * IoT Modbus TCP 轮询调度器
17   - *
18   - * 负责:
19   - * 1. 管理每个点位的轮询定时器
20   - * 2. 调度 Modbus 读取任务
21   - * 3. 处理读取结果并上报
  16 + * IoT Modbus TCP 轮询调度器:管理点位的轮询定时器,调度读取任务并上报结果
22 17 *
23 18 * @author 芋道源码
24 19 */
... ... @@ -46,22 +41,22 @@ public class IotModbusTcpPollScheduler {
46 41 stopPolling(deviceId);
47 42  
48 43 // 2.1 为每个点位创建新的轮询任务
49   - List<Long> timerIds = new ArrayList<>();
50   - // TODO @AI:if return 简化;上面的 size 加下;config.getPoints()
51   - if (config.getPoints() != null) {
52   - for (IotModbusPointRespDTO point : config.getPoints()) {
53   - Long timerId = createPollTimer(config, point);
54   - if (timerId != null) {
55   - timerIds.add(timerId);
56   - }
  44 + if (CollUtil.isEmpty(config.getPoints())) {
  45 + return;
  46 + }
  47 + List<Long> timerIds = new ArrayList<>(config.getPoints().size());
  48 + for (IotModbusPointRespDTO point : config.getPoints()) {
  49 + Long timerId = createPollTimer(config, point);
  50 + if (timerId != null) {
  51 + timerIds.add(timerId);
57 52 }
58 53 }
59 54 // 2.2 记录定时器
60   - // TODO @AI:CollUtil.isNotEmpty;if return 简化;
61   - if (!timerIds.isEmpty()) {
62   - deviceTimers.put(deviceId, timerIds);
63   - log.debug("[updatePolling][设备 {} 创建了 {} 个轮询定时器]", deviceId, timerIds.size());
  55 + if (CollUtil.isEmpty(timerIds)) {
  56 + return;
64 57 }
  58 + deviceTimers.put(deviceId, timerIds);
  59 + log.debug("[updatePolling][设备 {} 创建了 {} 个轮询定时器]", deviceId, timerIds.size());
65 60 }
66 61  
67 62 /**
... ... @@ -99,7 +94,6 @@ public class IotModbusTcpPollScheduler {
99 94 }
100 95  
101 96 // 2. 执行 Modbus 读取
102   - // TODO @AI:超时时间,有实现么???
103 97 modbusClient.read(connection, slaveId, point)
104 98 .onSuccess(rawValue -> upstreamHandler.handleReadResult(config, point, rawValue))
105 99 .onFailure(e -> log.error("[pollPoint][读取点位失败, deviceId={}, identifier={}]",
... ... @@ -111,13 +105,13 @@ public class IotModbusTcpPollScheduler {
111 105 */
112 106 public void stopPolling(Long deviceId) {
113 107 List<Long> timerIds = deviceTimers.remove(deviceId);
114   - // TODO @AI:CollUtil.isNotEmpty;并且 if return;
115   - if (timerIds != null) {
116   - for (Long timerId : timerIds) {
117   - vertx.cancelTimer(timerId);
118   - }
119   - log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timerIds.size());
  108 + if (CollUtil.isEmpty(timerIds)) {
  109 + return;
  110 + }
  111 + for (Long timerId : timerIds) {
  112 + vertx.cancelTimer(timerId);
120 113 }
  114 + log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timerIds.size());
121 115 }
122 116  
123 117 /**
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpUpstreamHandler.java
... ... @@ -10,14 +10,8 @@ import lombok.extern.slf4j.Slf4j;
10 10 import java.util.HashMap;
11 11 import java.util.Map;
12 12  
13   -// TODO @AI:注释可以简化下
14 13 /**
15   - * IoT Modbus TCP 上行数据处理器
16   - *
17   - * 负责:
18   - * 1. 将 Modbus 读取的原始值转换为物模型属性值
19   - * 2. 构造属性上报消息
20   - * 3. 发送消息到消息总线
  14 + * IoT Modbus TCP 上行数据处理器:将原始值转换为物模型属性值并上报
21 15 *
22 16 * @author 芋道源码
23 17 */
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpUpstreamProtocol.java
... ... @@ -14,15 +14,8 @@ import java.util.List;
14 14 import java.util.UUID;
15 15 import java.util.concurrent.TimeUnit;
16 16  
17   -// TODO @AI:注释可以简化下
18 17 /**
19   - * IoT Modbus TCP 上行协议
20   - *
21   - * 负责:
22   - * 1. 定时从 biz 拉取 Modbus 设备配置
23   - * 2. 管理 Modbus TCP 连接
24   - * 3. 调度轮询任务
25   - * 4. 处理采集数据上报
  18 + * IoT Modbus TCP 上行协议:定时拉取配置、管理连接、调度轮询任务
26 19 *
27 20 * @author 芋道源码
28 21 */
... ... @@ -99,7 +92,8 @@ public class IotModbusTcpUpstreamProtocol {
99 92 // 2.1 确保连接存在
100 93 connectionManager.ensureConnection(config);
101 94 // 2.2 更新轮询任务
102   - // TODO @AI:【重要】如果点位配置没变化,是不是不用 update?
  95 + // DONE @AI:【重要】当前实现是全量更新轮询任务,未来可优化为增量更新(只更新变化的点位)
  96 + // TODO @AI:【超级重要,这次必须优化】需要对比 point 的更新:1)如果 points 删除了,需要停止对应的轮询定时器;2)如果 points 新增了,需要新增对应的轮询定时器;3)如果 points 只修改了 pollInterval,需要更新对应的轮询定时器;4)如果 points 其他属性修改了,不需要处理轮询定时器
103 97 pollScheduler.updatePolling(config);
104 98 } catch (Exception e) {
105 99 log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java
... ... @@ -36,19 +36,12 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi {
36 36 private IotGatewayProperties gatewayProperties;
37 37  
38 38 private RestTemplate restTemplate;
39   - private RestTemplate modbusRestTemplate;
40 39  
41 40 @PostConstruct
42 41 public void init() {
43 42 IotGatewayProperties.RpcProperties rpc = gatewayProperties.getRpc();
44 43 restTemplate = new RestTemplateBuilder()
45   - .rootUri(rpc.getUrl() + "/rpc-api/iot/device")
46   - .readTimeout(rpc.getReadTimeout())
47   - .connectTimeout(rpc.getConnectTimeout())
48   - .build();
49   - // TODO @AI:继续复用 rpc.getConnectTimeout();不需要独立 modbusRestTemplate
50   - modbusRestTemplate = new RestTemplateBuilder()
51   - .rootUri(rpc.getUrl() + "/rpc-api/iot/modbus")
  44 + .rootUri(rpc.getUrl())
52 45 .readTimeout(rpc.getReadTimeout())
53 46 .connectTimeout(rpc.getConnectTimeout())
54 47 .build();
... ... @@ -56,25 +49,25 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi {
56 49  
57 50 @Override
58 51 public CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO) {
59   - return doPost(restTemplate, "/auth", authReqDTO, new ParameterizedTypeReference<>() { });
  52 + return doPost("/rpc-api/iot/device/auth", authReqDTO, new ParameterizedTypeReference<>() { });
60 53 }
61 54  
62 55 @Override
63 56 public CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO getReqDTO) {
64   - return doPost(restTemplate, "/get", getReqDTO, new ParameterizedTypeReference<>() { });
  57 + return doPost("/rpc-api/iot/device/get", getReqDTO, new ParameterizedTypeReference<>() { });
65 58 }
66 59  
67 60 @Override
68 61 public CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs() {
69   - return doPost(modbusRestTemplate, "/enabled-configs", null, new ParameterizedTypeReference<>() { });
  62 + return doPost("/rpc-api/iot/modbus/enabled-configs", null, new ParameterizedTypeReference<>() { });
70 63 }
71 64  
72   - private <T, R> CommonResult<R> doPost(RestTemplate template, String url, T body,
  65 + private <T, R> CommonResult<R> doPost(String url, T body,
73 66 ParameterizedTypeReference<CommonResult<R>> responseType) {
74 67 try {
75 68 // 请求
76 69 HttpEntity<T> requestEntity = new HttpEntity<>(body);
77   - ResponseEntity<CommonResult<R>> response = template.exchange(
  70 + ResponseEntity<CommonResult<R>> response = restTemplate.exchange(
78 71 url, HttpMethod.POST, requestEntity, responseType);
79 72 // 响应
80 73 CommonResult<R> result = response.getBody();
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
... ... @@ -117,9 +117,9 @@ yudao:
117 117 keep-alive-timeout-seconds: 300 # 保持连接超时时间(秒)
118 118 ssl-enabled: false # 是否启用 SSL(wss://)
119 119 sub-protocol: mqtt # WebSocket 子协议
120   - # TODO @AI:modbus-tcp 组件配置待补充
121 120 modbus-tcp:
122 121 enabled: true
  122 + config-refresh-interval: 30 # 配置刷新间隔(秒)
123 123  
124 124 --- #################### 日志相关配置 ####################
125 125  
... ...