Commit ed78834eaf5ca092acdc0c56c72484e3b99578df
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<String> { |
| 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<Integer> { |
| 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<String> { |
| 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<String> { |
| 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
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 | ... | ... |