Commit 4e4c776bed8327d3972ab98ea327d7a8f5b4c72c
1 parent
c608b81c
feat(iot):modbus-tcp-slave、modbus-tcp-master 继续优化大量代码,并对接管理后台
Showing
38 changed files
with
520 additions
and
327 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
| 1 | package cn.iocoder.yudao.module.iot.api.device; | 1 | package cn.iocoder.yudao.module.iot.api.device; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.ObjUtil; | ||
| 5 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.enums.RpcConstants; | 6 | import cn.iocoder.yudao.framework.common.enums.RpcConstants; |
| 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 7 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | 9 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| 8 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | 10 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.*; | 11 | import cn.iocoder.yudao.module.iot.core.biz.dto.*; |
| 10 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | ||
| 11 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; | ||
| 12 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | ||
| 13 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | ||
| 14 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | 12 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; |
| 15 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | 13 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; |
| 16 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | 14 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; |
| @@ -35,8 +33,6 @@ import java.util.List; | @@ -35,8 +33,6 @@ import java.util.List; | ||
| 35 | import java.util.Map; | 33 | import java.util.Map; |
| 36 | import java.util.Set; | 34 | import java.util.Set; |
| 37 | 35 | ||
| 38 | -import java.util.List; | ||
| 39 | - | ||
| 40 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 36 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 41 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | 37 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 42 | 38 | ||
| @@ -81,12 +77,13 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | @@ -81,12 +77,13 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | ||
| 81 | } | 77 | } |
| 82 | 78 | ||
| 83 | @Override | 79 | @Override |
| 84 | - @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/modbus/enabled-configs") | 80 | + @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/modbus/config-list") |
| 85 | @PermitAll | 81 | @PermitAll |
| 86 | @TenantIgnore | 82 | @TenantIgnore |
| 87 | - public CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs() { | ||
| 88 | - // 1. 获取所有启用的 Modbus 连接配置 | ||
| 89 | - List<IotDeviceModbusConfigDO> configList = modbusConfigService.getEnabledDeviceModbusConfigList(); | 83 | + public CommonResult<List<IotModbusDeviceConfigRespDTO>> getModbusDeviceConfigList( |
| 84 | + @RequestBody IotModbusDeviceConfigListReqDTO listReqDTO) { | ||
| 85 | + // 1. 获取 Modbus 连接配置 | ||
| 86 | + List<IotDeviceModbusConfigDO> configList = modbusConfigService.getDeviceModbusConfigList(listReqDTO); | ||
| 90 | if (CollUtil.isEmpty(configList)) { | 87 | if (CollUtil.isEmpty(configList)) { |
| 91 | return success(new ArrayList<>()); | 88 | return success(new ArrayList<>()); |
| 92 | } | 89 | } |
| @@ -95,6 +92,7 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | @@ -95,6 +92,7 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | ||
| 95 | Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId); | 92 | Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId); |
| 96 | Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds); | 93 | Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds); |
| 97 | Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds); | 94 | Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds); |
| 95 | + Map<Long, IotProductDO> productMap = productService.getProductMap(convertSet(deviceMap.values(), IotDeviceDO::getProductId)); | ||
| 98 | List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size()); | 96 | List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size()); |
| 99 | for (IotDeviceModbusConfigDO config : configList) { | 97 | for (IotDeviceModbusConfigDO config : configList) { |
| 100 | // 3.1 获取设备信息 | 98 | // 3.1 获取设备信息 |
| @@ -102,12 +100,20 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | @@ -102,12 +100,20 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { | ||
| 102 | if (device == null) { | 100 | if (device == null) { |
| 103 | continue; | 101 | continue; |
| 104 | } | 102 | } |
| 105 | - // 3.2 获取启用的点位列表 | 103 | + // 3.2 按 protocolType 筛选(如果非空) |
| 104 | + if (StrUtil.isNotEmpty(listReqDTO.getProtocolType())) { | ||
| 105 | + IotProductDO product = productMap.get(device.getProductId()); | ||
| 106 | + if (product == null || ObjUtil.notEqual(listReqDTO.getProtocolType(), product.getProtocolType())) { | ||
| 107 | + continue; | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + // 3.3 获取启用的点位列表 | ||
| 106 | List<IotDeviceModbusPointDO> pointList = pointMap.get(config.getDeviceId()); | 111 | List<IotDeviceModbusPointDO> pointList = pointMap.get(config.getDeviceId()); |
| 107 | if (CollUtil.isEmpty(pointList)) { | 112 | if (CollUtil.isEmpty(pointList)) { |
| 108 | continue; | 113 | continue; |
| 109 | } | 114 | } |
| 110 | - // 3.3 构建 IotModbusDeviceConfigRespDTO 对象 | 115 | + |
| 116 | + // 3.4 构建 IotModbusDeviceConfigRespDTO 对象 | ||
| 111 | IotModbusDeviceConfigRespDTO configDTO = BeanUtils.toBean(config, IotModbusDeviceConfigRespDTO.class, o -> | 117 | IotModbusDeviceConfigRespDTO configDTO = BeanUtils.toBean(config, IotModbusDeviceConfigRespDTO.class, o -> |
| 112 | o.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()) | 118 | o.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()) |
| 113 | .setPoints(BeanUtils.toBean(pointList, IotModbusPointRespDTO.class))); | 119 | .setPoints(BeanUtils.toBean(pointList, IotModbusPointRespDTO.class))); |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigRespVO.java
| @@ -33,13 +33,11 @@ public class IotDeviceModbusConfigRespVO { | @@ -33,13 +33,11 @@ public class IotDeviceModbusConfigRespVO { | ||
| 33 | @Schema(description = "重试间隔(毫秒)", example = "1000") | 33 | @Schema(description = "重试间隔(毫秒)", example = "1000") |
| 34 | private Integer retryInterval; | 34 | private Integer retryInterval; |
| 35 | 35 | ||
| 36 | - // TODO @AI:不要【:1-云端轮询 2-主动上报】 | ||
| 37 | - @Schema(description = "模式:1-云端轮询 2-主动上报", example = "1") | 36 | + @Schema(description = "工作模式", example = "1") |
| 38 | private Integer mode; | 37 | private Integer mode; |
| 39 | 38 | ||
| 40 | - // TODO @AI:还是换成 int,然后写注释;不要【:modbus_tcp / modbus_rtu】 | ||
| 41 | - @Schema(description = "数据帧格式:modbus_tcp / modbus_rtu", example = "modbus_tcp") | ||
| 42 | - private String frameFormat; | 39 | + @Schema(description = "数据帧格式", example = "1") |
| 40 | + private Integer frameFormat; | ||
| 43 | 41 | ||
| 44 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | 42 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| 45 | private Integer status; | 43 | private Integer status; |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigSaveReqVO.java
| 1 | package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; | 1 | package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | ||
| 4 | +import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum; | ||
| 3 | import io.swagger.v3.oas.annotations.media.Schema; | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | -import jakarta.validation.constraints.NotEmpty; | ||
| 5 | import jakarta.validation.constraints.NotNull; | 7 | import jakarta.validation.constraints.NotNull; |
| 6 | import lombok.Data; | 8 | import lombok.Data; |
| 7 | 9 | ||
| @@ -13,12 +15,14 @@ public class IotDeviceModbusConfigSaveReqVO { | @@ -13,12 +15,14 @@ public class IotDeviceModbusConfigSaveReqVO { | ||
| 13 | @NotNull(message = "设备编号不能为空") | 15 | @NotNull(message = "设备编号不能为空") |
| 14 | private Long deviceId; | 16 | private Long deviceId; |
| 15 | 17 | ||
| 16 | - @Schema(description = "Modbus 服务器 IP 地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100") | ||
| 17 | - @NotEmpty(message = "Modbus 服务器 IP 地址不能为空") | 18 | + @Schema(description = "Modbus 服务器 IP 地址", example = "192.168.1.100") |
| 19 | +// @NotEmpty(message = "Modbus 服务器 IP 地址不能为空") | ||
| 20 | + // TODO @AI:这个字段,要根据情况校验; | ||
| 18 | private String ip; | 21 | private String ip; |
| 19 | 22 | ||
| 20 | - @Schema(description = "Modbus 端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "502") | ||
| 21 | - @NotNull(message = "Modbus 端口不能为空") | 23 | + @Schema(description = "Modbus 端口", example = "502") |
| 24 | +// @NotNull(message = "Modbus 端口不能为空") | ||
| 25 | + // TODO @AI:这个字段,要根据情况校验; | ||
| 22 | private Integer port; | 26 | private Integer port; |
| 23 | 27 | ||
| 24 | @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | 28 | @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| @@ -31,13 +35,13 @@ public class IotDeviceModbusConfigSaveReqVO { | @@ -31,13 +35,13 @@ public class IotDeviceModbusConfigSaveReqVO { | ||
| 31 | @Schema(description = "重试间隔(毫秒)", example = "1000") | 35 | @Schema(description = "重试间隔(毫秒)", example = "1000") |
| 32 | private Integer retryInterval; | 36 | private Integer retryInterval; |
| 33 | 37 | ||
| 34 | - // TODO @AI:不要【:1-云端轮询 2-主动上报】 | ||
| 35 | - @Schema(description = "模式:1-云端轮询 2-主动上报", example = "1") | 38 | + @Schema(description = "工作模式", example = "1") |
| 39 | + @InEnum(IotModbusModeEnum.class) | ||
| 36 | private Integer mode; | 40 | private Integer mode; |
| 37 | 41 | ||
| 38 | - // TODO @AI:不要【:1-云端轮询 2-主动上报】 | ||
| 39 | - @Schema(description = "数据帧格式:modbus_tcp / modbus_rtu", example = "modbus_tcp") | ||
| 40 | - private String frameFormat; | 42 | + @Schema(description = "数据帧格式", example = "1") |
| 43 | + @InEnum(IotModbusFrameFormatEnum.class) | ||
| 44 | + private Integer frameFormat; | ||
| 41 | 45 | ||
| 42 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | 46 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| 43 | @NotNull(message = "状态不能为空") | 47 | @NotNull(message = "状态不能为空") |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java
| @@ -69,7 +69,7 @@ public class IotDeviceModbusConfigDO extends TenantBaseDO { | @@ -69,7 +69,7 @@ public class IotDeviceModbusConfigDO extends TenantBaseDO { | ||
| 69 | * | 69 | * |
| 70 | * @see cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum | 70 | * @see cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum |
| 71 | */ | 71 | */ |
| 72 | - private String frameFormat; | 72 | + private Integer frameFormat; |
| 73 | /** | 73 | /** |
| 74 | * 状态 | 74 | * 状态 |
| 75 | * | 75 | * |
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.mybatis.core.mapper.BaseMapperX; | 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO; | ||
| 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; |
| 5 | import org.apache.ibatis.annotations.Mapper; | 7 | import org.apache.ibatis.annotations.Mapper; |
| 6 | 8 | ||
| @@ -18,8 +20,11 @@ public interface IotDeviceModbusConfigMapper extends BaseMapperX<IotDeviceModbus | @@ -18,8 +20,11 @@ public interface IotDeviceModbusConfigMapper extends BaseMapperX<IotDeviceModbus | ||
| 18 | return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId); | 20 | return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId); |
| 19 | } | 21 | } |
| 20 | 22 | ||
| 21 | - default List<IotDeviceModbusConfigDO> selectListByStatus(Integer status) { | ||
| 22 | - return selectList(IotDeviceModbusConfigDO::getStatus, status); | 23 | + default List<IotDeviceModbusConfigDO> selectList(IotModbusDeviceConfigListReqDTO reqDTO) { |
| 24 | + return selectList(new LambdaQueryWrapperX<IotDeviceModbusConfigDO>() | ||
| 25 | + .eqIfPresent(IotDeviceModbusConfigDO::getStatus, reqDTO.getStatus()) | ||
| 26 | + .eqIfPresent(IotDeviceModbusConfigDO::getMode, reqDTO.getMode()) | ||
| 27 | + .inIfPresent(IotDeviceModbusConfigDO::getDeviceId, reqDTO.getDeviceIds())); | ||
| 23 | } | 28 | } |
| 24 | 29 | ||
| 25 | } | 30 | } |
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.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO; | 3 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO; |
| 4 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO; | ||
| 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; |
| 5 | import jakarta.validation.Valid; | 6 | import jakarta.validation.Valid; |
| 6 | 7 | ||
| @@ -37,10 +38,11 @@ public interface IotDeviceModbusConfigService { | @@ -37,10 +38,11 @@ public interface IotDeviceModbusConfigService { | ||
| 37 | IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId); | 38 | IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId); |
| 38 | 39 | ||
| 39 | /** | 40 | /** |
| 40 | - * 获得所有启用的 Modbus 连接配置列表 | 41 | + * 获得 Modbus 连接配置列表 |
| 41 | * | 42 | * |
| 42 | - * @return 启用的 Modbus 连接配置列表 | 43 | + * @param listReqDTO 查询参数 |
| 44 | + * @return Modbus 连接配置列表 | ||
| 43 | */ | 45 | */ |
| 44 | - List<IotDeviceModbusConfigDO> getEnabledDeviceModbusConfigList(); | 46 | + List<IotDeviceModbusConfigDO> getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO); |
| 45 | 47 | ||
| 46 | } | 48 | } |
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; | ||
| 4 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 3 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 5 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO; | 4 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO; |
| 5 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO; | ||
| 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; |
| 7 | import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusConfigMapper; | 7 | import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusConfigMapper; |
| 8 | import jakarta.annotation.Resource; | 8 | import jakarta.annotation.Resource; |
| @@ -54,8 +54,8 @@ public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigSe | @@ -54,8 +54,8 @@ public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigSe | ||
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | @Override | 56 | @Override |
| 57 | - public List<IotDeviceModbusConfigDO> getEnabledDeviceModbusConfigList() { | ||
| 58 | - return modbusConfigMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); | 57 | + public List<IotDeviceModbusConfigDO> getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO) { |
| 58 | + return modbusConfigMapper.selectList(listReqDTO); | ||
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | } | 61 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
| @@ -10,6 +10,9 @@ import javax.annotation.Nullable; | @@ -10,6 +10,9 @@ import javax.annotation.Nullable; | ||
| 10 | import java.time.LocalDateTime; | 10 | import java.time.LocalDateTime; |
| 11 | import java.util.Collection; | 11 | import java.util.Collection; |
| 12 | import java.util.List; | 12 | import java.util.List; |
| 13 | +import java.util.Map; | ||
| 14 | + | ||
| 15 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||
| 13 | 16 | ||
| 14 | /** | 17 | /** |
| 15 | * IoT 产品 Service 接口 | 18 | * IoT 产品 Service 接口 |
| @@ -122,6 +125,24 @@ public interface IotProductService { | @@ -122,6 +125,24 @@ public interface IotProductService { | ||
| 122 | Long getProductCount(@Nullable LocalDateTime createTime); | 125 | Long getProductCount(@Nullable LocalDateTime createTime); |
| 123 | 126 | ||
| 124 | /** | 127 | /** |
| 128 | + * 批量获得产品列表 | ||
| 129 | + * | ||
| 130 | + * @param ids 产品编号集合 | ||
| 131 | + * @return 产品列表 | ||
| 132 | + */ | ||
| 133 | + List<IotProductDO> getProductList(Collection<Long> ids); | ||
| 134 | + | ||
| 135 | + /** | ||
| 136 | + * 批量获得产品 Map | ||
| 137 | + * | ||
| 138 | + * @param ids 产品编号集合 | ||
| 139 | + * @return 产品 Map(key: 产品编号, value: 产品) | ||
| 140 | + */ | ||
| 141 | + default Map<Long, IotProductDO> getProductMap(Collection<Long> ids) { | ||
| 142 | + return convertMap(getProductList(ids), IotProductDO::getId); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + /** | ||
| 125 | * 批量校验产品存在 | 146 | * 批量校验产品存在 |
| 126 | * | 147 | * |
| 127 | * @param ids 产品编号集合 | 148 | * @param ids 产品编号集合 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
| @@ -172,6 +172,11 @@ public class IotProductServiceImpl implements IotProductService { | @@ -172,6 +172,11 @@ public class IotProductServiceImpl implements IotProductService { | ||
| 172 | } | 172 | } |
| 173 | 173 | ||
| 174 | @Override | 174 | @Override |
| 175 | + public List<IotProductDO> getProductList(Collection<Long> ids) { | ||
| 176 | + return productMapper.selectByIds(ids); | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + @Override | ||
| 175 | public void validateProductsExist(Collection<Long> ids) { | 180 | public void validateProductsExist(Collection<Long> ids) { |
| 176 | if (CollUtil.isEmpty(ids)) { | 181 | if (CollUtil.isEmpty(ids)) { |
| 177 | return; | 182 | return; |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
| 1 | package cn.iocoder.yudao.module.iot.core.biz; | 1 | package cn.iocoder.yudao.module.iot.core.biz; |
| 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.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | ||
| 5 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; | ||
| 6 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | ||
| 7 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | ||
| 8 | - | ||
| 9 | -import java.util.List; | ||
| 10 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | 4 | +import cn.iocoder.yudao.module.iot.core.biz.dto.*; |
| 11 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | 5 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; |
| 12 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | 6 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; |
| 13 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; | 7 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; |
| @@ -54,10 +48,11 @@ public interface IotDeviceCommonApi { | @@ -54,10 +48,11 @@ public interface IotDeviceCommonApi { | ||
| 54 | CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO); | 48 | CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO); |
| 55 | 49 | ||
| 56 | /** | 50 | /** |
| 57 | - * 获取所有启用的 Modbus 设备配置列表 | 51 | + * 获取 Modbus 设备配置列表 |
| 58 | * | 52 | * |
| 53 | + * @param listReqDTO 查询参数 | ||
| 59 | * @return Modbus 设备配置列表 | 54 | * @return Modbus 设备配置列表 |
| 60 | */ | 55 | */ |
| 61 | - CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs(); | 56 | + CommonResult<List<IotModbusDeviceConfigRespDTO>> getModbusDeviceConfigList(IotModbusDeviceConfigListReqDTO listReqDTO); |
| 62 | 57 | ||
| 63 | } | 58 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigListReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.biz.dto; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | +import lombok.experimental.Accessors; | ||
| 5 | + | ||
| 6 | +import java.util.Set; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * IoT Modbus 设备配置列表查询 Request DTO | ||
| 10 | + * | ||
| 11 | + * @author 芋道源码 | ||
| 12 | + */ | ||
| 13 | +@Data | ||
| 14 | +@Accessors(chain = true) | ||
| 15 | +public class IotModbusDeviceConfigListReqDTO { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 状态 | ||
| 19 | + */ | ||
| 20 | + private Integer status; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 模式 | ||
| 24 | + */ | ||
| 25 | + private Integer mode; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 协议类型 | ||
| 29 | + */ | ||
| 30 | + private String protocolType; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 设备 ID 集合 | ||
| 34 | + */ | ||
| 35 | + private Set<Long> deviceIds; | ||
| 36 | + | ||
| 37 | +} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigRespDTO.java
| @@ -54,7 +54,7 @@ public class IotModbusDeviceConfigRespDTO { | @@ -54,7 +54,7 @@ public class IotModbusDeviceConfigRespDTO { | ||
| 54 | /** | 54 | /** |
| 55 | * 数据帧格式 | 55 | * 数据帧格式 |
| 56 | */ | 56 | */ |
| 57 | - private String frameFormat; | 57 | + private Integer frameFormat; |
| 58 | 58 | ||
| 59 | // ========== Modbus 点位配置 ========== | 59 | // ========== Modbus 点位配置 ========== |
| 60 | 60 |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusFrameFormatEnum.java
| @@ -13,26 +13,22 @@ import java.util.Arrays; | @@ -13,26 +13,22 @@ import java.util.Arrays; | ||
| 13 | */ | 13 | */ |
| 14 | @Getter | 14 | @Getter |
| 15 | @RequiredArgsConstructor | 15 | @RequiredArgsConstructor |
| 16 | -public enum IotModbusFrameFormatEnum implements ArrayValuable<String> { | 16 | +public enum IotModbusFrameFormatEnum implements ArrayValuable<Integer> { |
| 17 | 17 | ||
| 18 | - MODBUS_TCP("modbus_tcp", "Modbus TCP"), | ||
| 19 | - MODBUS_RTU("modbus_rtu", "Modbus RTU"); | 18 | + MODBUS_TCP(1), |
| 19 | + MODBUS_RTU(2); | ||
| 20 | 20 | ||
| 21 | - public static final String[] ARRAYS = Arrays.stream(values()) | 21 | + public static final Integer[] ARRAYS = Arrays.stream(values()) |
| 22 | .map(IotModbusFrameFormatEnum::getFormat) | 22 | .map(IotModbusFrameFormatEnum::getFormat) |
| 23 | - .toArray(String[]::new); | 23 | + .toArray(Integer[]::new); |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| 26 | * 格式 | 26 | * 格式 |
| 27 | */ | 27 | */ |
| 28 | - private final String format; | ||
| 29 | - /** | ||
| 30 | - * 名称 | ||
| 31 | - */ | ||
| 32 | - private final String name; | 28 | + private final Integer format; |
| 33 | 29 | ||
| 34 | @Override | 30 | @Override |
| 35 | - public String[] array() { | 31 | + public Integer[] array() { |
| 36 | return ARRAYS; | 32 | return ARRAYS; |
| 37 | } | 33 | } |
| 38 | 34 |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java
| @@ -25,6 +25,14 @@ public class IotDeviceAuthUtils { | @@ -25,6 +25,14 @@ public class IotDeviceAuthUtils { | ||
| 25 | return String.format("%s.%s", productKey, deviceName); | 25 | return String.format("%s.%s", productKey, deviceName); |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | + public static String buildClientIdFromUsername(String username) { | ||
| 29 | + IotDeviceIdentity identity = parseUsername(username); | ||
| 30 | + if (identity == null) { | ||
| 31 | + return null; | ||
| 32 | + } | ||
| 33 | + return buildClientId(identity.getProductKey(), identity.getDeviceName()); | ||
| 34 | + } | ||
| 35 | + | ||
| 28 | public static String buildUsername(String productKey, String deviceName) { | 36 | public static String buildUsername(String productKey, String deviceName) { |
| 29 | return String.format("%s&%s", deviceName, productKey); | 37 | return String.format("%s&%s", deviceName, productKey); |
| 30 | } | 38 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java
| @@ -32,6 +32,10 @@ public abstract class AbstractIotModbusPollScheduler { | @@ -32,6 +32,10 @@ public abstract class AbstractIotModbusPollScheduler { | ||
| 32 | * 同设备最小请求间隔(毫秒),防止 Modbus 设备性能不足时请求堆积 | 32 | * 同设备最小请求间隔(毫秒),防止 Modbus 设备性能不足时请求堆积 |
| 33 | */ | 33 | */ |
| 34 | private static final long MIN_REQUEST_INTERVAL = 1000; | 34 | private static final long MIN_REQUEST_INTERVAL = 1000; |
| 35 | + /** | ||
| 36 | + * 每个设备请求队列的最大长度,超出时丢弃最旧请求 | ||
| 37 | + */ | ||
| 38 | + private static final int MAX_QUEUE_SIZE = 1000; | ||
| 35 | 39 | ||
| 36 | /** | 40 | /** |
| 37 | * 设备点位的定时器映射:deviceId -> (pointId -> PointTimerInfo) | 41 | * 设备点位的定时器映射:deviceId -> (pointId -> PointTimerInfo) |
| @@ -159,6 +163,11 @@ public abstract class AbstractIotModbusPollScheduler { | @@ -159,6 +163,11 @@ public abstract class AbstractIotModbusPollScheduler { | ||
| 159 | private void submitPollRequest(Long deviceId, Long pointId) { | 163 | private void submitPollRequest(Long deviceId, Long pointId) { |
| 160 | // 1. 【重要】将请求添加到设备的请求队列 | 164 | // 1. 【重要】将请求添加到设备的请求队列 |
| 161 | Queue<Runnable> queue = deviceRequestQueues.computeIfAbsent(deviceId, k -> new ConcurrentLinkedQueue<>()); | 165 | Queue<Runnable> queue = deviceRequestQueues.computeIfAbsent(deviceId, k -> new ConcurrentLinkedQueue<>()); |
| 166 | + while (queue.size() >= MAX_QUEUE_SIZE) { | ||
| 167 | + // 超出上限时,丢弃最旧的请求 | ||
| 168 | + queue.poll(); | ||
| 169 | + log.warn("[submitPollRequest][设备 {} 请求队列已满({}), 丢弃最旧请求]", deviceId, MAX_QUEUE_SIZE); | ||
| 170 | + } | ||
| 162 | queue.offer(() -> pollPoint(deviceId, pointId)); | 171 | queue.offer(() -> pollPoint(deviceId, pointId)); |
| 163 | 172 | ||
| 164 | // 2. 处理设备请求队列(如果没有延迟 timer 在等待) | 173 | // 2. 处理设备请求队列(如果没有延迟 timer 在等待) |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusCommonUtils.java
| @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils; | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils; | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.util.ArrayUtil; | 4 | import cn.hutool.core.util.ArrayUtil; |
| 5 | import cn.hutool.core.util.ObjectUtil; | 5 | import cn.hutool.core.util.ObjectUtil; |
| 6 | +import cn.hutool.core.util.StrUtil; | ||
| 6 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | 7 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; |
| 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; |
| @@ -331,10 +332,10 @@ public class IotModbusCommonUtils { | @@ -331,10 +332,10 @@ public class IotModbusCommonUtils { | ||
| 331 | // 其他字节序调整 | 332 | // 其他字节序调整 |
| 332 | byte[] result = new byte[bytes.length]; | 333 | byte[] result = new byte[bytes.length]; |
| 333 | switch (byteOrderEnum) { | 334 | switch (byteOrderEnum) { |
| 334 | - case BA: // 小端序(16 位) | ||
| 335 | - if (bytes.length >= 2) { | ||
| 336 | - result[0] = bytes[1]; | ||
| 337 | - result[1] = bytes[0]; | 335 | + case BA: // 小端序:按每 2 字节一组交换(16 位场景 [1,0],32 位场景 [1,0,3,2]) |
| 336 | + for (int i = 0; i + 1 < bytes.length; i += 2) { | ||
| 337 | + result[i] = bytes[i + 1]; | ||
| 338 | + result[i + 1] = bytes[i]; | ||
| 338 | } | 339 | } |
| 339 | break; | 340 | break; |
| 340 | case CDAB: // 大端字交换(32 位) | 341 | case CDAB: // 大端字交换(32 位) |
| @@ -509,6 +510,9 @@ public class IotModbusCommonUtils { | @@ -509,6 +510,9 @@ public class IotModbusCommonUtils { | ||
| 509 | * @return 匹配的点位配置,未找到返回 null | 510 | * @return 匹配的点位配置,未找到返回 null |
| 510 | */ | 511 | */ |
| 511 | public static IotModbusPointRespDTO findPoint(IotModbusDeviceConfigRespDTO config, String identifier) { | 512 | public static IotModbusPointRespDTO findPoint(IotModbusDeviceConfigRespDTO config, String identifier) { |
| 513 | + if (config == null || StrUtil.isBlank(identifier)) { | ||
| 514 | + return null; | ||
| 515 | + } | ||
| 512 | return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier())); | 516 | return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier())); |
| 513 | } | 517 | } |
| 514 | 518 | ||
| @@ -520,6 +524,9 @@ public class IotModbusCommonUtils { | @@ -520,6 +524,9 @@ public class IotModbusCommonUtils { | ||
| 520 | * @return 匹配的点位配置,未找到返回 null | 524 | * @return 匹配的点位配置,未找到返回 null |
| 521 | */ | 525 | */ |
| 522 | public static IotModbusPointRespDTO findPointById(IotModbusDeviceConfigRespDTO config, Long pointId) { | 526 | public static IotModbusPointRespDTO findPointById(IotModbusDeviceConfigRespDTO config, Long pointId) { |
| 527 | + if (config == null || pointId == null) { | ||
| 528 | + return null; | ||
| 529 | + } | ||
| 523 | return CollUtil.findOne(config.getPoints(), p -> p.getId().equals(pointId)); | 530 | return CollUtil.findOne(config.getPoints(), p -> p.getId().equals(pointId)); |
| 524 | } | 531 | } |
| 525 | 532 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpMasterUtils.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils; |
| 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.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager; | 4 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpMasterConnectionManager; |
| 5 | import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction; | 5 | import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction; |
| 6 | import com.ghgande.j2mod.modbus.msg.*; | 6 | import com.ghgande.j2mod.modbus.msg.*; |
| 7 | import com.ghgande.j2mod.modbus.procimg.InputRegister; | 7 | import com.ghgande.j2mod.modbus.procimg.InputRegister; |
| @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.I | @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.I | ||
| 19 | * <p> | 19 | * <p> |
| 20 | * 封装基于 j2mod 的 Modbus TCP 读写操作: | 20 | * 封装基于 j2mod 的 Modbus TCP 读写操作: |
| 21 | * 1. 根据功能码创建对应的 Modbus 读/写请求 | 21 | * 1. 根据功能码创建对应的 Modbus 读/写请求 |
| 22 | - * 2. 通过 {@link IotModbusTcpConnectionManager.ModbusConnection} 执行事务 | 22 | + * 2. 通过 {@link IotModbusTcpMasterConnectionManager.ModbusConnection} 执行事务 |
| 23 | * 3. 从响应中提取原始值 | 23 | * 3. 从响应中提取原始值 |
| 24 | * | 24 | * |
| 25 | * @author 芋道源码 | 25 | * @author 芋道源码 |
| @@ -36,9 +36,9 @@ public class IotModbusTcpMasterUtils { | @@ -36,9 +36,9 @@ public class IotModbusTcpMasterUtils { | ||
| 36 | * @param point 点位配置 | 36 | * @param point 点位配置 |
| 37 | * @return 原始值(int 数组) | 37 | * @return 原始值(int 数组) |
| 38 | */ | 38 | */ |
| 39 | - public static Future<int[]> read(IotModbusTcpConnectionManager.ModbusConnection connection, | ||
| 40 | - Integer slaveId, | ||
| 41 | - IotModbusPointRespDTO point) { | 39 | + public static Future<int[]> read(IotModbusTcpMasterConnectionManager.ModbusConnection connection, |
| 40 | + Integer slaveId, | ||
| 41 | + IotModbusPointRespDTO point) { | ||
| 42 | return connection.executeBlocking(tcpConnection -> { | 42 | return connection.executeBlocking(tcpConnection -> { |
| 43 | try { | 43 | try { |
| 44 | // 1. 创建请求 | 44 | // 1. 创建请求 |
| @@ -70,10 +70,10 @@ public class IotModbusTcpMasterUtils { | @@ -70,10 +70,10 @@ public class IotModbusTcpMasterUtils { | ||
| 70 | * @param values 要写入的值 | 70 | * @param values 要写入的值 |
| 71 | * @return 是否成功 | 71 | * @return 是否成功 |
| 72 | */ | 72 | */ |
| 73 | - public static Future<Boolean> write(IotModbusTcpConnectionManager.ModbusConnection connection, | ||
| 74 | - Integer slaveId, | ||
| 75 | - IotModbusPointRespDTO point, | ||
| 76 | - int[] values) { | 73 | + public static Future<Boolean> write(IotModbusTcpMasterConnectionManager.ModbusConnection connection, |
| 74 | + Integer slaveId, | ||
| 75 | + IotModbusPointRespDTO point, | ||
| 76 | + int[] values) { | ||
| 77 | return connection.executeBlocking(tcpConnection -> { | 77 | return connection.executeBlocking(tcpConnection -> { |
| 78 | try { | 78 | try { |
| 79 | // 1. 创建请求 | 79 | // 1. 创建请求 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/IotModbusTcpMasterProtocol.java
| @@ -6,16 +6,15 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | @@ -6,16 +6,15 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | ||
| 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; |
| 7 | import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; |
| 8 | import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; | 8 | import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; |
| 9 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 9 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 11 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties; | 10 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties; |
| 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; | 11 | import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; |
| 13 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream.IotModbusTcpDownstreamHandler; | ||
| 14 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream.IotModbusTcpDownstreamSubscriber; | ||
| 15 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.upstream.IotModbusTcpUpstreamHandler; | ||
| 16 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConfigCacheService; | ||
| 17 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager; | ||
| 18 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpPollScheduler; | 12 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream.IotModbusTcpMasterDownstreamHandler; |
| 13 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream.IotModbusTcpMasterDownstreamSubscriber; | ||
| 14 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.upstream.IotModbusTcpMasterUpstreamHandler; | ||
| 15 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpMasterConfigCacheService; | ||
| 16 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpMasterConnectionManager; | ||
| 17 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpMasterPollScheduler; | ||
| 19 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | 18 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| 20 | import io.vertx.core.Vertx; | 19 | import io.vertx.core.Vertx; |
| 21 | import lombok.Getter; | 20 | import lombok.Getter; |
| @@ -23,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; | @@ -23,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; | ||
| 23 | import org.redisson.api.RedissonClient; | 22 | import org.redisson.api.RedissonClient; |
| 24 | 23 | ||
| 25 | import java.util.List; | 24 | import java.util.List; |
| 25 | +import java.util.Set; | ||
| 26 | import java.util.concurrent.TimeUnit; | 26 | import java.util.concurrent.TimeUnit; |
| 27 | 27 | ||
| 28 | /** | 28 | /** |
| @@ -61,15 +61,14 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | @@ -61,15 +61,14 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | ||
| 61 | /** | 61 | /** |
| 62 | * 连接管理器 | 62 | * 连接管理器 |
| 63 | */ | 63 | */ |
| 64 | - private final IotModbusTcpConnectionManager connectionManager; | 64 | + private final IotModbusTcpMasterConnectionManager connectionManager; |
| 65 | /** | 65 | /** |
| 66 | * 下行消息订阅者 | 66 | * 下行消息订阅者 |
| 67 | */ | 67 | */ |
| 68 | - private final IotModbusTcpDownstreamSubscriber downstreamSubscriber; | 68 | + private final IotModbusTcpMasterDownstreamSubscriber downstreamSubscriber; |
| 69 | 69 | ||
| 70 | - private final IotModbusTcpConfigCacheService configCacheService; | ||
| 71 | - private final IotModbusTcpPollScheduler pollScheduler; | ||
| 72 | - private final IotDeviceMessageService messageService; | 70 | + private final IotModbusTcpMasterConfigCacheService configCacheService; |
| 71 | + private final IotModbusTcpMasterPollScheduler pollScheduler; | ||
| 73 | 72 | ||
| 74 | public IotModbusTcpMasterProtocol(ProtocolProperties properties) { | 73 | public IotModbusTcpMasterProtocol(ProtocolProperties properties) { |
| 75 | IotModbusTcpMasterConfig modbusTcpMasterConfig = properties.getModbusTcpMaster(); | 74 | IotModbusTcpMasterConfig modbusTcpMasterConfig = properties.getModbusTcpMaster(); |
| @@ -83,22 +82,23 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | @@ -83,22 +82,23 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | ||
| 83 | // 初始化 Manager | 82 | // 初始化 Manager |
| 84 | RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class); | 83 | RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class); |
| 85 | IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); | 84 | IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); |
| 86 | - this.connectionManager = new IotModbusTcpConnectionManager(redissonClient, vertx); | ||
| 87 | - this.configCacheService = new IotModbusTcpConfigCacheService(deviceApi); | 85 | + IotDeviceMessageService messageService = SpringUtil.getBean(IotDeviceMessageService.class); |
| 86 | + this.configCacheService = new IotModbusTcpMasterConfigCacheService(deviceApi); | ||
| 87 | + // DONE @AI:上线/下线消息已移到 ConnectionManager 内部处理,不再走回调 | ||
| 88 | + this.connectionManager = new IotModbusTcpMasterConnectionManager(redissonClient, vertx, | ||
| 89 | + messageService, configCacheService, serverId); | ||
| 88 | 90 | ||
| 89 | // 初始化 Handler | 91 | // 初始化 Handler |
| 90 | - this.messageService = SpringUtil.getBean(IotDeviceMessageService.class); | ||
| 91 | - IotDeviceMessageService messageService = this.messageService; | ||
| 92 | - IotModbusTcpUpstreamHandler upstreamHandler = new IotModbusTcpUpstreamHandler(messageService, serverId); | ||
| 93 | - IotModbusTcpDownstreamHandler downstreamHandler = new IotModbusTcpDownstreamHandler(connectionManager, | 92 | + IotModbusTcpMasterUpstreamHandler upstreamHandler = new IotModbusTcpMasterUpstreamHandler(messageService, serverId); |
| 93 | + IotModbusTcpMasterDownstreamHandler downstreamHandler = new IotModbusTcpMasterDownstreamHandler(connectionManager, | ||
| 94 | configCacheService); | 94 | configCacheService); |
| 95 | 95 | ||
| 96 | // 初始化轮询调度器 | 96 | // 初始化轮询调度器 |
| 97 | - this.pollScheduler = new IotModbusTcpPollScheduler(vertx, connectionManager, upstreamHandler, configCacheService); | 97 | + this.pollScheduler = new IotModbusTcpMasterPollScheduler(vertx, connectionManager, upstreamHandler, configCacheService); |
| 98 | 98 | ||
| 99 | // 初始化下行消息订阅者 | 99 | // 初始化下行消息订阅者 |
| 100 | IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | 100 | IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); |
| 101 | - this.downstreamSubscriber = new IotModbusTcpDownstreamSubscriber(this, downstreamHandler, messageBus); | 101 | + this.downstreamSubscriber = new IotModbusTcpMasterDownstreamSubscriber(this, downstreamHandler, messageBus); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | @Override | 104 | @Override |
| @@ -196,46 +196,21 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | @@ -196,46 +196,21 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | ||
| 196 | // 2. 更新连接和轮询任务 | 196 | // 2. 更新连接和轮询任务 |
| 197 | for (IotModbusDeviceConfigRespDTO config : configs) { | 197 | for (IotModbusDeviceConfigRespDTO config : configs) { |
| 198 | try { | 198 | try { |
| 199 | - // 2.1 检测是否为首次连接 | ||
| 200 | - boolean isNewConnection = connectionManager.getConnection(config.getDeviceId()) == null; | ||
| 201 | - // 2.2 确保连接存在 | 199 | + // 2.1 确保连接存在 |
| 202 | connectionManager.ensureConnection(config); | 200 | connectionManager.ensureConnection(config); |
| 203 | - // 2.3 首次建连成功后发送上线消息 | ||
| 204 | - // TODO @AI:在这里判断上线 ,会不会有点奇怪??? | ||
| 205 | - if (isNewConnection && connectionManager.getConnection(config.getDeviceId()) != null) { | ||
| 206 | - try { | ||
| 207 | - IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); | ||
| 208 | - messageService.sendDeviceMessage(onlineMessage, | ||
| 209 | - config.getProductKey(), config.getDeviceName(), serverId); | ||
| 210 | - } catch (Exception ex) { | ||
| 211 | - log.error("[refreshConfig][发送设备上线消息失败, deviceId={}]", config.getDeviceId(), ex); | ||
| 212 | - } | ||
| 213 | - } | ||
| 214 | - // 2.4 更新轮询任务 | 201 | + // 2.2 更新轮询任务 |
| 215 | pollScheduler.updatePolling(config); | 202 | pollScheduler.updatePolling(config); |
| 216 | } catch (Exception e) { | 203 | } catch (Exception e) { |
| 217 | log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e); | 204 | log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e); |
| 218 | } | 205 | } |
| 219 | } | 206 | } |
| 220 | 207 | ||
| 221 | - // 3. 清理已删除设备的资源(仅 API 成功时才执行) | ||
| 222 | - configCacheService.cleanupRemovedDevices(configs, deviceId -> { | ||
| 223 | - // 3.1 发送设备下线消息 | ||
| 224 | - // TODO @AI:在这里判断上线 ,会不会有点奇怪??? | ||
| 225 | - IotModbusDeviceConfigRespDTO removedConfig = configCacheService.getConfig(deviceId); | ||
| 226 | - if (removedConfig != null) { | ||
| 227 | - try { | ||
| 228 | - IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline(); | ||
| 229 | - messageService.sendDeviceMessage(offlineMessage, | ||
| 230 | - removedConfig.getProductKey(), removedConfig.getDeviceName(), serverId); | ||
| 231 | - } catch (Exception ex) { | ||
| 232 | - log.error("[refreshConfig][发送设备下线消息失败, deviceId={}]", deviceId, ex); | ||
| 233 | - } | ||
| 234 | - } | ||
| 235 | - // 3.2 停止轮询和移除连接 | 208 | + // 3. 清理已删除设备的资源 |
| 209 | + Set<Long> removedDeviceIds = configCacheService.cleanupRemovedDevices(configs); | ||
| 210 | + for (Long deviceId : removedDeviceIds) { | ||
| 236 | pollScheduler.stopPolling(deviceId); | 211 | pollScheduler.stopPolling(deviceId); |
| 237 | connectionManager.removeDevice(deviceId); | 212 | connectionManager.removeDevice(deviceId); |
| 238 | - }); | 213 | + } |
| 239 | } catch (Exception e) { | 214 | } catch (Exception e) { |
| 240 | log.error("[refreshConfig][刷新配置失败]", e); | 215 | log.error("[refreshConfig][刷新配置失败]", e); |
| 241 | } | 216 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpMasterDownstreamHandler.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.ObjUtil; | ||
| 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 cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 6 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; | 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; |
| 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpMasterUtils; | 9 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpMasterUtils; |
| 9 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConfigCacheService; | ||
| 10 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager; | 10 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpMasterConfigCacheService; |
| 11 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpMasterConnectionManager; | ||
| 11 | import lombok.RequiredArgsConstructor; | 12 | import lombok.RequiredArgsConstructor; |
| 12 | import lombok.extern.slf4j.Slf4j; | 13 | import lombok.extern.slf4j.Slf4j; |
| 13 | 14 | ||
| 14 | import java.util.Map; | 15 | import java.util.Map; |
| 15 | 16 | ||
| 16 | /** | 17 | /** |
| 17 | - * IoT Modbus TCP 下行消息处理器 | 18 | + * IoT Modbus TCP Master 下行消息处理器 |
| 18 | * <p> | 19 | * <p> |
| 19 | * 负责: | 20 | * 负责: |
| 20 | * 1. 处理下行消息(如属性设置 thing.service.property.set) | 21 | * 1. 处理下行消息(如属性设置 thing.service.property.set) |
| 21 | - * 2. 执行 Modbus 写入操作 | 22 | + * 2. 将属性值转换为 Modbus 写指令,通过 TCP 连接发送给设备 |
| 22 | * | 23 | * |
| 23 | * @author 芋道源码 | 24 | * @author 芋道源码 |
| 24 | */ | 25 | */ |
| 25 | @RequiredArgsConstructor | 26 | @RequiredArgsConstructor |
| 26 | @Slf4j | 27 | @Slf4j |
| 27 | -public class IotModbusTcpDownstreamHandler { | 28 | +public class IotModbusTcpMasterDownstreamHandler { |
| 28 | 29 | ||
| 29 | - private final IotModbusTcpConnectionManager connectionManager; | ||
| 30 | - private final IotModbusTcpConfigCacheService configCacheService; | 30 | + private final IotModbusTcpMasterConnectionManager connectionManager; |
| 31 | + private final IotModbusTcpMasterConfigCacheService configCacheService; | ||
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * 处理下行消息 | 34 | * 处理下行消息 |
| 34 | */ | 35 | */ |
| 35 | - @SuppressWarnings("unchecked") | 36 | + @SuppressWarnings({"unchecked", "DuplicatedCode"}) |
| 36 | public void handle(IotDeviceMessage message) { | 37 | public void handle(IotDeviceMessage message) { |
| 37 | // 1.1 检查是否是属性设置消息 | 38 | // 1.1 检查是否是属性设置消息 |
| 38 | - if (!IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod().equals(message.getMethod())) { | 39 | + if (ObjUtil.notEqual(IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod(), message.getMethod())) { |
| 39 | log.debug("[handle][忽略非属性设置消息: {}]", message.getMethod()); | 40 | log.debug("[handle][忽略非属性设置消息: {}]", message.getMethod()); |
| 40 | return; | 41 | return; |
| 41 | } | 42 | } |
| @@ -78,7 +79,7 @@ public class IotModbusTcpDownstreamHandler { | @@ -78,7 +79,7 @@ public class IotModbusTcpDownstreamHandler { | ||
| 78 | */ | 79 | */ |
| 79 | private void writeProperty(IotModbusDeviceConfigRespDTO config, IotModbusPointRespDTO point, Object value) { | 80 | private void writeProperty(IotModbusDeviceConfigRespDTO config, IotModbusPointRespDTO point, Object value) { |
| 80 | // 1.1 获取连接 | 81 | // 1.1 获取连接 |
| 81 | - IotModbusTcpConnectionManager.ModbusConnection connection = connectionManager.getConnection(config.getDeviceId()); | 82 | + IotModbusTcpMasterConnectionManager.ModbusConnection connection = connectionManager.getConnection(config.getDeviceId()); |
| 82 | if (connection == null) { | 83 | if (connection == null) { |
| 83 | log.warn("[writeProperty][设备 {} 没有连接]", config.getDeviceId()); | 84 | log.warn("[writeProperty][设备 {} 没有连接]", config.getDeviceId()); |
| 84 | return; | 85 | return; |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamSubscriber.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpMasterDownstreamSubscriber.java
| @@ -12,13 +12,13 @@ import lombok.extern.slf4j.Slf4j; | @@ -12,13 +12,13 @@ import lombok.extern.slf4j.Slf4j; | ||
| 12 | * @author 芋道源码 | 12 | * @author 芋道源码 |
| 13 | */ | 13 | */ |
| 14 | @Slf4j | 14 | @Slf4j |
| 15 | -public class IotModbusTcpDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber { | 15 | +public class IotModbusTcpMasterDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber { |
| 16 | 16 | ||
| 17 | - private final IotModbusTcpDownstreamHandler downstreamHandler; | 17 | + private final IotModbusTcpMasterDownstreamHandler downstreamHandler; |
| 18 | 18 | ||
| 19 | - public IotModbusTcpDownstreamSubscriber(IotModbusTcpMasterProtocol protocol, | ||
| 20 | - IotModbusTcpDownstreamHandler downstreamHandler, | ||
| 21 | - IotMessageBus messageBus) { | 19 | + public IotModbusTcpMasterDownstreamSubscriber(IotModbusTcpMasterProtocol protocol, |
| 20 | + IotModbusTcpMasterDownstreamHandler downstreamHandler, | ||
| 21 | + IotMessageBus messageBus) { | ||
| 22 | super(protocol, messageBus); | 22 | super(protocol, messageBus); |
| 23 | this.downstreamHandler = downstreamHandler; | 23 | this.downstreamHandler = downstreamHandler; |
| 24 | } | 24 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/upstream/IotModbusTcpUpstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/upstream/IotModbusTcpMasterUpstreamHandler.java
| @@ -17,13 +17,14 @@ import java.util.Map; | @@ -17,13 +17,14 @@ import java.util.Map; | ||
| 17 | * @author 芋道源码 | 17 | * @author 芋道源码 |
| 18 | */ | 18 | */ |
| 19 | @Slf4j | 19 | @Slf4j |
| 20 | -public class IotModbusTcpUpstreamHandler { | 20 | +public class IotModbusTcpMasterUpstreamHandler { |
| 21 | 21 | ||
| 22 | private final IotDeviceMessageService messageService; | 22 | private final IotDeviceMessageService messageService; |
| 23 | + | ||
| 23 | private final String serverId; | 24 | private final String serverId; |
| 24 | 25 | ||
| 25 | - public IotModbusTcpUpstreamHandler(IotDeviceMessageService messageService, | ||
| 26 | - String serverId) { | 26 | + public IotModbusTcpMasterUpstreamHandler(IotDeviceMessageService messageService, |
| 27 | + String serverId) { | ||
| 27 | this.messageService = messageService; | 28 | this.messageService = messageService; |
| 28 | this.serverId = serverId; | 29 | this.serverId = serverId; |
| 29 | } | 30 | } |
| @@ -39,7 +40,7 @@ public class IotModbusTcpUpstreamHandler { | @@ -39,7 +40,7 @@ public class IotModbusTcpUpstreamHandler { | ||
| 39 | IotModbusPointRespDTO point, | 40 | IotModbusPointRespDTO point, |
| 40 | int[] rawValue) { | 41 | int[] rawValue) { |
| 41 | try { | 42 | try { |
| 42 | - // 1.1 转换原始值为物模型属性值 | 43 | + // 1.1 转换原始值为物模型属性值(点位翻译) |
| 43 | Object convertedValue = IotModbusCommonUtils.convertToPropertyValue(rawValue, point); | 44 | Object convertedValue = IotModbusCommonUtils.convertToPropertyValue(rawValue, point); |
| 44 | log.debug("[handleReadResult][设备={}, 属性={}, 原始值={}, 转换值={}]", | 45 | log.debug("[handleReadResult][设备={}, 属性={}, 原始值={}, 转换值={}]", |
| 45 | config.getDeviceId(), point.getIdentifier(), rawValue, convertedValue); | 46 | config.getDeviceId(), point.getIdentifier(), rawValue, convertedValue); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpConfigCacheService.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpMasterConfigCacheService.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | 5 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 6 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO; | ||
| 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; |
| 8 | +import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum; | ||
| 9 | +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; | ||
| 6 | import lombok.RequiredArgsConstructor; | 10 | import lombok.RequiredArgsConstructor; |
| 7 | import lombok.extern.slf4j.Slf4j; | 11 | import lombok.extern.slf4j.Slf4j; |
| 8 | 12 | ||
| 9 | -import java.util.*; | 13 | +import java.util.HashSet; |
| 14 | +import java.util.List; | ||
| 15 | +import java.util.Map; | ||
| 16 | +import java.util.Set; | ||
| 10 | import java.util.concurrent.ConcurrentHashMap; | 17 | import java.util.concurrent.ConcurrentHashMap; |
| 11 | -import java.util.function.Consumer; | ||
| 12 | 18 | ||
| 13 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | 19 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 14 | 20 | ||
| 15 | /** | 21 | /** |
| 16 | - * IoT Modbus TCP 配置缓存服务,负责:从 biz 拉取 Modbus 设备配置,缓存配置数据,并检测配置变更 | 22 | + * IoT Modbus TCP Master 配置缓存服务 |
| 17 | * | 23 | * |
| 18 | * @author 芋道源码 | 24 | * @author 芋道源码 |
| 19 | */ | 25 | */ |
| 20 | @RequiredArgsConstructor | 26 | @RequiredArgsConstructor |
| 21 | @Slf4j | 27 | @Slf4j |
| 22 | -public class IotModbusTcpConfigCacheService { | 28 | +public class IotModbusTcpMasterConfigCacheService { |
| 23 | 29 | ||
| 24 | private final IotDeviceCommonApi deviceApi; | 30 | private final IotDeviceCommonApi deviceApi; |
| 25 | 31 | ||
| @@ -31,7 +37,7 @@ public class IotModbusTcpConfigCacheService { | @@ -31,7 +37,7 @@ public class IotModbusTcpConfigCacheService { | ||
| 31 | /** | 37 | /** |
| 32 | * 已知的设备 ID 集合(作用:用于检测已删除的设备) | 38 | * 已知的设备 ID 集合(作用:用于检测已删除的设备) |
| 33 | * | 39 | * |
| 34 | - * @see #cleanupRemovedDevices(List, Consumer) | 40 | + * @see #cleanupRemovedDevices(List) |
| 35 | */ | 41 | */ |
| 36 | private final Set<Long> knownDeviceIds = ConcurrentHashMap.newKeySet(); | 42 | private final Set<Long> knownDeviceIds = ConcurrentHashMap.newKeySet(); |
| 37 | 43 | ||
| @@ -43,7 +49,9 @@ public class IotModbusTcpConfigCacheService { | @@ -43,7 +49,9 @@ public class IotModbusTcpConfigCacheService { | ||
| 43 | public List<IotModbusDeviceConfigRespDTO> refreshConfig() { | 49 | public List<IotModbusDeviceConfigRespDTO> refreshConfig() { |
| 44 | try { | 50 | try { |
| 45 | // 1. 从远程获取配置 | 51 | // 1. 从远程获取配置 |
| 46 | - CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getEnabledModbusDeviceConfigs(); | 52 | + CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getModbusDeviceConfigList( |
| 53 | + new IotModbusDeviceConfigListReqDTO().setStatus(CommonStatusEnum.ENABLE.getStatus()) | ||
| 54 | + .setMode(IotModbusModeEnum.POLLING.getMode()).setProtocolType(IotProtocolTypeEnum.MODBUS_TCP_MASTER.getType())); | ||
| 47 | result.checkError(); | 55 | result.checkError(); |
| 48 | List<IotModbusDeviceConfigRespDTO> configs = result.getData(); | 56 | List<IotModbusDeviceConfigRespDTO> configs = result.getData(); |
| 49 | 57 | ||
| @@ -69,28 +77,30 @@ public class IotModbusTcpConfigCacheService { | @@ -69,28 +77,30 @@ public class IotModbusTcpConfigCacheService { | ||
| 69 | } | 77 | } |
| 70 | 78 | ||
| 71 | /** | 79 | /** |
| 72 | - * 清理已删除设备的资源,并更新已知设备 ID 集合 | 80 | + * 计算已删除设备的 ID 集合,清理缓存,并更新已知设备 ID 集合 |
| 81 | + * | ||
| 82 | + * DONE @AI:不再使用 callback 模式,返回已删除的设备 ID 集合,由调用方直接清理 | ||
| 73 | * | 83 | * |
| 74 | * @param currentConfigs 当前有效的配置列表 | 84 | * @param currentConfigs 当前有效的配置列表 |
| 75 | - * @param cleanupAction 清理动作 | 85 | + * @return 已删除的设备 ID 集合 |
| 76 | */ | 86 | */ |
| 77 | - public void cleanupRemovedDevices(List<IotModbusDeviceConfigRespDTO> currentConfigs, Consumer<Long> cleanupAction) { | 87 | + public Set<Long> cleanupRemovedDevices(List<IotModbusDeviceConfigRespDTO> currentConfigs) { |
| 78 | // 1.1 获取当前有效的设备 ID | 88 | // 1.1 获取当前有效的设备 ID |
| 79 | Set<Long> currentDeviceIds = convertSet(currentConfigs, IotModbusDeviceConfigRespDTO::getDeviceId); | 89 | Set<Long> currentDeviceIds = convertSet(currentConfigs, IotModbusDeviceConfigRespDTO::getDeviceId); |
| 80 | // 1.2 找出已删除的设备(基于旧的 knownDeviceIds) | 90 | // 1.2 找出已删除的设备(基于旧的 knownDeviceIds) |
| 81 | Set<Long> removedDeviceIds = new HashSet<>(knownDeviceIds); | 91 | Set<Long> removedDeviceIds = new HashSet<>(knownDeviceIds); |
| 82 | removedDeviceIds.removeAll(currentDeviceIds); | 92 | removedDeviceIds.removeAll(currentDeviceIds); |
| 83 | 93 | ||
| 84 | - // 2. 清理已删除设备(先执行 cleanupAction,再从缓存移除,保证 action 中仍可获取 config) | 94 | + // 2. 清理已删除设备的缓存 |
| 85 | for (Long deviceId : removedDeviceIds) { | 95 | for (Long deviceId : removedDeviceIds) { |
| 86 | log.info("[cleanupRemovedDevices][清理已删除设备: {}]", deviceId); | 96 | log.info("[cleanupRemovedDevices][清理已删除设备: {}]", deviceId); |
| 87 | - cleanupAction.accept(deviceId); | ||
| 88 | configCache.remove(deviceId); | 97 | configCache.remove(deviceId); |
| 89 | } | 98 | } |
| 90 | 99 | ||
| 91 | // 3. 更新已知设备 ID 集合为当前有效的设备 ID | 100 | // 3. 更新已知设备 ID 集合为当前有效的设备 ID |
| 92 | knownDeviceIds.clear(); | 101 | knownDeviceIds.clear(); |
| 93 | knownDeviceIds.addAll(currentDeviceIds); | 102 | knownDeviceIds.addAll(currentDeviceIds); |
| 103 | + return removedDeviceIds; | ||
| 94 | } | 104 | } |
| 95 | 105 | ||
| 96 | } | 106 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpConnectionManager.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpMasterConnectionManager.java
| @@ -2,12 +2,13 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager; | @@ -2,12 +2,13 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.util.ObjUtil; | 3 | import cn.hutool.core.util.ObjUtil; |
| 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; |
| 5 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 6 | +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | ||
| 5 | import com.ghgande.j2mod.modbus.net.TCPMasterConnection; | 7 | import com.ghgande.j2mod.modbus.net.TCPMasterConnection; |
| 6 | import io.vertx.core.Context; | 8 | import io.vertx.core.Context; |
| 7 | import io.vertx.core.Future; | 9 | import io.vertx.core.Future; |
| 8 | import io.vertx.core.Vertx; | 10 | import io.vertx.core.Vertx; |
| 9 | import lombok.Data; | 11 | import lombok.Data; |
| 10 | -import lombok.RequiredArgsConstructor; | ||
| 11 | import lombok.extern.slf4j.Slf4j; | 12 | import lombok.extern.slf4j.Slf4j; |
| 12 | import org.redisson.api.RLock; | 13 | import org.redisson.api.RLock; |
| 13 | import org.redisson.api.RedissonClient; | 14 | import org.redisson.api.RedissonClient; |
| @@ -26,14 +27,16 @@ import java.util.concurrent.ConcurrentHashMap; | @@ -26,14 +27,16 @@ import java.util.concurrent.ConcurrentHashMap; | ||
| 26 | * | 27 | * |
| 27 | * @author 芋道源码 | 28 | * @author 芋道源码 |
| 28 | */ | 29 | */ |
| 29 | -@RequiredArgsConstructor | ||
| 30 | @Slf4j | 30 | @Slf4j |
| 31 | -public class IotModbusTcpConnectionManager { | 31 | +public class IotModbusTcpMasterConnectionManager { |
| 32 | 32 | ||
| 33 | private static final String LOCK_KEY_PREFIX = "iot:modbus-tcp:connection:"; | 33 | private static final String LOCK_KEY_PREFIX = "iot:modbus-tcp:connection:"; |
| 34 | 34 | ||
| 35 | private final RedissonClient redissonClient; | 35 | private final RedissonClient redissonClient; |
| 36 | private final Vertx vertx; | 36 | private final Vertx vertx; |
| 37 | + private final IotDeviceMessageService messageService; | ||
| 38 | + private final IotModbusTcpMasterConfigCacheService configCacheService; | ||
| 39 | + private final String serverId; | ||
| 37 | 40 | ||
| 38 | /** | 41 | /** |
| 39 | * 连接池:key = ip:port | 42 | * 连接池:key = ip:port |
| @@ -45,8 +48,21 @@ public class IotModbusTcpConnectionManager { | @@ -45,8 +48,21 @@ public class IotModbusTcpConnectionManager { | ||
| 45 | */ | 48 | */ |
| 46 | private final Map<Long, String> deviceConnectionMap = new ConcurrentHashMap<>(); | 49 | private final Map<Long, String> deviceConnectionMap = new ConcurrentHashMap<>(); |
| 47 | 50 | ||
| 51 | + public IotModbusTcpMasterConnectionManager(RedissonClient redissonClient, Vertx vertx, | ||
| 52 | + IotDeviceMessageService messageService, | ||
| 53 | + IotModbusTcpMasterConfigCacheService configCacheService, | ||
| 54 | + String serverId) { | ||
| 55 | + this.redissonClient = redissonClient; | ||
| 56 | + this.vertx = vertx; | ||
| 57 | + this.messageService = messageService; | ||
| 58 | + this.configCacheService = configCacheService; | ||
| 59 | + this.serverId = serverId; | ||
| 60 | + } | ||
| 61 | + | ||
| 48 | /** | 62 | /** |
| 49 | * 确保连接存在 | 63 | * 确保连接存在 |
| 64 | + * <p> | ||
| 65 | + * 首次建连成功时,直接发送设备上线消息 | ||
| 50 | * | 66 | * |
| 51 | * @param config 设备配置 | 67 | * @param config 设备配置 |
| 52 | */ | 68 | */ |
| @@ -59,37 +75,39 @@ public class IotModbusTcpConnectionManager { | @@ -59,37 +75,39 @@ public class IotModbusTcpConnectionManager { | ||
| 59 | config.getDeviceId(), oldConnectionKey, connectionKey); | 75 | config.getDeviceId(), oldConnectionKey, connectionKey); |
| 60 | removeDevice(config.getDeviceId()); | 76 | removeDevice(config.getDeviceId()); |
| 61 | } | 77 | } |
| 62 | - // 1.2 记录设备与连接的关系 | 78 | + // 1.2 记录设备与连接的映射 |
| 63 | deviceConnectionMap.put(config.getDeviceId(), connectionKey); | 79 | deviceConnectionMap.put(config.getDeviceId(), connectionKey); |
| 64 | 80 | ||
| 65 | - // 2. 情况一:连接已存在,添加设备引用 | 81 | + // 2. 情况一:连接已存在,注册设备并发送上线消息 |
| 66 | ModbusConnection connection = connectionPool.get(connectionKey); | 82 | ModbusConnection connection = connectionPool.get(connectionKey); |
| 67 | if (connection != null) { | 83 | if (connection != null) { |
| 68 | - connection.addDevice(config.getDeviceId(), config.getSlaveId()); | 84 | + addDeviceAndOnline(connection, config); |
| 69 | return; | 85 | return; |
| 70 | } | 86 | } |
| 71 | 87 | ||
| 72 | - // 3. 情况二:连接不存在,创建新连接 | ||
| 73 | - // 3.1 尝试获取分布式锁 | 88 | + // 3. 情况二:连接不存在,加分布式锁创建新连接 |
| 74 | RLock lock = redissonClient.getLock(LOCK_KEY_PREFIX + connectionKey); | 89 | RLock lock = redissonClient.getLock(LOCK_KEY_PREFIX + connectionKey); |
| 75 | if (!lock.tryLock()) { | 90 | if (!lock.tryLock()) { |
| 76 | log.debug("[ensureConnection][获取锁失败, 由其他节点负责: {}]", connectionKey); | 91 | log.debug("[ensureConnection][获取锁失败, 由其他节点负责: {}]", connectionKey); |
| 77 | return; | 92 | return; |
| 78 | } | 93 | } |
| 79 | - // 3.2 double-check:拿到锁后再次检查,避免并发创建重复连接 | ||
| 80 | try { | 94 | try { |
| 95 | + // 3.1 double-check:拿到锁后再次检查,避免并发创建重复连接 | ||
| 81 | connection = connectionPool.get(connectionKey); | 96 | connection = connectionPool.get(connectionKey); |
| 82 | if (connection != null) { | 97 | if (connection != null) { |
| 83 | - connection.addDevice(config.getDeviceId(), config.getSlaveId()); | ||
| 84 | - lock.unlock(); | 98 | + addDeviceAndOnline(connection, config); |
| 85 | return; | 99 | return; |
| 86 | } | 100 | } |
| 87 | - // 3.3 创建新连接 | ||
| 88 | - connection = createConnection(config, lock); | 101 | + // 3.2 创建新连接 |
| 102 | + connection = createConnection(config); | ||
| 89 | connectionPool.put(connectionKey, connection); | 103 | connectionPool.put(connectionKey, connection); |
| 90 | log.info("[ensureConnection][创建 Modbus 连接成功: {}]", connectionKey); | 104 | log.info("[ensureConnection][创建 Modbus 连接成功: {}]", connectionKey); |
| 105 | + // 3.3 注册设备并发送上线消息 | ||
| 106 | + addDeviceAndOnline(connection, config); | ||
| 91 | } catch (Exception e) { | 107 | } catch (Exception e) { |
| 92 | log.error("[ensureConnection][创建 Modbus 连接失败: {}]", connectionKey, e); | 108 | log.error("[ensureConnection][创建 Modbus 连接失败: {}]", connectionKey, e); |
| 109 | + } finally { | ||
| 110 | + // TODO @AI:如果这里释放,会不会出现,集群模式下,多个节点同时创建连接的情况?需要验证一下 Redisson 的分布式锁特性?! | ||
| 93 | lock.unlock(); | 111 | lock.unlock(); |
| 94 | } | 112 | } |
| 95 | } | 113 | } |
| @@ -97,7 +115,7 @@ public class IotModbusTcpConnectionManager { | @@ -97,7 +115,7 @@ public class IotModbusTcpConnectionManager { | ||
| 97 | /** | 115 | /** |
| 98 | * 创建 Modbus TCP 连接 | 116 | * 创建 Modbus TCP 连接 |
| 99 | */ | 117 | */ |
| 100 | - private ModbusConnection createConnection(IotModbusDeviceConfigRespDTO config, RLock lock) throws Exception { | 118 | + private ModbusConnection createConnection(IotModbusDeviceConfigRespDTO config) throws Exception { |
| 101 | // 1. 创建 TCP 连接 | 119 | // 1. 创建 TCP 连接 |
| 102 | TCPMasterConnection tcpConnection = new TCPMasterConnection(InetAddress.getByName(config.getIp())); | 120 | TCPMasterConnection tcpConnection = new TCPMasterConnection(InetAddress.getByName(config.getIp())); |
| 103 | tcpConnection.setPort(config.getPort()); | 121 | tcpConnection.setPort(config.getPort()); |
| @@ -105,12 +123,10 @@ public class IotModbusTcpConnectionManager { | @@ -105,12 +123,10 @@ public class IotModbusTcpConnectionManager { | ||
| 105 | tcpConnection.connect(); | 123 | tcpConnection.connect(); |
| 106 | 124 | ||
| 107 | // 2. 创建 Modbus 连接对象 | 125 | // 2. 创建 Modbus 连接对象 |
| 108 | - ModbusConnection connection = new ModbusConnection() | 126 | + return new ModbusConnection() |
| 109 | .setConnectionKey(buildConnectionKey(config.getIp(), config.getPort())) | 127 | .setConnectionKey(buildConnectionKey(config.getIp(), config.getPort())) |
| 110 | - .setTcpConnection(tcpConnection).setLock(lock).setContext(vertx.getOrCreateContext()) | 128 | + .setTcpConnection(tcpConnection).setContext(vertx.getOrCreateContext()) |
| 111 | .setTimeout(config.getTimeout()).setRetryInterval(config.getRetryInterval()); | 129 | .setTimeout(config.getTimeout()).setRetryInterval(config.getRetryInterval()); |
| 112 | - connection.addDevice(config.getDeviceId(), config.getSlaveId()); | ||
| 113 | - return connection; | ||
| 114 | } | 130 | } |
| 115 | 131 | ||
| 116 | /** | 132 | /** |
| @@ -137,25 +153,71 @@ public class IotModbusTcpConnectionManager { | @@ -137,25 +153,71 @@ public class IotModbusTcpConnectionManager { | ||
| 137 | 153 | ||
| 138 | /** | 154 | /** |
| 139 | * 移除设备 | 155 | * 移除设备 |
| 156 | + * <p> | ||
| 157 | + * 移除时直接发送设备下线消息 | ||
| 140 | */ | 158 | */ |
| 141 | public void removeDevice(Long deviceId) { | 159 | public void removeDevice(Long deviceId) { |
| 142 | - // 1. 移除设备引用 | 160 | + // 1.1 移除设备时,发送下线消息 |
| 161 | + sendOfflineMessage(deviceId); | ||
| 162 | + // 1.2 移除设备引用 | ||
| 143 | String connectionKey = deviceConnectionMap.remove(deviceId); | 163 | String connectionKey = deviceConnectionMap.remove(deviceId); |
| 144 | if (connectionKey == null) { | 164 | if (connectionKey == null) { |
| 145 | return; | 165 | return; |
| 146 | } | 166 | } |
| 167 | + | ||
| 168 | + // 2.1 移除连接中的设备引用 | ||
| 147 | ModbusConnection connection = connectionPool.get(connectionKey); | 169 | ModbusConnection connection = connectionPool.get(connectionKey); |
| 148 | if (connection == null) { | 170 | if (connection == null) { |
| 149 | return; | 171 | return; |
| 150 | } | 172 | } |
| 151 | connection.removeDevice(deviceId); | 173 | connection.removeDevice(deviceId); |
| 152 | - | ||
| 153 | - // 2. 如果没有设备引用了,关闭连接 | 174 | + // 2.2 如果没有设备引用了,关闭连接 |
| 154 | if (connection.getDeviceCount() == 0) { | 175 | if (connection.getDeviceCount() == 0) { |
| 155 | closeConnection(connectionKey); | 176 | closeConnection(connectionKey); |
| 156 | } | 177 | } |
| 157 | } | 178 | } |
| 158 | 179 | ||
| 180 | + // ==================== 设备连接 & 上下线消息 ==================== | ||
| 181 | + | ||
| 182 | + /** | ||
| 183 | + * 注册设备到连接,并发送上线消息 | ||
| 184 | + */ | ||
| 185 | + private void addDeviceAndOnline(ModbusConnection connection, | ||
| 186 | + IotModbusDeviceConfigRespDTO config) { | ||
| 187 | + connection.addDevice(config.getDeviceId(), config.getSlaveId()); | ||
| 188 | + sendOnlineMessage(config); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * 发送设备上线消息 | ||
| 193 | + */ | ||
| 194 | + private void sendOnlineMessage(IotModbusDeviceConfigRespDTO config) { | ||
| 195 | + try { | ||
| 196 | + IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); | ||
| 197 | + messageService.sendDeviceMessage(onlineMessage, | ||
| 198 | + config.getProductKey(), config.getDeviceName(), serverId); | ||
| 199 | + } catch (Exception ex) { | ||
| 200 | + log.error("[sendOnlineMessage][发送设备上线消息失败, deviceId={}]", config.getDeviceId(), ex); | ||
| 201 | + } | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + /** | ||
| 205 | + * 发送设备下线消息 | ||
| 206 | + */ | ||
| 207 | + private void sendOfflineMessage(Long deviceId) { | ||
| 208 | + IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); | ||
| 209 | + if (config == null) { | ||
| 210 | + return; | ||
| 211 | + } | ||
| 212 | + try { | ||
| 213 | + IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline(); | ||
| 214 | + messageService.sendDeviceMessage(offlineMessage, | ||
| 215 | + config.getProductKey(), config.getDeviceName(), serverId); | ||
| 216 | + } catch (Exception ex) { | ||
| 217 | + log.error("[sendOfflineMessage][发送设备下线消息失败, deviceId={}]", deviceId, ex); | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + | ||
| 159 | /** | 221 | /** |
| 160 | * 关闭指定连接 | 222 | * 关闭指定连接 |
| 161 | */ | 223 | */ |
| @@ -170,10 +232,10 @@ public class IotModbusTcpConnectionManager { | @@ -170,10 +232,10 @@ public class IotModbusTcpConnectionManager { | ||
| 170 | connection.getTcpConnection().close(); | 232 | connection.getTcpConnection().close(); |
| 171 | } | 233 | } |
| 172 | // 强制解锁,避免死锁(正常情况下应该不会发生锁未释放的情况) | 234 | // 强制解锁,避免死锁(正常情况下应该不会发生锁未释放的情况) |
| 173 | - RLock lock = connection.getLock(); | ||
| 174 | - if (lock != null && lock.isLocked()) { | ||
| 175 | - lock.forceUnlock(); | ||
| 176 | - } | 235 | +// RLock lock = connection.getLock(); |
| 236 | +// if (lock != null && lock.isLocked()) { | ||
| 237 | +// lock.forceUnlock(); | ||
| 238 | +// } | ||
| 177 | log.info("[closeConnection][关闭 Modbus 连接: {}]", connectionKey); | 239 | log.info("[closeConnection][关闭 Modbus 连接: {}]", connectionKey); |
| 178 | } catch (Exception e) { | 240 | } catch (Exception e) { |
| 179 | log.error("[closeConnection][关闭连接失败: {}]", connectionKey, e); | 241 | log.error("[closeConnection][关闭连接失败: {}]", connectionKey, e); |
| @@ -202,11 +264,14 @@ public class IotModbusTcpConnectionManager { | @@ -202,11 +264,14 @@ public class IotModbusTcpConnectionManager { | ||
| 202 | 264 | ||
| 203 | private String connectionKey; | 265 | private String connectionKey; |
| 204 | private TCPMasterConnection tcpConnection; | 266 | private TCPMasterConnection tcpConnection; |
| 205 | - private RLock lock; | ||
| 206 | private Integer timeout; | 267 | private Integer timeout; |
| 207 | private Integer retryInterval; | 268 | private Integer retryInterval; |
| 269 | + | ||
| 208 | private Context context; | 270 | private Context context; |
| 209 | 271 | ||
| 272 | + // TODO @AI:是不是需要 lock?!避免集群模式下的竞争(肯定不能让别的节点连接上)!!!【另外,RLock 在节点(持有所锁的节点) cransh 的时候,会自动释放】 | ||
| 273 | +// private RLock lock; | ||
| 274 | + | ||
| 210 | /** | 275 | /** |
| 211 | * 设备 ID 到 slave ID 的映射 | 276 | * 设备 ID 到 slave ID 的映射 |
| 212 | */ | 277 | */ |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpPollScheduler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpMasterPollScheduler.java
| @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | ||
| 7 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.manager.AbstractIotModbusPollScheduler; | 7 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.manager.AbstractIotModbusPollScheduler; |
| 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; | 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; |
| 9 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpMasterUtils; | 9 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpMasterUtils; |
| 10 | -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.upstream.IotModbusTcpUpstreamHandler; | 10 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.upstream.IotModbusTcpMasterUpstreamHandler; |
| 11 | import io.vertx.core.Vertx; | 11 | import io.vertx.core.Vertx; |
| 12 | import lombok.extern.slf4j.Slf4j; | 12 | import lombok.extern.slf4j.Slf4j; |
| 13 | 13 | ||
| @@ -17,16 +17,16 @@ import lombok.extern.slf4j.Slf4j; | @@ -17,16 +17,16 @@ import lombok.extern.slf4j.Slf4j; | ||
| 17 | * @author 芋道源码 | 17 | * @author 芋道源码 |
| 18 | */ | 18 | */ |
| 19 | @Slf4j | 19 | @Slf4j |
| 20 | -public class IotModbusTcpPollScheduler extends AbstractIotModbusPollScheduler { | 20 | +public class IotModbusTcpMasterPollScheduler extends AbstractIotModbusPollScheduler { |
| 21 | 21 | ||
| 22 | - private final IotModbusTcpConnectionManager connectionManager; | ||
| 23 | - private final IotModbusTcpUpstreamHandler upstreamHandler; | ||
| 24 | - private final IotModbusTcpConfigCacheService configCacheService; | 22 | + private final IotModbusTcpMasterConnectionManager connectionManager; |
| 23 | + private final IotModbusTcpMasterUpstreamHandler upstreamHandler; | ||
| 24 | + private final IotModbusTcpMasterConfigCacheService configCacheService; | ||
| 25 | 25 | ||
| 26 | - public IotModbusTcpPollScheduler(Vertx vertx, | ||
| 27 | - IotModbusTcpConnectionManager connectionManager, | ||
| 28 | - IotModbusTcpUpstreamHandler upstreamHandler, | ||
| 29 | - IotModbusTcpConfigCacheService configCacheService) { | 26 | + public IotModbusTcpMasterPollScheduler(Vertx vertx, |
| 27 | + IotModbusTcpMasterConnectionManager connectionManager, | ||
| 28 | + IotModbusTcpMasterUpstreamHandler upstreamHandler, | ||
| 29 | + IotModbusTcpMasterConfigCacheService configCacheService) { | ||
| 30 | super(vertx); | 30 | super(vertx); |
| 31 | this.connectionManager = connectionManager; | 31 | this.connectionManager = connectionManager; |
| 32 | this.upstreamHandler = upstreamHandler; | 32 | this.upstreamHandler = upstreamHandler; |
| @@ -54,7 +54,7 @@ public class IotModbusTcpPollScheduler extends AbstractIotModbusPollScheduler { | @@ -54,7 +54,7 @@ public class IotModbusTcpPollScheduler extends AbstractIotModbusPollScheduler { | ||
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | // 2.1 获取连接 | 56 | // 2.1 获取连接 |
| 57 | - IotModbusTcpConnectionManager.ModbusConnection connection = connectionManager.getConnection(deviceId); | 57 | + IotModbusTcpMasterConnectionManager.ModbusConnection connection = connectionManager.getConnection(deviceId); |
| 58 | if (connection == null) { | 58 | if (connection == null) { |
| 59 | log.warn("[pollPoint][设备 {} 没有连接]", deviceId); | 59 | log.warn("[pollPoint][设备 {} 没有连接]", deviceId); |
| 60 | return; | 60 | return; |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/package-info.java
| 1 | /** | 1 | /** |
| 2 | - * Modbus TCP Master 协议实现包 | 2 | + * Modbus TCP Master(主站)协议:网关主动连接并轮询 Modbus 从站设备 |
| 3 | * <p> | 3 | * <p> |
| 4 | - * 提供基于 j2mod 的 Modbus TCP 主站(Master)功能,支持: | ||
| 5 | - * 1. 定时轮询 Modbus 从站设备数据 | ||
| 6 | - * 2. 下发属性设置命令到从站设备 | ||
| 7 | - * 3. 数据格式转换(寄存器值 ↔ 物模型属性值) | 4 | + * 基于 j2mod 实现,支持 FC01-04 读、FC05/06/15/16 写,定时轮询 + 下发属性设置 |
| 8 | */ | 5 | */ |
| 9 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster; | 6 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster; |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveProtocol.java
| @@ -81,22 +81,27 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -81,22 +81,27 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 81 | */ | 81 | */ |
| 82 | private Long requestCleanupTimerId; | 82 | private Long requestCleanupTimerId; |
| 83 | 83 | ||
| 84 | - // ========== 各组件 ========== | ||
| 85 | - // TODO @芋艿:稍后排序下,有点小乱; | 84 | + /** |
| 85 | + * 连接管理器 | ||
| 86 | + */ | ||
| 87 | + private final IotModbusTcpSlaveConnectionManager connectionManager; | ||
| 88 | + /** | ||
| 89 | + * 下行消息订阅者 | ||
| 90 | + */ | ||
| 91 | + private final IotModbusTcpSlaveDownstreamSubscriber downstreamSubscriber; | ||
| 86 | 92 | ||
| 87 | - private final IotModbusTcpSlaveConfig slaveConfig; | ||
| 88 | private final IotModbusFrameDecoder frameDecoder; | 93 | private final IotModbusFrameDecoder frameDecoder; |
| 94 | + @SuppressWarnings("FieldCanBeLocal") | ||
| 89 | private final IotModbusFrameEncoder frameEncoder; | 95 | private final IotModbusFrameEncoder frameEncoder; |
| 90 | - private final IotModbusTcpSlaveConnectionManager connectionManager; | 96 | + |
| 91 | private final IotModbusTcpSlaveConfigCacheService configCacheService; | 97 | private final IotModbusTcpSlaveConfigCacheService configCacheService; |
| 92 | private final IotModbusTcpSlavePendingRequestManager pendingRequestManager; | 98 | private final IotModbusTcpSlavePendingRequestManager pendingRequestManager; |
| 93 | private final IotModbusTcpSlaveUpstreamHandler upstreamHandler; | 99 | private final IotModbusTcpSlaveUpstreamHandler upstreamHandler; |
| 94 | - private final IotModbusTcpSlaveDownstreamSubscriber downstreamSubscriber; | ||
| 95 | private final IotModbusTcpSlavePollScheduler pollScheduler; | 100 | private final IotModbusTcpSlavePollScheduler pollScheduler; |
| 96 | private final IotDeviceMessageService messageService; | 101 | private final IotDeviceMessageService messageService; |
| 97 | 102 | ||
| 98 | public IotModbusTcpSlaveProtocol(ProtocolProperties properties) { | 103 | public IotModbusTcpSlaveProtocol(ProtocolProperties properties) { |
| 99 | - this.slaveConfig = properties.getModbusTcpSlave(); | 104 | + IotModbusTcpSlaveConfig slaveConfig = properties.getModbusTcpSlave(); |
| 100 | Assert.notNull(slaveConfig, "Modbus TCP Slave 协议配置(modbusTcpSlave)不能为空"); | 105 | Assert.notNull(slaveConfig, "Modbus TCP Slave 协议配置(modbusTcpSlave)不能为空"); |
| 101 | this.properties = properties; | 106 | this.properties = properties; |
| 102 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); | 107 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); |
| @@ -124,10 +129,9 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -124,10 +129,9 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 124 | 129 | ||
| 125 | // 初始化 Handler | 130 | // 初始化 Handler |
| 126 | this.messageService = SpringUtil.getBean(IotDeviceMessageService.class); | 131 | this.messageService = SpringUtil.getBean(IotDeviceMessageService.class); |
| 127 | - IotDeviceMessageService messageService = this.messageService; | ||
| 128 | IotDeviceService deviceService = SpringUtil.getBean(IotDeviceService.class); | 132 | IotDeviceService deviceService = SpringUtil.getBean(IotDeviceService.class); |
| 129 | this.upstreamHandler = new IotModbusTcpSlaveUpstreamHandler( | 133 | this.upstreamHandler = new IotModbusTcpSlaveUpstreamHandler( |
| 130 | - deviceApi, messageService, frameEncoder, | 134 | + deviceApi, this.messageService, frameEncoder, |
| 131 | connectionManager, configCacheService, pendingRequestManager, | 135 | connectionManager, configCacheService, pendingRequestManager, |
| 132 | pollScheduler, deviceService, serverId); | 136 | pollScheduler, deviceService, serverId); |
| 133 | 137 | ||
| @@ -158,9 +162,9 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -158,9 +162,9 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 158 | 162 | ||
| 159 | try { | 163 | try { |
| 160 | // 1. 启动配置刷新定时器 | 164 | // 1. 启动配置刷新定时器 |
| 161 | - int refreshInterval = slaveConfig.getConfigRefreshInterval(); | 165 | + IotModbusTcpSlaveConfig slaveConfig = properties.getModbusTcpSlave(); |
| 162 | configRefreshTimerId = vertx.setPeriodic( | 166 | configRefreshTimerId = vertx.setPeriodic( |
| 163 | - TimeUnit.SECONDS.toMillis(refreshInterval), | 167 | + TimeUnit.SECONDS.toMillis(slaveConfig.getConfigRefreshInterval()), |
| 164 | id -> refreshConfig()); | 168 | id -> refreshConfig()); |
| 165 | 169 | ||
| 166 | // 2.1 启动 TCP Server | 170 | // 2.1 启动 TCP Server |
| @@ -178,6 +182,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -178,6 +182,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 178 | downstreamSubscriber.start(); | 182 | downstreamSubscriber.start(); |
| 179 | } catch (Exception e) { | 183 | } catch (Exception e) { |
| 180 | log.error("[start][IoT Modbus TCP Slave 协议 {} 启动失败]", getId(), e); | 184 | log.error("[start][IoT Modbus TCP Slave 协议 {} 启动失败]", getId(), e); |
| 185 | + // TODO @芋艿:后续统一优化 stop 逻辑; | ||
| 181 | if (configRefreshTimerId != null) { | 186 | if (configRefreshTimerId != null) { |
| 182 | vertx.cancelTimer(configRefreshTimerId); | 187 | vertx.cancelTimer(configRefreshTimerId); |
| 183 | configRefreshTimerId = null; | 188 | configRefreshTimerId = null; |
| @@ -223,9 +228,9 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -223,9 +228,9 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 223 | pollScheduler.stopAll(); | 228 | pollScheduler.stopAll(); |
| 224 | // 2.3 清理 PendingRequest | 229 | // 2.3 清理 PendingRequest |
| 225 | pendingRequestManager.clear(); | 230 | pendingRequestManager.clear(); |
| 226 | - // 2.3 关闭所有连接 | 231 | + // 2.4 关闭所有连接 |
| 227 | connectionManager.closeAll(); | 232 | connectionManager.closeAll(); |
| 228 | - // 2.4 关闭 TCP Server | 233 | + // 2.5 关闭 TCP Server |
| 229 | if (netServer != null) { | 234 | if (netServer != null) { |
| 230 | try { | 235 | try { |
| 231 | netServer.close().result(); | 236 | netServer.close().result(); |
| @@ -308,9 +313,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -308,9 +313,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 308 | 313 | ||
| 309 | /** | 314 | /** |
| 310 | * 刷新已连接设备的配置(定时调用) | 315 | * 刷新已连接设备的配置(定时调用) |
| 311 | - * <p> | ||
| 312 | - * 与 tcpmaster 不同,slave 只刷新已连接设备的配置,不做全量 diff。 | ||
| 313 | - * 设备的新增(认证时)和删除(断连时)分别在 {@link #handleConnection} 中处理。 | ||
| 314 | */ | 316 | */ |
| 315 | private synchronized void refreshConfig() { | 317 | private synchronized void refreshConfig() { |
| 316 | try { | 318 | try { |
| @@ -321,6 +323,10 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -321,6 +323,10 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 321 | } | 323 | } |
| 322 | List<IotModbusDeviceConfigRespDTO> configs = | 324 | List<IotModbusDeviceConfigRespDTO> configs = |
| 323 | configCacheService.refreshConnectedDeviceConfigList(connectedDeviceIds); | 325 | configCacheService.refreshConnectedDeviceConfigList(connectedDeviceIds); |
| 326 | + if (configs == null) { | ||
| 327 | + log.warn("[refreshConfig][刷新配置失败,跳过本次刷新]"); | ||
| 328 | + return; | ||
| 329 | + } | ||
| 324 | log.debug("[refreshConfig][刷新了 {} 个已连接设备的配置]", configs.size()); | 330 | log.debug("[refreshConfig][刷新了 {} 个已连接设备的配置]", configs.size()); |
| 325 | 331 | ||
| 326 | // 2. 更新已连接设备的轮询任务 | 332 | // 2. 更新已连接设备的轮询任务 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrame.java
| @@ -29,7 +29,7 @@ public class IotModbusFrame { | @@ -29,7 +29,7 @@ public class IotModbusFrame { | ||
| 29 | /** | 29 | /** |
| 30 | * 事务标识符 | 30 | * 事务标识符 |
| 31 | * <p> | 31 | * <p> |
| 32 | - * 仅 {@link IotModbusFrameFormatEnum#MODBUS_TCP} 格式有值, | 32 | + * 仅 {@link IotModbusFrameFormatEnum#MODBUS_TCP} 格式有值 |
| 33 | */ | 33 | */ |
| 34 | private Integer transactionId; | 34 | private Integer transactionId; |
| 35 | 35 | ||
| @@ -37,14 +37,13 @@ public class IotModbusFrame { | @@ -37,14 +37,13 @@ public class IotModbusFrame { | ||
| 37 | * 异常码 | 37 | * 异常码 |
| 38 | * <p> | 38 | * <p> |
| 39 | * 当功能码最高位为 1 时(异常响应),此字段存储异常码。 | 39 | * 当功能码最高位为 1 时(异常响应),此字段存储异常码。 |
| 40 | - * 为 null 表示非异常响应。 | ||
| 41 | * | 40 | * |
| 42 | * @see IotModbusCommonUtils#FC_EXCEPTION_MASK | 41 | * @see IotModbusCommonUtils#FC_EXCEPTION_MASK |
| 43 | */ | 42 | */ |
| 44 | private Integer exceptionCode; | 43 | private Integer exceptionCode; |
| 45 | 44 | ||
| 46 | /** | 45 | /** |
| 47 | - * 自定义功能码时的 JSON 字符串 | 46 | + * 自定义功能码时的 JSON 字符串(用于 auth 认证等等) |
| 48 | */ | 47 | */ |
| 49 | private String customData; | 48 | private String customData; |
| 50 | 49 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameDecoder.java
| @@ -20,20 +20,20 @@ import java.util.function.BiConsumer; | @@ -20,20 +20,20 @@ import java.util.function.BiConsumer; | ||
| 20 | * 1. 首帧检测:读前 6 字节,判断 MODBUS_TCP(ProtocolId==0x0000 且 Length 合理)或 MODBUS_RTU | 20 | * 1. 首帧检测:读前 6 字节,判断 MODBUS_TCP(ProtocolId==0x0000 且 Length 合理)或 MODBUS_RTU |
| 21 | * 2. 检测后切换到对应的拆包 Handler,并将首包 6 字节通过 handleFirstBytes() 交给新 Handler 处理 | 21 | * 2. 检测后切换到对应的拆包 Handler,并将首包 6 字节通过 handleFirstBytes() 交给新 Handler 处理 |
| 22 | * 3. 拆包完成后解码为 IotModbusFrame,通过回调返回 | 22 | * 3. 拆包完成后解码为 IotModbusFrame,通过回调返回 |
| 23 | - * - MODBUS_TCP:两阶段 RecordParser(MBAP length 字段驱动) | ||
| 24 | - * - MODBUS_RTU:功能码驱动的状态机 | 23 | + * - MODBUS_TCP:两阶段 RecordParser(MBAP length 字段驱动) |
| 24 | + * - MODBUS_RTU:功能码驱动的状态机 | ||
| 25 | * | 25 | * |
| 26 | * @author 芋道源码 | 26 | * @author 芋道源码 |
| 27 | */ | 27 | */ |
| 28 | +@RequiredArgsConstructor | ||
| 28 | @Slf4j | 29 | @Slf4j |
| 29 | public class IotModbusFrameDecoder { | 30 | public class IotModbusFrameDecoder { |
| 30 | 31 | ||
| 32 | + /** | ||
| 33 | + * 自定义功能码 | ||
| 34 | + */ | ||
| 31 | private final int customFunctionCode; | 35 | private final int customFunctionCode; |
| 32 | 36 | ||
| 33 | - public IotModbusFrameDecoder(int customFunctionCode) { | ||
| 34 | - this.customFunctionCode = customFunctionCode; | ||
| 35 | - } | ||
| 36 | - | ||
| 37 | /** | 37 | /** |
| 38 | * 创建带自动帧格式检测的 RecordParser | 38 | * 创建带自动帧格式检测的 RecordParser |
| 39 | * | 39 | * |
| @@ -82,7 +82,7 @@ public class IotModbusFrameDecoder { | @@ -82,7 +82,7 @@ public class IotModbusFrameDecoder { | ||
| 82 | // 提取 PDU 数据(从 functionCode 之后到末尾) | 82 | // 提取 PDU 数据(从 functionCode 之后到末尾) |
| 83 | byte[] pdu = new byte[data.length - 8]; | 83 | byte[] pdu = new byte[data.length - 8]; |
| 84 | System.arraycopy(data, 8, pdu, 0, pdu.length); | 84 | System.arraycopy(data, 8, pdu, 0, pdu.length); |
| 85 | - | 85 | + // 构建 IotModbusFrame |
| 86 | return buildFrame(slaveId, functionCode, pdu, transactionId); | 86 | return buildFrame(slaveId, functionCode, pdu, transactionId); |
| 87 | } | 87 | } |
| 88 | 88 | ||
| @@ -105,7 +105,7 @@ public class IotModbusFrameDecoder { | @@ -105,7 +105,7 @@ public class IotModbusFrameDecoder { | ||
| 105 | // PDU 数据(不含 slaveId、functionCode、CRC) | 105 | // PDU 数据(不含 slaveId、functionCode、CRC) |
| 106 | byte[] pdu = new byte[data.length - 4]; | 106 | byte[] pdu = new byte[data.length - 4]; |
| 107 | System.arraycopy(data, 2, pdu, 0, pdu.length); | 107 | System.arraycopy(data, 2, pdu, 0, pdu.length); |
| 108 | - | 108 | + // 构建 IotModbusFrame |
| 109 | return buildFrame(slaveId, functionCode, pdu, null); | 109 | return buildFrame(slaveId, functionCode, pdu, null); |
| 110 | } | 110 | } |
| 111 | 111 | ||
| @@ -144,7 +144,6 @@ public class IotModbusFrameDecoder { | @@ -144,7 +144,6 @@ public class IotModbusFrameDecoder { | ||
| 144 | /** | 144 | /** |
| 145 | * 帧格式检测阶段 Handler(仅处理首包,探测后切换到对应的拆包 Handler) | 145 | * 帧格式检测阶段 Handler(仅处理首包,探测后切换到对应的拆包 Handler) |
| 146 | */ | 146 | */ |
| 147 | - @SuppressWarnings("ClassCanBeRecord") | ||
| 148 | @RequiredArgsConstructor | 147 | @RequiredArgsConstructor |
| 149 | private class DetectPhaseHandler implements Handler<Buffer> { | 148 | private class DetectPhaseHandler implements Handler<Buffer> { |
| 150 | 149 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamHandler.java
| @@ -51,7 +51,7 @@ public class IotModbusTcpSlaveDownstreamHandler { | @@ -51,7 +51,7 @@ public class IotModbusTcpSlaveDownstreamHandler { | ||
| 51 | /** | 51 | /** |
| 52 | * 处理下行消息 | 52 | * 处理下行消息 |
| 53 | */ | 53 | */ |
| 54 | - @SuppressWarnings("unchecked") | 54 | + @SuppressWarnings({"unchecked", "DuplicatedCode"}) |
| 55 | public void handle(IotDeviceMessage message) { | 55 | public void handle(IotDeviceMessage message) { |
| 56 | // 1.1 检查是否是属性设置消息 | 56 | // 1.1 检查是否是属性设置消息 |
| 57 | if (ObjUtil.notEqual(IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod(), message.getMethod())) { | 57 | if (ObjUtil.notEqual(IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod(), message.getMethod())) { |
| @@ -125,17 +125,26 @@ public class IotModbusTcpSlaveDownstreamHandler { | @@ -125,17 +125,26 @@ public class IotModbusTcpSlaveDownstreamHandler { | ||
| 125 | point.getRegisterAddress(), rawValues[0], frameFormat, transactionId); | 125 | point.getRegisterAddress(), rawValues[0], frameFormat, transactionId); |
| 126 | } else if (writeMultipleCode != null) { | 126 | } else if (writeMultipleCode != null) { |
| 127 | // 多个值:使用多写功能码(FC15/FC16) | 127 | // 多个值:使用多写功能码(FC15/FC16) |
| 128 | - data = frameEncoder.encodeWriteMultipleRegistersRequest(slaveId, | ||
| 129 | - point.getRegisterAddress(), rawValues, frameFormat, transactionId); | 128 | + if (writeMultipleCode == IotModbusCommonUtils.FC_WRITE_MULTIPLE_COILS) { |
| 129 | + data = frameEncoder.encodeWriteMultipleCoilsRequest(slaveId, | ||
| 130 | + point.getRegisterAddress(), rawValues, frameFormat, transactionId); | ||
| 131 | + } else { | ||
| 132 | + data = frameEncoder.encodeWriteMultipleRegistersRequest(slaveId, | ||
| 133 | + point.getRegisterAddress(), rawValues, frameFormat, transactionId); | ||
| 134 | + } | ||
| 130 | } else { | 135 | } else { |
| 131 | log.warn("[writeProperty][点位 {} 不支持写操作, 功能码={}]", point.getIdentifier(), readFunctionCode); | 136 | log.warn("[writeProperty][点位 {} 不支持写操作, 功能码={}]", point.getIdentifier(), readFunctionCode); |
| 132 | return; | 137 | return; |
| 133 | } | 138 | } |
| 134 | 139 | ||
| 135 | - // 2. 发送 | ||
| 136 | - connectionManager.sendToDevice(deviceId, data); | ||
| 137 | - log.info("[writeProperty][写入成功, deviceId={}, identifier={}, value={}]", | ||
| 138 | - deviceId, point.getIdentifier(), value); | 140 | + // 2. 发送消息 |
| 141 | + connectionManager.sendToDevice(deviceId, data).onSuccess(v -> | ||
| 142 | + log.info("[writeProperty][写入成功, deviceId={}, identifier={}, value={}]", | ||
| 143 | + deviceId, point.getIdentifier(), value) | ||
| 144 | + ).onFailure(e -> | ||
| 145 | + log.error("[writeProperty][写入失败, deviceId={}, identifier={}, value={}]", | ||
| 146 | + deviceId, point.getIdentifier(), value, e) | ||
| 147 | + ); | ||
| 139 | } | 148 | } |
| 140 | 149 | ||
| 141 | } | 150 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/upstream/IotModbusTcpSlaveUpstreamHandler.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.handler.upstream; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.handler.upstream; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.collection.CollUtil; | ||
| 4 | import cn.hutool.core.lang.Assert; | 3 | import cn.hutool.core.lang.Assert; |
| 5 | import cn.hutool.core.map.MapUtil; | 4 | import cn.hutool.core.map.MapUtil; |
| 6 | import cn.hutool.core.util.BooleanUtil; | 5 | import cn.hutool.core.util.BooleanUtil; |
| 6 | +import cn.hutool.core.util.ObjUtil; | ||
| 7 | import cn.hutool.core.util.StrUtil; | 7 | import cn.hutool.core.util.StrUtil; |
| 8 | import cn.iocoder.yudao.framework.common.exception.ServiceException; | 8 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 9 | import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | 9 | import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; |
| @@ -61,6 +61,7 @@ public class IotModbusTcpSlaveUpstreamHandler { | @@ -61,6 +61,7 @@ public class IotModbusTcpSlaveUpstreamHandler { | ||
| 61 | private final IotModbusTcpSlavePendingRequestManager pendingRequestManager; | 61 | private final IotModbusTcpSlavePendingRequestManager pendingRequestManager; |
| 62 | private final IotModbusTcpSlavePollScheduler pollScheduler; | 62 | private final IotModbusTcpSlavePollScheduler pollScheduler; |
| 63 | private final IotDeviceService deviceService; | 63 | private final IotDeviceService deviceService; |
| 64 | + | ||
| 64 | private final String serverId; | 65 | private final String serverId; |
| 65 | 66 | ||
| 66 | public IotModbusTcpSlaveUpstreamHandler(IotDeviceCommonApi deviceApi, | 67 | public IotModbusTcpSlaveUpstreamHandler(IotDeviceCommonApi deviceApi, |
| @@ -153,16 +154,20 @@ public class IotModbusTcpSlaveUpstreamHandler { | @@ -153,16 +154,20 @@ public class IotModbusTcpSlaveUpstreamHandler { | ||
| 153 | // 1. 解析认证参数 | 154 | // 1. 解析认证参数 |
| 154 | IotDeviceAuthReqDTO request = JsonUtils.convertObject(params, IotDeviceAuthReqDTO.class); | 155 | IotDeviceAuthReqDTO request = JsonUtils.convertObject(params, IotDeviceAuthReqDTO.class); |
| 155 | Assert.notNull(request, "认证参数不能为空"); | 156 | Assert.notNull(request, "认证参数不能为空"); |
| 156 | - Assert.notBlank(request.getClientId(), "clientId 不能为空"); | ||
| 157 | Assert.notBlank(request.getUsername(), "username 不能为空"); | 157 | Assert.notBlank(request.getUsername(), "username 不能为空"); |
| 158 | Assert.notBlank(request.getPassword(), "password 不能为空"); | 158 | Assert.notBlank(request.getPassword(), "password 不能为空"); |
| 159 | + // 特殊:考虑到 modbus 消息体积较小,默认 clientId 传递空串 | ||
| 160 | + if (StrUtil.isBlank(request.getClientId())) { | ||
| 161 | + request.setClientId(IotDeviceAuthUtils.buildClientIdFromUsername(request.getUsername())); | ||
| 162 | + } | ||
| 163 | + Assert.notBlank(request.getClientId(), "clientId 不能为空"); | ||
| 159 | 164 | ||
| 160 | // 2.1 调用认证 API | 165 | // 2.1 调用认证 API |
| 161 | CommonResult<Boolean> result = deviceApi.authDevice(request); | 166 | CommonResult<Boolean> result = deviceApi.authDevice(request); |
| 162 | result.checkError(); | 167 | result.checkError(); |
| 163 | if (BooleanUtil.isFalse(result.getData())) { | 168 | if (BooleanUtil.isFalse(result.getData())) { |
| 164 | log.warn("[handleAuth][认证失败, clientId={}, username={}]", request.getClientId(), request.getUsername()); | 169 | log.warn("[handleAuth][认证失败, clientId={}, username={}]", request.getClientId(), request.getUsername()); |
| 165 | - sendCustomResponse(socket, frame, frameFormat, METHOD_AUTH, 1, "认证失败"); | 170 | + sendCustomResponse(socket, frame, frameFormat, METHOD_AUTH, BAD_REQUEST.getCode(), "认证失败"); |
| 166 | return; | 171 | return; |
| 167 | } | 172 | } |
| 168 | // 2.2 解析设备信息 | 173 | // 2.2 解析设备信息 |
| @@ -171,7 +176,21 @@ public class IotModbusTcpSlaveUpstreamHandler { | @@ -171,7 +176,21 @@ public class IotModbusTcpSlaveUpstreamHandler { | ||
| 171 | // 2.3 获取设备信息 | 176 | // 2.3 获取设备信息 |
| 172 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName()); | 177 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName()); |
| 173 | Assert.notNull(device, "设备不存在"); | 178 | Assert.notNull(device, "设备不存在"); |
| 174 | - // TODO @AI:2.4 必须找到连接配置; | 179 | + // 2.4 加载设备 Modbus 配置,无配置则阻断认证 |
| 180 | + IotModbusDeviceConfigRespDTO modbusConfig = configCacheService.loadDeviceConfig(device.getId()); | ||
| 181 | + if (modbusConfig == null) { | ||
| 182 | + log.warn("[handleAuth][设备 {} 没有 Modbus 点位配置, 拒绝认证]", device.getId()); | ||
| 183 | + sendCustomResponse(socket, frame, frameFormat, METHOD_AUTH, BAD_REQUEST.getCode(), "设备无 Modbus 配置"); | ||
| 184 | + return; | ||
| 185 | + } | ||
| 186 | + // 2.5 协议不一致,阻断认证 | ||
| 187 | + if (ObjUtil.notEqual(frameFormat.getFormat(), modbusConfig.getFrameFormat())) { | ||
| 188 | + log.warn("[handleAuth][设备 {} frameFormat 不一致, 连接协议={}, 设备配置={},拒绝认证]", | ||
| 189 | + device.getId(), frameFormat.getFormat(), modbusConfig.getFrameFormat()); | ||
| 190 | + sendCustomResponse(socket, frame, frameFormat, METHOD_AUTH, BAD_REQUEST.getCode(), | ||
| 191 | + "frameFormat 协议不一致"); | ||
| 192 | + return; | ||
| 193 | + } | ||
| 175 | 194 | ||
| 176 | // 3.1 注册连接 | 195 | // 3.1 注册连接 |
| 177 | ConnectionInfo connectionInfo = new ConnectionInfo() | 196 | ConnectionInfo connectionInfo = new ConnectionInfo() |
| @@ -189,13 +208,8 @@ public class IotModbusTcpSlaveUpstreamHandler { | @@ -189,13 +208,8 @@ public class IotModbusTcpSlaveUpstreamHandler { | ||
| 189 | GlobalErrorCodeConstants.SUCCESS.getCode(), "success"); | 208 | GlobalErrorCodeConstants.SUCCESS.getCode(), "success"); |
| 190 | log.info("[handleAuth][认证成功, clientId={}, deviceId={}]", request.getClientId(), device.getId()); | 209 | log.info("[handleAuth][认证成功, clientId={}, deviceId={}]", request.getClientId(), device.getId()); |
| 191 | 210 | ||
| 192 | - // 4. 加载设备配置并启动轮询 | ||
| 193 | - IotModbusDeviceConfigRespDTO config = configCacheService.loadDeviceConfig(device.getId()); | ||
| 194 | - if (config != null) { | ||
| 195 | - pollScheduler.updatePolling(config); | ||
| 196 | - } else { | ||
| 197 | - log.warn("[handleAuth][认证成功但未找到设备配置, deviceId={}]", device.getId()); | ||
| 198 | - } | 211 | + // 4. 启动轮询 |
| 212 | + pollScheduler.updatePolling(modbusConfig); | ||
| 199 | } | 213 | } |
| 200 | 214 | ||
| 201 | /** | 215 | /** |
| @@ -245,20 +259,19 @@ public class IotModbusTcpSlaveUpstreamHandler { | @@ -245,20 +259,19 @@ public class IotModbusTcpSlaveUpstreamHandler { | ||
| 245 | } | 259 | } |
| 246 | // 2.3 查找点位配置 | 260 | // 2.3 查找点位配置 |
| 247 | IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(info.getDeviceId()); | 261 | IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(info.getDeviceId()); |
| 248 | - if (config == null || CollUtil.isEmpty(config.getPoints())) { | ||
| 249 | - return; | ||
| 250 | - } | ||
| 251 | IotModbusPointRespDTO point = IotModbusCommonUtils.findPointById(config, request.getPointId()); | 262 | IotModbusPointRespDTO point = IotModbusCommonUtils.findPointById(config, request.getPointId()); |
| 252 | if (point == null) { | 263 | if (point == null) { |
| 253 | return; | 264 | return; |
| 254 | } | 265 | } |
| 255 | 266 | ||
| 256 | - // 3.1 点位翻译 | 267 | + // 3.1 转换原始值为物模型属性值(点位翻译) |
| 257 | Object convertedValue = IotModbusCommonUtils.convertToPropertyValue(rawValues, point); | 268 | Object convertedValue = IotModbusCommonUtils.convertToPropertyValue(rawValues, point); |
| 258 | - // 3.2 上报属性 | 269 | + // 3.2 构造属性上报消息 |
| 259 | Map<String, Object> params = MapUtil.of(request.getIdentifier(), convertedValue); | 270 | Map<String, Object> params = MapUtil.of(request.getIdentifier(), convertedValue); |
| 260 | IotDeviceMessage message = IotDeviceMessage.requestOf( | 271 | IotDeviceMessage message = IotDeviceMessage.requestOf( |
| 261 | IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), params); | 272 | IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), params); |
| 273 | + | ||
| 274 | + // 4. 发送到消息总线 | ||
| 262 | messageService.sendDeviceMessage(message, info.getProductKey(), info.getDeviceName(), serverId); | 275 | messageService.sendDeviceMessage(message, info.getProductKey(), info.getDeviceName(), serverId); |
| 263 | log.debug("[handlePollingResponse][设备={}, 属性={}, 原始值={}, 转换值={}]", | 276 | log.debug("[handlePollingResponse][设备={}, 属性={}, 原始值={}, 转换值={}]", |
| 264 | info.getDeviceId(), request.getIdentifier(), rawValues, convertedValue); | 277 | info.getDeviceId(), request.getIdentifier(), rawValues, convertedValue); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConfigCacheService.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | 6 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 7 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO; | ||
| 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; |
| 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; |
| 10 | +import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; | ||
| 11 | +import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum; | ||
| 12 | +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; | ||
| 8 | import lombok.RequiredArgsConstructor; | 13 | import lombok.RequiredArgsConstructor; |
| 9 | import lombok.extern.slf4j.Slf4j; | 14 | import lombok.extern.slf4j.Slf4j; |
| 10 | 15 | ||
| @@ -13,16 +18,7 @@ import java.util.*; | @@ -13,16 +18,7 @@ import java.util.*; | ||
| 13 | import java.util.concurrent.ConcurrentHashMap; | 18 | import java.util.concurrent.ConcurrentHashMap; |
| 14 | 19 | ||
| 15 | /** | 20 | /** |
| 16 | - * IoT Modbus TCP Slave 配置缓存服务 | ||
| 17 | - * <p> | ||
| 18 | - * 与 tcpmaster 的 {@code IotModbusTcpConfigCacheService} 不同: | ||
| 19 | - * - tcpmaster 启动时拉全量配置 → 主动建连,需要全量 diff 检测新增/删除设备 | ||
| 20 | - * - tcpslave 设备主动连接 → 认证时按需加载配置,断连时清理,定时刷新已连接设备的配置 | ||
| 21 | - * <p> | ||
| 22 | - * 配置生命周期: | ||
| 23 | - * 1. 认证时:{@link #loadDeviceConfig(Long)} 按 deviceId 从 API 加载配置到缓存 | ||
| 24 | - * 2. 断连时:{@link #removeConfig(Long)} 从缓存中移除 | ||
| 25 | - * 3. 定时刷新:{@link #refreshConnectedDeviceConfigList(Set)} 批量刷新已连接设备的配置 | 21 | + * IoT Modbus TCP Slave 配置缓存:认证时按需加载,断连时清理,定时刷新已连接设备 |
| 26 | * | 22 | * |
| 27 | * @author 芋道源码 | 23 | * @author 芋道源码 |
| 28 | */ | 24 | */ |
| @@ -41,44 +37,33 @@ public class IotModbusTcpSlaveConfigCacheService { | @@ -41,44 +37,33 @@ public class IotModbusTcpSlaveConfigCacheService { | ||
| 41 | 37 | ||
| 42 | /** | 38 | /** |
| 43 | * 加载单个设备的配置(认证成功后调用) | 39 | * 加载单个设备的配置(认证成功后调用) |
| 44 | - * <p> | ||
| 45 | - * 从远程 API 获取全量配置,然后按 deviceId 匹配。 | ||
| 46 | - * 如果远程获取失败,尝试从 Mock 数据中匹配。 | ||
| 47 | * | 40 | * |
| 48 | * @param deviceId 设备 ID | 41 | * @param deviceId 设备 ID |
| 49 | - * @return 设备配置,未找到返回 null | 42 | + * @return 设备配置 |
| 50 | */ | 43 | */ |
| 51 | public IotModbusDeviceConfigRespDTO loadDeviceConfig(Long deviceId) { | 44 | public IotModbusDeviceConfigRespDTO loadDeviceConfig(Long deviceId) { |
| 52 | try { | 45 | try { |
| 53 | - // 1. 从远程 API 获取全量配置 | ||
| 54 | - // TODO @AI:等待修复,不着急; | ||
| 55 | - CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getEnabledModbusDeviceConfigs(); | ||
| 56 | - if (result != null && result.isSuccess() && result.getData() != null) { | ||
| 57 | - for (IotModbusDeviceConfigRespDTO config : result.getData()) { | ||
| 58 | - // 顺便更新缓存(其他已连接设备也受益) | ||
| 59 | - configCache.put(config.getDeviceId(), config); | ||
| 60 | - if (config.getDeviceId().equals(deviceId)) { | ||
| 61 | - return config; | ||
| 62 | - } | ||
| 63 | - } | 46 | + // 1. 从远程 API 获取配置 |
| 47 | + IotModbusDeviceConfigListReqDTO reqDTO = new IotModbusDeviceConfigListReqDTO() | ||
| 48 | + .setStatus(CommonStatusEnum.ENABLE.getStatus()) | ||
| 49 | + .setMode(IotModbusModeEnum.POLLING.getMode()) | ||
| 50 | + .setProtocolType(IotProtocolTypeEnum.MODBUS_TCP_SLAVE.getType()) | ||
| 51 | + .setDeviceIds(Collections.singleton(deviceId)); | ||
| 52 | + CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getModbusDeviceConfigList(reqDTO); | ||
| 53 | + result.checkError(); | ||
| 54 | + IotModbusDeviceConfigRespDTO modbusConfig = CollUtil.getFirst(result.getData()); | ||
| 55 | + if (modbusConfig == null) { | ||
| 56 | + log.warn("[loadDeviceConfig][远程获取配置失败,未找到数据, deviceId={}]", deviceId); | ||
| 57 | + return null; | ||
| 64 | } | 58 | } |
| 59 | + | ||
| 60 | + // 2. 更新缓存并返回 | ||
| 61 | + configCache.put(modbusConfig.getDeviceId(), modbusConfig); | ||
| 62 | + return modbusConfig; | ||
| 65 | } catch (Exception e) { | 63 | } catch (Exception e) { |
| 66 | log.error("[loadDeviceConfig][从远程获取配置失败, deviceId={}]", deviceId, e); | 64 | log.error("[loadDeviceConfig][从远程获取配置失败, deviceId={}]", deviceId, e); |
| 65 | + return null; | ||
| 67 | } | 66 | } |
| 68 | - | ||
| 69 | - // 2. 远程未找到,尝试 Mock 数据(仅 mockEnabled=true 时) | ||
| 70 | - // DONE @AI:【from codex】【中】Mock 数据已通过 mockEnabled 配置开关控制,线上环境不会污染真实配置。 | ||
| 71 | - // TODO @芋艿:测试完成后移除 | ||
| 72 | - if (true) { | ||
| 73 | - for (IotModbusDeviceConfigRespDTO mockConfig : buildMockConfigs()) { | ||
| 74 | - configCache.put(mockConfig.getDeviceId(), mockConfig); | ||
| 75 | - if (mockConfig.getDeviceId().equals(deviceId)) { | ||
| 76 | - return mockConfig; | ||
| 77 | - } | ||
| 78 | - } | ||
| 79 | - } | ||
| 80 | - | ||
| 81 | - return configCache.get(deviceId); | ||
| 82 | } | 67 | } |
| 83 | 68 | ||
| 84 | // ==================== 定时刷新(已连接设备) ==================== | 69 | // ==================== 定时刷新(已连接设备) ==================== |
| @@ -96,26 +81,25 @@ public class IotModbusTcpSlaveConfigCacheService { | @@ -96,26 +81,25 @@ public class IotModbusTcpSlaveConfigCacheService { | ||
| 96 | return Collections.emptyList(); | 81 | return Collections.emptyList(); |
| 97 | } | 82 | } |
| 98 | try { | 83 | try { |
| 99 | - // 1. 从远程获取全量配置 | ||
| 100 | - // TODO @AI:传递 ids 批量查询;需要分批啦; | ||
| 101 | - CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getEnabledModbusDeviceConfigs(); | ||
| 102 | - List<IotModbusDeviceConfigRespDTO> allConfigs; | ||
| 103 | - if (result != null && result.isSuccess() && result.getData() != null) { | ||
| 104 | - allConfigs = new ArrayList<>(result.getData()); | ||
| 105 | - } else { | ||
| 106 | - log.warn("[refreshConnectedDeviceConfigList][获取 Modbus 配置失败: {}]", result); | ||
| 107 | - allConfigs = new ArrayList<>(); | ||
| 108 | - } | 84 | + // 1. 从远程获取已连接设备的配置 |
| 85 | + CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getModbusDeviceConfigList( | ||
| 86 | + new IotModbusDeviceConfigListReqDTO().setStatus(CommonStatusEnum.ENABLE.getStatus()) | ||
| 87 | + .setMode(IotModbusModeEnum.POLLING.getMode()) | ||
| 88 | + .setProtocolType(IotProtocolTypeEnum.MODBUS_TCP_SLAVE.getType()) | ||
| 89 | + .setDeviceIds(connectedDeviceIds)); | ||
| 90 | + List<IotModbusDeviceConfigRespDTO> modbusConfigs = result.getCheckedData(); | ||
| 109 | 91 | ||
| 110 | // 2. 追加 Mock 测试数据(仅 mockEnabled=true 时) | 92 | // 2. 追加 Mock 测试数据(仅 mockEnabled=true 时) |
| 111 | // TODO @芋艿:测试完成后移除 | 93 | // TODO @芋艿:测试完成后移除 |
| 94 | + // TODO @claude-code:【严重】同上,if(true) 导致 mockEnabled 开关失效,Mock 数据永远加载 | ||
| 112 | if (true) { | 95 | if (true) { |
| 113 | - allConfigs.addAll(buildMockConfigs()); | 96 | + modbusConfigs.addAll(buildMockConfigs()); |
| 114 | } | 97 | } |
| 115 | 98 | ||
| 116 | - // 3. 只保留已连接设备的配置,更新缓存 | 99 | + // 2. 只保留已连接设备的配置,更新缓存 |
| 100 | + // TODO @AI:是不是直接添加到 configCache 缓存(或者覆盖),然后返回 modbusConfigs 就 ok 了?! | ||
| 117 | List<IotModbusDeviceConfigRespDTO> connectedConfigs = new ArrayList<>(); | 101 | List<IotModbusDeviceConfigRespDTO> connectedConfigs = new ArrayList<>(); |
| 118 | - for (IotModbusDeviceConfigRespDTO config : allConfigs) { | 102 | + for (IotModbusDeviceConfigRespDTO config : modbusConfigs) { |
| 119 | if (connectedDeviceIds.contains(config.getDeviceId())) { | 103 | if (connectedDeviceIds.contains(config.getDeviceId())) { |
| 120 | configCache.put(config.getDeviceId(), config); | 104 | configCache.put(config.getDeviceId(), config); |
| 121 | connectedConfigs.add(config); | 105 | connectedConfigs.add(config); |
| @@ -124,15 +108,7 @@ public class IotModbusTcpSlaveConfigCacheService { | @@ -124,15 +108,7 @@ public class IotModbusTcpSlaveConfigCacheService { | ||
| 124 | return connectedConfigs; | 108 | return connectedConfigs; |
| 125 | } catch (Exception e) { | 109 | } catch (Exception e) { |
| 126 | log.error("[refreshConnectedDeviceConfigList][刷新配置失败]", e); | 110 | log.error("[refreshConnectedDeviceConfigList][刷新配置失败]", e); |
| 127 | - // 降级:返回缓存中已连接设备的配置 | ||
| 128 | - List<IotModbusDeviceConfigRespDTO> fallback = new ArrayList<>(); | ||
| 129 | - for (Long deviceId : connectedDeviceIds) { | ||
| 130 | - IotModbusDeviceConfigRespDTO config = configCache.get(deviceId); | ||
| 131 | - if (config != null) { | ||
| 132 | - fallback.add(config); | ||
| 133 | - } | ||
| 134 | - } | ||
| 135 | - return fallback; | 111 | + return null; |
| 136 | } | 112 | } |
| 137 | } | 113 | } |
| 138 | 114 | ||
| @@ -142,7 +118,12 @@ public class IotModbusTcpSlaveConfigCacheService { | @@ -142,7 +118,12 @@ public class IotModbusTcpSlaveConfigCacheService { | ||
| 142 | * 获取设备配置 | 118 | * 获取设备配置 |
| 143 | */ | 119 | */ |
| 144 | public IotModbusDeviceConfigRespDTO getConfig(Long deviceId) { | 120 | public IotModbusDeviceConfigRespDTO getConfig(Long deviceId) { |
| 145 | - return configCache.get(deviceId); | 121 | + IotModbusDeviceConfigRespDTO config = configCache.get(deviceId); |
| 122 | + if (config != null) { | ||
| 123 | + return config; | ||
| 124 | + } | ||
| 125 | + // 缓存未命中,从远程 API 获取 | ||
| 126 | + return loadDeviceConfig(deviceId); | ||
| 146 | } | 127 | } |
| 147 | 128 | ||
| 148 | /** | 129 | /** |
| @@ -169,7 +150,7 @@ public class IotModbusTcpSlaveConfigCacheService { | @@ -169,7 +150,7 @@ public class IotModbusTcpSlaveConfigCacheService { | ||
| 169 | config.setDeviceName("small"); | 150 | config.setDeviceName("small"); |
| 170 | config.setSlaveId(1); | 151 | config.setSlaveId(1); |
| 171 | config.setMode(1); // 云端轮询 | 152 | config.setMode(1); // 云端轮询 |
| 172 | - config.setFrameFormat("modbus_tcp"); | 153 | + config.setFrameFormat(IotModbusFrameFormatEnum.MODBUS_TCP.getFormat()); |
| 173 | 154 | ||
| 174 | // 点位列表 | 155 | // 点位列表 |
| 175 | List<IotModbusPointRespDTO> points = new ArrayList<>(); | 156 | List<IotModbusPointRespDTO> points = new ArrayList<>(); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConnectionManager.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; | 3 | import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; |
| 4 | +import io.vertx.core.Future; | ||
| 4 | import io.vertx.core.buffer.Buffer; | 5 | import io.vertx.core.buffer.Buffer; |
| 5 | import io.vertx.core.net.NetSocket; | 6 | import io.vertx.core.net.NetSocket; |
| 6 | import lombok.Data; | 7 | import lombok.Data; |
| @@ -129,21 +130,25 @@ public class IotModbusTcpSlaveConnectionManager { | @@ -129,21 +130,25 @@ public class IotModbusTcpSlaveConnectionManager { | ||
| 129 | 130 | ||
| 130 | /** | 131 | /** |
| 131 | * 发送数据到设备 | 132 | * 发送数据到设备 |
| 133 | + * | ||
| 134 | + * @return 发送结果 Future | ||
| 132 | */ | 135 | */ |
| 133 | - public void sendToDevice(Long deviceId, byte[] data) { | 136 | + public Future<Void> sendToDevice(Long deviceId, byte[] data) { |
| 134 | NetSocket socket = deviceSocketMap.get(deviceId); | 137 | NetSocket socket = deviceSocketMap.get(deviceId); |
| 135 | if (socket == null) { | 138 | if (socket == null) { |
| 136 | log.warn("[sendToDevice][设备 {} 没有连接]", deviceId); | 139 | log.warn("[sendToDevice][设备 {} 没有连接]", deviceId); |
| 137 | - return; | 140 | + return Future.failedFuture("设备 " + deviceId + " 没有连接"); |
| 138 | } | 141 | } |
| 139 | - sendToSocket(socket, data); | 142 | + return sendToSocket(socket, data); |
| 140 | } | 143 | } |
| 141 | 144 | ||
| 142 | /** | 145 | /** |
| 143 | * 发送数据到指定 socket | 146 | * 发送数据到指定 socket |
| 147 | + * | ||
| 148 | + * @return 发送结果 Future | ||
| 144 | */ | 149 | */ |
| 145 | - public void sendToSocket(NetSocket socket, byte[] data) { | ||
| 146 | - socket.write(Buffer.buffer(data)); | 150 | + public Future<Void> sendToSocket(NetSocket socket, byte[] data) { |
| 151 | + return socket.write(Buffer.buffer(data)); | ||
| 147 | } | 152 | } |
| 148 | 153 | ||
| 149 | /** | 154 | /** |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlavePendingRequestManager.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; | 4 | import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; |
| 5 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; | ||
| 5 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; | 6 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; |
| 6 | import lombok.AllArgsConstructor; | 7 | import lombok.AllArgsConstructor; |
| 7 | import lombok.Data; | 8 | import lombok.Data; |
| @@ -76,8 +77,9 @@ public class IotModbusTcpSlavePendingRequestManager { | @@ -76,8 +77,9 @@ public class IotModbusTcpSlavePendingRequestManager { | ||
| 76 | if (frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP && frame.getTransactionId() != null) { | 77 | if (frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP && frame.getTransactionId() != null) { |
| 77 | return matchByTransactionId(queue, frame.getTransactionId()); | 78 | return matchByTransactionId(queue, frame.getTransactionId()); |
| 78 | } | 79 | } |
| 79 | - // RTU 模式:FIFO,匹配 slaveId + functionCode | ||
| 80 | - return matchByFifo(queue, frame.getSlaveId(), frame.getFunctionCode()); | 80 | + // RTU 模式:FIFO,匹配 slaveId + functionCode + registerCount |
| 81 | + int responseRegisterCount = extractRegisterCountFromResponse(frame); | ||
| 82 | + return matchByFifo(queue, frame.getSlaveId(), frame.getFunctionCode(), responseRegisterCount); | ||
| 81 | } | 83 | } |
| 82 | 84 | ||
| 83 | /** | 85 | /** |
| @@ -96,13 +98,16 @@ public class IotModbusTcpSlavePendingRequestManager { | @@ -96,13 +98,16 @@ public class IotModbusTcpSlavePendingRequestManager { | ||
| 96 | } | 98 | } |
| 97 | 99 | ||
| 98 | /** | 100 | /** |
| 99 | - * 按 FIFO 匹配 | 101 | + * 按 FIFO 匹配(slaveId + functionCode + registerCount) |
| 100 | */ | 102 | */ |
| 101 | - private PendingRequest matchByFifo(Deque<PendingRequest> queue, int slaveId, int functionCode) { | 103 | + private PendingRequest matchByFifo(Deque<PendingRequest> queue, int slaveId, int functionCode, |
| 104 | + int responseRegisterCount) { | ||
| 102 | Iterator<PendingRequest> it = queue.iterator(); | 105 | Iterator<PendingRequest> it = queue.iterator(); |
| 103 | while (it.hasNext()) { | 106 | while (it.hasNext()) { |
| 104 | PendingRequest req = it.next(); | 107 | PendingRequest req = it.next(); |
| 105 | - if (req.getSlaveId() == slaveId && req.getFunctionCode() == functionCode) { | 108 | + if (req.getSlaveId() == slaveId |
| 109 | + && req.getFunctionCode() == functionCode | ||
| 110 | + && (responseRegisterCount <= 0 || req.getRegisterCount() == responseRegisterCount)) { | ||
| 106 | it.remove(); | 111 | it.remove(); |
| 107 | return req; | 112 | return req; |
| 108 | } | 113 | } |
| @@ -110,6 +115,29 @@ public class IotModbusTcpSlavePendingRequestManager { | @@ -110,6 +115,29 @@ public class IotModbusTcpSlavePendingRequestManager { | ||
| 110 | return null; | 115 | return null; |
| 111 | } | 116 | } |
| 112 | 117 | ||
| 118 | + // TODO @AI:是不是放到 modbus 工具类里,更合适? | ||
| 119 | + /** | ||
| 120 | + * 从响应帧中提取 registerCount(通过 PDU 的 byteCount 推断) | ||
| 121 | + * | ||
| 122 | + * @return registerCount,无法提取时返回 -1(匹配时跳过校验) | ||
| 123 | + */ | ||
| 124 | + private int extractRegisterCountFromResponse(IotModbusFrame frame) { | ||
| 125 | + byte[] pdu = frame.getPdu(); | ||
| 126 | + if (pdu == null || pdu.length < 1) { | ||
| 127 | + return -1; | ||
| 128 | + } | ||
| 129 | + int byteCount = pdu[0] & 0xFF; | ||
| 130 | + int fc = frame.getFunctionCode(); | ||
| 131 | + // FC03/04 寄存器读响应:registerCount = byteCount / 2 | ||
| 132 | + if (fc == IotModbusCommonUtils.FC_READ_HOLDING_REGISTERS | ||
| 133 | + || fc == IotModbusCommonUtils.FC_READ_INPUT_REGISTERS) { | ||
| 134 | + return byteCount / 2; | ||
| 135 | + } | ||
| 136 | + // FC01/02 线圈/离散输入读响应:registerCount = byteCount * 8(线圈数量) | ||
| 137 | + // 但因为按 bit 打包有余位,无法精确反推,返回 -1 跳过校验 | ||
| 138 | + return -1; | ||
| 139 | + } | ||
| 140 | + | ||
| 113 | /** | 141 | /** |
| 114 | * 清理过期请求 | 142 | * 清理过期请求 |
| 115 | */ | 143 | */ |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlavePollScheduler.java
| @@ -55,6 +55,7 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | @@ -55,6 +55,7 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | ||
| 55 | * 轮询单个点位 | 55 | * 轮询单个点位 |
| 56 | */ | 56 | */ |
| 57 | @Override | 57 | @Override |
| 58 | + @SuppressWarnings("DuplicatedCode") | ||
| 58 | protected void pollPoint(Long deviceId, Long pointId) { | 59 | protected void pollPoint(Long deviceId, Long pointId) { |
| 59 | // 1.1 从 configCache 获取最新配置 | 60 | // 1.1 从 configCache 获取最新配置 |
| 60 | IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); | 61 | IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); |
| @@ -78,7 +79,7 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | @@ -78,7 +79,7 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | ||
| 78 | // 2.2 获取 slave ID | 79 | // 2.2 获取 slave ID |
| 79 | IotModbusFrameFormatEnum frameFormat = connection.getFrameFormat(); | 80 | IotModbusFrameFormatEnum frameFormat = connection.getFrameFormat(); |
| 80 | Assert.notNull(frameFormat, "设备 {} 的帧格式不能为空", deviceId); | 81 | Assert.notNull(frameFormat, "设备 {} 的帧格式不能为空", deviceId); |
| 81 | - int slaveId = connection.getSlaveId(); | 82 | + Integer slaveId = connection.getSlaveId(); |
| 82 | Assert.notNull(connection.getSlaveId(), "设备 {} 的 slaveId 不能为空", deviceId); | 83 | Assert.notNull(connection.getSlaveId(), "设备 {} 的 slaveId 不能为空", deviceId); |
| 83 | 84 | ||
| 84 | // 3.1 编码读请求 | 85 | // 3.1 编码读请求 |
| @@ -96,10 +97,13 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | @@ -96,10 +97,13 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | ||
| 96 | System.currentTimeMillis() + requestTimeout); | 97 | System.currentTimeMillis() + requestTimeout); |
| 97 | pendingRequestManager.addRequest(pendingRequest); | 98 | pendingRequestManager.addRequest(pendingRequest); |
| 98 | // 3.3 发送读请求 | 99 | // 3.3 发送读请求 |
| 99 | - connectionManager.sendToDevice(deviceId, data); | ||
| 100 | - log.debug("[pollPoint][设备={}, 点位={}, FC={}, 地址={}, 数量={}]", | ||
| 101 | - deviceId, point.getIdentifier(), point.getFunctionCode(), | ||
| 102 | - point.getRegisterAddress(), point.getRegisterCount()); | 100 | + connectionManager.sendToDevice(deviceId, data).onSuccess(v -> |
| 101 | + log.debug("[pollPoint][设备={}, 点位={}, FC={}, 地址={}, 数量={}]", | ||
| 102 | + deviceId, point.getIdentifier(), point.getFunctionCode(), | ||
| 103 | + point.getRegisterAddress(), point.getRegisterCount()) | ||
| 104 | + ).onFailure(e -> | ||
| 105 | + log.warn("[pollPoint][发送失败, 设备={}, 点位={}]", deviceId, point.getIdentifier(), e) | ||
| 106 | + ); | ||
| 103 | } | 107 | } |
| 104 | 108 | ||
| 105 | } | 109 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/package-info.java
0 → 100644
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java
| @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | ||
| 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; | 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 9 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO; | ||
| 9 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | 10 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; | 11 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; |
| 11 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | 12 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; |
| @@ -64,8 +65,8 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi { | @@ -64,8 +65,8 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi { | ||
| 64 | } | 65 | } |
| 65 | 66 | ||
| 66 | @Override | 67 | @Override |
| 67 | - public CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs() { | ||
| 68 | - return doPost("/rpc-api/iot/modbus/enabled-configs", null, new ParameterizedTypeReference<>() { }); | 68 | + public CommonResult<List<IotModbusDeviceConfigRespDTO>> getModbusDeviceConfigList(IotModbusDeviceConfigListReqDTO listReqDTO) { |
| 69 | + return doPost("/rpc-api/iot/modbus/config-list", listReqDTO, new ParameterizedTypeReference<>() { }); | ||
| 69 | } | 70 | } |
| 70 | 71 | ||
| 71 | @Override | 72 | @Override |
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
| @@ -167,7 +167,7 @@ yudao: | @@ -167,7 +167,7 @@ yudao: | ||
| 167 | # 针对引入的 Modbus TCP Master 组件的配置 | 167 | # 针对引入的 Modbus TCP Master 组件的配置 |
| 168 | # ==================================== | 168 | # ==================================== |
| 169 | - id: modbus-tcp-master-1 | 169 | - id: modbus-tcp-master-1 |
| 170 | - enabled: false | 170 | + enabled: true |
| 171 | protocol: modbus_tcp_master | 171 | protocol: modbus_tcp_master |
| 172 | port: 502 | 172 | port: 502 |
| 173 | modbus-tcp-master: | 173 | modbus-tcp-master: |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveModbusTcpIntegrationTest.java
| @@ -67,9 +67,9 @@ public class IotModbusTcpSlaveModbusTcpIntegrationTest { | @@ -67,9 +67,9 @@ public class IotModbusTcpSlaveModbusTcpIntegrationTest { | ||
| 67 | 67 | ||
| 68 | // ===================== 设备信息(根据实际情况修改,从 iot_device 表查询) ===================== | 68 | // ===================== 设备信息(根据实际情况修改,从 iot_device 表查询) ===================== |
| 69 | 69 | ||
| 70 | - private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; | ||
| 71 | - private static final String DEVICE_NAME = "small"; | ||
| 72 | - private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; | 70 | + private static final String PRODUCT_KEY = "modbus_tcp_slave_product_demo"; |
| 71 | + private static final String DEVICE_NAME = "modbus_tcp_slave_device_demo_tcp"; | ||
| 72 | + private static final String DEVICE_SECRET = "8e4adeb3d25342ab88643421d3fba3f6"; | ||
| 73 | 73 | ||
| 74 | @BeforeAll | 74 | @BeforeAll |
| 75 | static void setUp() { | 75 | static void setUp() { |
| @@ -128,7 +128,6 @@ public class IotModbusTcpSlaveModbusTcpIntegrationTest { | @@ -128,7 +128,6 @@ public class IotModbusTcpSlaveModbusTcpIntegrationTest { | ||
| 128 | 128 | ||
| 129 | // 2. 设置持续监听:每收到一个读请求,自动回复 | 129 | // 2. 设置持续监听:每收到一个读请求,自动回复 |
| 130 | log.info("[testPollingResponse][开始持续监听网关下发的读请求...]"); | 130 | log.info("[testPollingResponse][开始持续监听网关下发的读请求...]"); |
| 131 | - CompletableFuture<Void> done = new CompletableFuture<>(); | ||
| 132 | RecordParser parser = FRAME_DECODER.createRecordParser((frame, frameFormat) -> { | 131 | RecordParser parser = FRAME_DECODER.createRecordParser((frame, frameFormat) -> { |
| 133 | log.info("[testPollingResponse][收到请求: slaveId={}, FC={}, transactionId={}]", | 132 | log.info("[testPollingResponse][收到请求: slaveId={}, FC={}, transactionId={}]", |
| 134 | frame.getSlaveId(), frame.getFunctionCode(), frame.getTransactionId()); | 133 | frame.getSlaveId(), frame.getFunctionCode(), frame.getTransactionId()); |
| @@ -201,6 +200,7 @@ public class IotModbusTcpSlaveModbusTcpIntegrationTest { | @@ -201,6 +200,7 @@ public class IotModbusTcpSlaveModbusTcpIntegrationTest { | ||
| 201 | */ | 200 | */ |
| 202 | private IotModbusFrame authenticate(NetSocket socket) throws Exception { | 201 | private IotModbusFrame authenticate(NetSocket socket) throws Exception { |
| 203 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); | 202 | IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); |
| 203 | + authInfo.setClientId(""); // 特殊:考虑到 modbus 消息长度限制,默认 clientId 不发送 | ||
| 204 | byte[] authFrame = buildAuthFrame(authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword()); | 204 | byte[] authFrame = buildAuthFrame(authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword()); |
| 205 | return sendAndReceive(socket, authFrame); | 205 | return sendAndReceive(socket, authFrame); |
| 206 | } | 206 | } |