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