syncProductPropertyTable() {
+ productService.syncProductPropertyTable();
+ return success(true);
+ }
+
@GetMapping("/simple-list")
@Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
@Parameter(name = "deviceType", description = "设备类型", example = "1")
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
index ffc92a2132..302b072620 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
@@ -67,10 +67,15 @@ public class IotProductRespVO {
@DictFormat(DictTypeConstants.NET_TYPE)
private Integer netType;
- @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
- @ExcelProperty(value = "数据格式", converter = DictConvert.class)
- @DictFormat(DictTypeConstants.CODEC_TYPE)
- private String codecType;
+ @Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt")
+ @ExcelProperty(value = "协议类型", converter = DictConvert.class)
+ @DictFormat(DictTypeConstants.PROTOCOL_TYPE)
+ private String protocolType;
+
+ @Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json")
+ @ExcelProperty(value = "序列化类型", converter = DictConvert.class)
+ @DictFormat(DictTypeConstants.SERIALIZE_TYPE)
+ private String serializeType;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
index 08c636f7f2..fceede0eb0 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
@@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
+import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -44,9 +46,15 @@ public class IotProductSaveReqVO {
@InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}")
private Integer netType;
- @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
- @NotEmpty(message = "数据格式不能为空")
- private String codecType;
+ @Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt")
+ @InEnum(value = IotProtocolTypeEnum.class, message = "协议类型必须是 {value}")
+ @NotEmpty(message = "协议类型不能为空")
+ private String protocolType;
+
+ @Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json")
+ @InEnum(value = IotSerializeTypeEnum.class, message = "序列化类型必须是 {value}")
+ @NotEmpty(message = "序列化类型不能为空")
+ private String serializeType;
@Schema(description = "是否开启动态注册", example = "false")
@NotNull(message = "是否开启动态注册不能为空")
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.java
index 22837c48ba..539ebbfc20 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.java
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsSummaryRespVO;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
index 7b7d021c3b..0499ac09f4 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -108,10 +108,6 @@ public class IotDeviceDO extends TenantBaseDO {
*/
private LocalDateTime activeTime;
- /**
- * 设备的 IP 地址
- */
- private String ip;
/**
* 固件编号
*
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
index 9f1f6a6a0c..233b2c1402 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
@@ -84,7 +84,7 @@ public class IotDeviceMessageDO {
* 请求方法
*
* 枚举 {@link IotDeviceMessageMethodEnum}
- * 例如说:thing.property.report 属性上报
+ * 例如说:thing.property.post 属性上报
*/
private String method;
/**
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java
new file mode 100644
index 0000000000..26981506b4
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.device;
+
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusModeEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * IoT 设备 Modbus 连接配置 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("iot_device_modbus_config")
+@KeySequence("iot_device_modbus_config_seq")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceModbusConfigDO extends TenantBaseDO {
+
+ /**
+ * 主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 产品编号
+ *
+ * 关联 {@link IotProductDO#getId()}
+ */
+ private Long productId;
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ */
+ private Long deviceId;
+
+ /**
+ * Modbus 服务器 IP 地址
+ */
+ private String ip;
+ /**
+ * Modbus 服务器端口
+ */
+ private Integer port;
+ /**
+ * 从站地址
+ */
+ private Integer slaveId;
+ /**
+ * 连接超时时间,单位:毫秒
+ */
+ private Integer timeout;
+ /**
+ * 重试间隔,单位:毫秒
+ */
+ private Integer retryInterval;
+ /**
+ * 模式
+ *
+ * @see IotModbusModeEnum
+ */
+ private Integer mode;
+ /**
+ * 数据帧格式
+ *
+ * @see IotModbusFrameFormatEnum
+ */
+ private Integer frameFormat;
+ /**
+ * 状态
+ *
+ * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java
new file mode 100644
index 0000000000..cfc084166a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.device;
+
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusByteOrderEnum;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusRawDataTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * IoT 设备 Modbus 点位配置 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("iot_device_modbus_point")
+@KeySequence("iot_device_modbus_point_seq")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceModbusPointDO extends TenantBaseDO {
+
+ /**
+ * 主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ */
+ private Long deviceId;
+ /**
+ * 物模型属性编号
+ *
+ * 关联 {@link IotThingModelDO#getId()}
+ */
+ private Long thingModelId;
+ /**
+ * 属性标识符
+ *
+ * 冗余 {@link IotThingModelDO#getIdentifier()}
+ */
+ private String identifier;
+ /**
+ * 属性名称
+ *
+ * 冗余 {@link IotThingModelDO#getName()}
+ */
+ private String name;
+
+ // ========== Modbus 协议配置 ==========
+
+ /**
+ * Modbus 功能码
+ *
+ * 取值范围:FC01-04(读线圈、读离散输入、读保持寄存器、读输入寄存器)
+ */
+ private Integer functionCode;
+ /**
+ * 寄存器起始地址
+ */
+ private Integer registerAddress;
+ /**
+ * 寄存器数量
+ */
+ private Integer registerCount;
+ /**
+ * 字节序
+ *
+ * 枚举 {@link IotModbusByteOrderEnum}
+ */
+ private String byteOrder;
+ /**
+ * 原始数据类型
+ *
+ * 枚举 {@link IotModbusRawDataTypeEnum}
+ */
+ private String rawDataType;
+ /**
+ * 缩放因子
+ */
+ private BigDecimal scale;
+ /**
+ * 轮询间隔(毫秒)
+ */
+ private Integer pollInterval;
+ /**
+ * 状态
+ *
+ * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
index e296b35017..a1a77fc5e4 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
@@ -78,12 +78,16 @@ public class IotProductDO extends TenantBaseDO {
*/
private Integer netType;
/**
- * 数据格式(编解码器类型)
+ * 协议类型
*
- * 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}
- *
- * 目的:用于 gateway-server 解析消息格式
+ * 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum}
*/
- private String codecType;
+ private String protocolType;
+ /**
+ * 序列化类型
+ *
+ * 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum}
+ */
+ private String serializeType;
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java
new file mode 100644
index 0000000000..b18769c6a6
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.device;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 设备 Modbus 连接配置 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotDeviceModbusConfigMapper extends BaseMapperX {
+
+ default IotDeviceModbusConfigDO selectByDeviceId(Long deviceId) {
+ return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId);
+ }
+
+ default List selectList(IotModbusDeviceConfigListReqDTO reqDTO) {
+ return selectList(new LambdaQueryWrapperX()
+ .eqIfPresent(IotDeviceModbusConfigDO::getStatus, reqDTO.getStatus())
+ .eqIfPresent(IotDeviceModbusConfigDO::getMode, reqDTO.getMode())
+ .inIfPresent(IotDeviceModbusConfigDO::getDeviceId, reqDTO.getDeviceIds()));
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusPointMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusPointMapper.java
new file mode 100644
index 0000000000..7c9b5d3bae
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusPointMapper.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.device;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * IoT 设备 Modbus 点位配置 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotDeviceModbusPointMapper extends BaseMapperX {
+
+ default PageResult selectPage(IotDeviceModbusPointPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(IotDeviceModbusPointDO::getDeviceId, reqVO.getDeviceId())
+ .likeIfPresent(IotDeviceModbusPointDO::getIdentifier, reqVO.getIdentifier())
+ .likeIfPresent(IotDeviceModbusPointDO::getName, reqVO.getName())
+ .eqIfPresent(IotDeviceModbusPointDO::getFunctionCode, reqVO.getFunctionCode())
+ .eqIfPresent(IotDeviceModbusPointDO::getStatus, reqVO.getStatus())
+ .orderByDesc(IotDeviceModbusPointDO::getId));
+ }
+
+ default List selectListByDeviceIdsAndStatus(Collection deviceIds, Integer status) {
+ return selectList(new LambdaQueryWrapperX()
+ .in(IotDeviceModbusPointDO::getDeviceId, deviceIds)
+ .eq(IotDeviceModbusPointDO::getStatus, status));
+ }
+
+ default IotDeviceModbusPointDO selectByDeviceIdAndIdentifier(Long deviceId, String identifier) {
+ return selectOne(IotDeviceModbusPointDO::getDeviceId, deviceId,
+ IotDeviceModbusPointDO::getIdentifier, identifier);
+ }
+
+ default void updateByThingModelId(Long thingModelId, IotDeviceModbusPointDO updateObj) {
+ update(updateObj, new LambdaQueryWrapperX()
+ .eq(IotDeviceModbusPointDO::getThingModelId, thingModelId));
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
index 2ed27dbb67..8c611d0d46 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
@@ -38,6 +38,10 @@ public interface IotProductMapper extends BaseMapperX {
.apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
}
+ default List selectListByStatus(Integer status) {
+ return selectList(IotProductDO::getStatus, status);
+ }
+
default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) {
return selectCount(new LambdaQueryWrapperX()
.geIfPresent(IotProductDO::getCreateTime, createTime));
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
index 4f07ddfc1c..cf6bec1181 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
@@ -8,8 +8,8 @@ package cn.iocoder.yudao.module.iot.enums;
public class DictTypeConstants {
public static final String NET_TYPE = "iot_net_type";
- public static final String LOCATION_TYPE = "iot_location_type";
- public static final String CODEC_TYPE = "iot_codec_type";
+ public static final String PROTOCOL_TYPE = "iot_protocol_type";
+ public static final String SERIALIZE_TYPE = "iot_serialize_type";
public static final String PRODUCT_STATUS = "iot_product_status";
public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type";
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
index 3679dbf1ce..065eb2d229 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -54,6 +54,14 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
+ // ========== 设备 Modbus 配置 1-050-006-000 ==========
+ ErrorCode DEVICE_MODBUS_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "设备 Modbus 连接配置不存在");
+ ErrorCode DEVICE_MODBUS_CONFIG_EXISTS = new ErrorCode(1_050_006_001, "设备 Modbus 连接配置已存在");
+
+ // ========== 设备 Modbus 点位 1-050-007-000 ==========
+ ErrorCode DEVICE_MODBUS_POINT_NOT_EXISTS = new ErrorCode(1_050_007_000, "设备 Modbus 点位配置不存在");
+ ErrorCode DEVICE_MODBUS_POINT_EXISTS = new ErrorCode(1_050_007_001, "设备 Modbus 点位配置已存在");
+
// ========== OTA 固件相关 1-050-008-000 ==========
ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
index 440fab5f53..96b477d69d 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
@@ -19,9 +19,9 @@ public enum IotDataSinkTypeEnum implements ArrayValuable {
TCP(2, "TCP"),
WEBSOCKET(3, "WebSocket"),
- MQTT(10, "MQTT"), // TODO 待实现;
+ MQTT(10, "MQTT"), // TODO @puhui999:待实现;
- DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。
+ DATABASE(20, "Database"), // TODO @puhui999:待实现;
REDIS(21, "Redis"),
ROCKETMQ(30, "RocketMQ"),
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java
index 6bd27a679a..789b2f25ad 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java
@@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.framework.iot.config.YudaoIotProperties;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java
index 8a15c5e7bb..9aa2312117 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java
@@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java
index 7e039d0327..31c507889b 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.device;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -67,7 +67,6 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber {
+public class IotDataRuleMessageSubscriber implements IotMessageSubscriber {
@Resource
private IotDataRuleService dataRuleService;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageSubscriber.java
similarity index 90%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageSubscriber.java
index 19e1f18ba3..de74bebcce 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageSubscriber.java
@@ -9,7 +9,6 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
-// TODO @puhui999:后面重构哈
/**
* 针对 {@link IotDeviceMessage} 的消费者,处理规则场景
*
@@ -17,7 +16,7 @@ import org.springframework.stereotype.Component;
*/
@Component
@Slf4j
-public class IotSceneRuleMessageHandler implements IotMessageSubscriber {
+public class IotSceneRuleMessageSubscriber implements IotMessageSubscriber {
@Resource
private IotSceneRuleService sceneRuleService;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigService.java
new file mode 100644
index 0000000000..2d9ef7ec61
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigService.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.iot.service.device;
+
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
+import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * IoT 设备 Modbus 连接配置 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface IotDeviceModbusConfigService {
+
+ /**
+ * 保存设备 Modbus 连接配置(新增或更新)
+ *
+ * @param saveReqVO 保存信息
+ */
+ void saveDeviceModbusConfig(@Valid IotDeviceModbusConfigSaveReqVO saveReqVO);
+
+ /**
+ * 获得设备 Modbus 连接配置
+ *
+ * @param id 编号
+ * @return 设备 Modbus 连接配置
+ */
+ IotDeviceModbusConfigDO getDeviceModbusConfig(Long id);
+
+ /**
+ * 根据设备编号获得 Modbus 连接配置
+ *
+ * @param deviceId 设备编号
+ * @return 设备 Modbus 连接配置
+ */
+ IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId);
+
+ /**
+ * 获得 Modbus 连接配置列表
+ *
+ * @param listReqDTO 查询参数
+ * @return Modbus 连接配置列表
+ */
+ List getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO);
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigServiceImpl.java
new file mode 100644
index 0000000000..2a8ae4e439
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigServiceImpl.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.iot.service.device;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
+import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
+import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusConfigMapper;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+/**
+ * IoT 设备 Modbus 连接配置 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigService {
+
+ @Resource
+ private IotDeviceModbusConfigMapper modbusConfigMapper;
+
+ @Resource
+ private IotDeviceService deviceService;
+ @Resource
+ private IotProductService productService;
+
+ @Override
+ public void saveDeviceModbusConfig(IotDeviceModbusConfigSaveReqVO saveReqVO) {
+ // 1.1 校验设备存在
+ IotDeviceDO device = deviceService.validateDeviceExists(saveReqVO.getDeviceId());
+ // 1.2 根据产品 protocolType 条件校验
+ IotProductDO product = productService.getProduct(device.getProductId());
+ Assert.notNull(product, "产品不存在");
+ validateModbusConfigByProtocolType(saveReqVO, product.getProtocolType());
+
+ // 2. 根据数据库中是否已有配置,决定是新增还是更新
+ IotDeviceModbusConfigDO existConfig = modbusConfigMapper.selectByDeviceId(saveReqVO.getDeviceId());
+ if (existConfig == null) {
+ IotDeviceModbusConfigDO modbusConfig = BeanUtils.toBean(saveReqVO, IotDeviceModbusConfigDO.class);
+ modbusConfigMapper.insert(modbusConfig);
+ } else {
+ IotDeviceModbusConfigDO updateObj = BeanUtils.toBean(saveReqVO, IotDeviceModbusConfigDO.class,
+ o -> o.setId(existConfig.getId()));
+ modbusConfigMapper.updateById(updateObj);
+ }
+ }
+
+ @Override
+ public IotDeviceModbusConfigDO getDeviceModbusConfig(Long id) {
+ return modbusConfigMapper.selectById(id);
+ }
+
+ @Override
+ public IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId) {
+ return modbusConfigMapper.selectByDeviceId(deviceId);
+ }
+
+ @Override
+ public List getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO) {
+ return modbusConfigMapper.selectList(listReqDTO);
+ }
+
+ private void validateModbusConfigByProtocolType(IotDeviceModbusConfigSaveReqVO saveReqVO, String protocolType) {
+ IotProtocolTypeEnum protocolTypeEnum = IotProtocolTypeEnum.of(protocolType);
+ if (protocolTypeEnum == null) {
+ return;
+ }
+ if (protocolTypeEnum == IotProtocolTypeEnum.MODBUS_TCP_CLIENT) {
+ Assert.isTrue(StrUtil.isNotEmpty(saveReqVO.getIp()), "Client 模式下,IP 地址不能为空");
+ Assert.notNull(saveReqVO.getPort(), "Client 模式下,端口不能为空");
+ Assert.notNull(saveReqVO.getTimeout(), "Client 模式下,连接超时时间不能为空");
+ Assert.notNull(saveReqVO.getRetryInterval(), "Client 模式下,重试间隔不能为空");
+ } else if (protocolTypeEnum == IotProtocolTypeEnum.MODBUS_TCP_SERVER) {
+ Assert.notNull(saveReqVO.getMode(), "Server 模式下,工作模式不能为空");
+ Assert.notNull(saveReqVO.getFrameFormat(), "Server 模式下,数据帧格式不能为空");
+ }
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointService.java
new file mode 100644
index 0000000000..0be20e1057
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointService.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.iot.service.device;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * IoT 设备 Modbus 点位配置 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface IotDeviceModbusPointService {
+
+ /**
+ * 创建设备 Modbus 点位配置
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createDeviceModbusPoint(@Valid IotDeviceModbusPointSaveReqVO createReqVO);
+
+ /**
+ * 更新设备 Modbus 点位配置
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateDeviceModbusPoint(@Valid IotDeviceModbusPointSaveReqVO updateReqVO);
+
+ /**
+ * 删除设备 Modbus 点位配置
+ *
+ * @param id 编号
+ */
+ void deleteDeviceModbusPoint(Long id);
+
+ /**
+ * 获得设备 Modbus 点位配置
+ *
+ * @param id 编号
+ * @return 设备 Modbus 点位配置
+ */
+ IotDeviceModbusPointDO getDeviceModbusPoint(Long id);
+
+ /**
+ * 获得设备 Modbus 点位配置分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 设备 Modbus 点位配置分页
+ */
+ PageResult getDeviceModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO);
+
+ /**
+ * 物模型变更时,更新关联点位的冗余字段(identifier、name)
+ *
+ * @param thingModelId 物模型编号
+ * @param identifier 物模型标识符
+ * @param name 物模型名称
+ */
+ void updateDeviceModbusPointByThingModel(Long thingModelId, String identifier, String name);
+
+ /**
+ * 根据设备编号批量获得启用的点位配置 Map
+ *
+ * @param deviceIds 设备编号集合
+ * @return 设备点位 Map,key 为设备编号,value 为点位配置列表
+ */
+ Map> getEnabledDeviceModbusPointMapByDeviceIds(Collection deviceIds);
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointServiceImpl.java
new file mode 100644
index 0000000000..7683aa7ecc
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointServiceImpl.java
@@ -0,0 +1,135 @@
+package cn.iocoder.yudao.module.iot.service.device;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusPointMapper;
+import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
+import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
+
+/**
+ * IoT 设备 Modbus 点位配置 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class IotDeviceModbusPointServiceImpl implements IotDeviceModbusPointService {
+
+ @Resource
+ private IotDeviceModbusPointMapper modbusPointMapper;
+
+ @Resource
+ private IotDeviceService deviceService;
+
+ @Resource
+ private IotThingModelService thingModelService;
+
+ @Override
+ public Long createDeviceModbusPoint(IotDeviceModbusPointSaveReqVO createReqVO) {
+ // 1.1 校验设备存在
+ deviceService.validateDeviceExists(createReqVO.getDeviceId());
+ // 1.2 校验物模型属性存在
+ IotThingModelDO thingModel = validateThingModelExists(createReqVO.getThingModelId());
+ // 1.3 校验同一设备下点位唯一性(基于 identifier)
+ validateDeviceModbusPointUnique(createReqVO.getDeviceId(), thingModel.getIdentifier(), null);
+
+ // 2. 插入
+ IotDeviceModbusPointDO modbusPoint = BeanUtils.toBean(createReqVO, IotDeviceModbusPointDO.class,
+ o -> o.setIdentifier(thingModel.getIdentifier()).setName(thingModel.getName()));
+ modbusPointMapper.insert(modbusPoint);
+ return modbusPoint.getId();
+ }
+
+ @Override
+ public void updateDeviceModbusPoint(IotDeviceModbusPointSaveReqVO updateReqVO) {
+ // 1.1 校验存在
+ validateDeviceModbusPointExists(updateReqVO.getId());
+ // 1.2 校验设备存在
+ deviceService.validateDeviceExists(updateReqVO.getDeviceId());
+ // 1.3 校验物模型属性存在
+ IotThingModelDO thingModel = validateThingModelExists(updateReqVO.getThingModelId());
+ // 1.4 校验同一设备下点位唯一性
+ validateDeviceModbusPointUnique(updateReqVO.getDeviceId(), thingModel.getIdentifier(), updateReqVO.getId());
+
+ // 2. 更新
+ IotDeviceModbusPointDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceModbusPointDO.class,
+ o -> o.setIdentifier(thingModel.getIdentifier()).setName(thingModel.getName()));
+ modbusPointMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void updateDeviceModbusPointByThingModel(Long thingModelId, String identifier, String name) {
+ IotDeviceModbusPointDO updateObj = new IotDeviceModbusPointDO()
+ .setIdentifier(identifier).setName(name);
+ modbusPointMapper.updateByThingModelId(thingModelId, updateObj);
+ }
+
+ private IotThingModelDO validateThingModelExists(Long id) {
+ IotThingModelDO thingModel = thingModelService.getThingModel(id);
+ if (thingModel == null) {
+ throw exception(THING_MODEL_NOT_EXISTS);
+ }
+ return thingModel;
+ }
+
+ @Override
+ public void deleteDeviceModbusPoint(Long id) {
+ // 校验存在
+ validateDeviceModbusPointExists(id);
+ // 删除
+ modbusPointMapper.deleteById(id);
+ }
+
+ private void validateDeviceModbusPointExists(Long id) {
+ IotDeviceModbusPointDO point = modbusPointMapper.selectById(id);
+ if (point == null) {
+ throw exception(DEVICE_MODBUS_POINT_NOT_EXISTS);
+ }
+ }
+
+ private void validateDeviceModbusPointUnique(Long deviceId, String identifier, Long excludeId) {
+ IotDeviceModbusPointDO point = modbusPointMapper.selectByDeviceIdAndIdentifier(deviceId, identifier);
+ if (point != null && ObjUtil.notEqual(point.getId(), excludeId)) {
+ throw exception(DEVICE_MODBUS_POINT_EXISTS);
+ }
+ }
+
+ @Override
+ public IotDeviceModbusPointDO getDeviceModbusPoint(Long id) {
+ return modbusPointMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getDeviceModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO) {
+ return modbusPointMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public Map> getEnabledDeviceModbusPointMapByDeviceIds(Collection deviceIds) {
+ if (CollUtil.isEmpty(deviceIds)) {
+ return Collections.emptyMap();
+ }
+ List pointList = modbusPointMapper.selectListByDeviceIdsAndStatus(
+ deviceIds, CommonStatusEnum.ENABLE.getStatus());
+ return convertMultiMap(pointList, IotDeviceModbusPointDO::getDeviceId);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
index 5a622e5654..74339af6df 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
index 4ec70e08fb..f05776f4cb 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
@@ -17,7 +17,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
@@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoChangeReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
@@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
if (BooleanUtil.isFalse(product.getRegisterEnabled())) {
throw exception(DEVICE_REGISTER_DISABLED);
}
- // 1.3 验证 productSecret
- if (ObjUtil.notEqual(product.getProductSecret(), reqDTO.getProductSecret())) {
+ // 1.3 【重要!!!】验证签名
+ if (!IotProductAuthUtils.verifySign(reqDTO.getProductKey(), reqDTO.getDeviceName(),
+ product.getProductSecret(), reqDTO.getSign())) {
throw exception(DEVICE_REGISTER_SECRET_INVALID);
}
return TenantUtils.execute(product.getTenantId(), () -> {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java
index eb75b91540..f9cd776210 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java
@@ -3,11 +3,15 @@ package cn.iocoder.yudao.module.iot.service.ota;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
-import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.core.topic.ota.IotDeviceOtaProgressReqDTO;
+import cn.iocoder.yudao.module.iot.core.topic.ota.IotDeviceOtaUpgradeReqDTO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
@@ -133,9 +137,9 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) {
try {
// 1. 推送 OTA 任务记录
- IotDeviceMessage message = IotDeviceMessage.buildOtaUpgrade(
- fireware.getVersion(), fireware.getFileUrl(), fireware.getFileSize(),
- fireware.getFileDigestAlgorithm(), fireware.getFileDigestValue());
+ IotDeviceOtaUpgradeReqDTO params = BeanUtils.toBean(fireware, IotDeviceOtaUpgradeReqDTO.class);
+ IotDeviceMessage message = IotDeviceMessage.requestOf(
+ IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), params);
deviceMessageService.sendDeviceMessage(message, device);
// 2. 更新 OTA 升级记录状态为进行中
@@ -163,17 +167,16 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
@Override
@Transactional(rollbackFor = Exception.class)
- @SuppressWarnings("unchecked")
public void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message) {
// 1.1 参数解析
- Map params = (Map) message.getParams();
- String version = MapUtil.getStr(params, "version");
+ IotDeviceOtaProgressReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceOtaProgressReqDTO.class);
+ String version = params.getVersion();
Assert.notBlank(version, "version 不能为空");
- Integer status = MapUtil.getInt(params, "status");
+ Integer status = params.getStatus();
Assert.notNull(status, "status 不能为空");
Assert.notNull(IotOtaTaskRecordStatusEnum.of(status), "status 状态不正确");
- String description = MapUtil.getStr(params, "description");
- Integer progress = MapUtil.getInt(params, "progress");
+ String description = params.getDescription();
+ Integer progress = params.getProgress();
Assert.notNull(progress, "progress 不能为空");
Assert.isTrue(progress >= 0 && progress <= 100, "progress 必须在 0-100 之间");
// 1.2 查询 OTA 升级记录
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
index d4292ef521..f31961cfd1 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
+++ b/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;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* IoT 产品 Service 接口
@@ -121,6 +124,24 @@ public interface IotProductService {
*/
Long getProductCount(@Nullable LocalDateTime createTime);
+ /**
+ * 批量获得产品列表
+ *
+ * @param ids 产品编号集合
+ * @return 产品列表
+ */
+ List getProductList(Collection ids);
+
+ /**
+ * 批量获得产品 Map
+ *
+ * @param ids 产品编号集合
+ * @return 产品 Map(key: 产品编号, value: 产品)
+ */
+ default Map getProductMap(Collection ids) {
+ return convertMap(getProductList(ids), IotProductDO::getId);
+ }
+
/**
* 批量校验产品存在
*
@@ -128,4 +149,11 @@ public interface IotProductService {
*/
void validateProductsExist(Collection ids);
+ /**
+ * 同步产品的 TDengine 表结构
+ *
+ * 目的:当 MySQL 和 TDengine 不同步时,强制将已发布产品的表结构同步到 TDengine 中
+ */
+ void syncProductPropertyTable();
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
index e001f46a2b..4c8a789b93 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
@@ -15,8 +15,10 @@ import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -33,6 +35,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
*
* @author ahh
*/
+@Slf4j
@Service
@Validated
public class IotProductServiceImpl implements IotProductService {
@@ -40,10 +43,11 @@ public class IotProductServiceImpl implements IotProductService {
@Resource
private IotProductMapper productMapper;
- @Resource
- private IotDevicePropertyService devicePropertyDataService;
@Resource
private IotDeviceService deviceService;
+ @Resource
+ @Lazy // 延迟加载,避免循环依赖
+ private IotDevicePropertyService devicePropertyDataService;
@Override
public Long createProduct(IotProductSaveReqVO createReqVO) {
@@ -171,6 +175,32 @@ public class IotProductServiceImpl implements IotProductService {
return productMapper.selectCountByCreateTime(createTime);
}
+ @Override
+ public List getProductList(Collection ids) {
+ return productMapper.selectByIds(ids);
+ }
+
+ @Override
+ public void syncProductPropertyTable() {
+ // 1. 获取所有已发布的产品
+ List products = productMapper.selectListByStatus(
+ IotProductStatusEnum.PUBLISHED.getStatus());
+ log.info("[syncProductPropertyTable][开始同步,已发布产品数量({})]", products.size());
+
+ // 2. 遍历同步 TDengine 表结构(创建产品超级表数据模型)
+ int successCount = 0;
+ for (IotProductDO product : products) {
+ try {
+ devicePropertyDataService.defineDevicePropertyData(product.getId());
+ successCount++;
+ log.info("[syncProductPropertyTable][产品({}/{}) 同步成功]", product.getId(), product.getName());
+ } catch (Exception e) {
+ log.error("[syncProductPropertyTable][产品({}/{}) 同步失败]", product.getId(), product.getName(), e);
+ }
+ }
+ log.info("[syncProductPropertyTable][同步完成,成功({}/{})个]", successCount, products.size());
+ }
+
@Override
public void validateProductsExist(Collection ids) {
if (CollUtil.isEmpty(ids)) {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
index 4319469082..cc282e1b8d 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
@@ -14,8 +14,6 @@ import java.time.Duration;
// TODO @芋艿:数据库
// TODO @芋艿:mqtt
-// TODO @芋艿:tcp
-// TODO @芋艿:websocket
/**
* 可缓存的 {@link IotDataRuleAction} 抽象实现
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDevicePropertySetSceneRuleAction.java
similarity index 98%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDevicePropertySetSceneRuleAction.java
index 79da298442..746e18d923 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDevicePropertySetSceneRuleAction.java
@@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
-import java.util.Map;
/**
* IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类
@@ -24,7 +23,7 @@ import java.util.Map;
*/
@Component
@Slf4j
-public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction {
+public class IotDevicePropertySetSceneRuleAction implements IotSceneRuleAction {
@Resource
private IotDeviceService deviceService;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
index ca04ecd5f3..4a8b97475b 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -50,6 +51,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
@Resource
@Lazy // 延迟加载,解决循环依赖
private IotProductService productService;
+ @Resource
+ @Lazy // 延迟加载,解决循环依赖
+ private IotDeviceModbusPointService deviceModbusPointService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -84,7 +88,11 @@ public class IotThingModelServiceImpl implements IotThingModelService {
IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO);
thingModelMapper.updateById(thingModel);
- // 3. 删除缓存
+ // 3. 同步更新 Modbus 点位的冗余字段(identifier、name)
+ deviceModbusPointService.updateDeviceModbusPointByThingModel(
+ updateReqVO.getId(), updateReqVO.getIdentifier(), updateReqVO.getName());
+
+ // 4. 删除缓存
deleteThingModelListCache(updateReqVO.getProductId());
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java
index 7fcae15713..7f4ec70d6a 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene;
import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
index 6e7caecdd3..b83e0b0892 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
index f41b0ec590..79511aaa9c 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
index cc0cb071a1..c0b3f9df31 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
@@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.iot.core.biz;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
-import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
-import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
-import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
+import cn.iocoder.yudao.module.iot.core.biz.dto.*;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
@@ -50,4 +47,12 @@ public interface IotDeviceCommonApi {
*/
CommonResult> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO);
+ /**
+ * 获取 Modbus 设备配置列表
+ *
+ * @param listReqDTO 查询参数
+ * @return Modbus 设备配置列表
+ */
+ CommonResult> getModbusDeviceConfigList(IotModbusDeviceConfigListReqDTO listReqDTO);
+
}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java
index add1167801..8ad2c5bcd0 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java
@@ -34,8 +34,12 @@ public class IotDeviceRespDTO {
*/
private Long productId;
/**
- * 编解码器类型
+ * 协议类型
*/
- private String codecType;
+ private String protocolType;
+ /**
+ * 序列化类型
+ */
+ private String serializeType;
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigListReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigListReqDTO.java
new file mode 100644
index 0000000000..7865a09f00
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigListReqDTO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.core.biz.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.Set;
+
+/**
+ * IoT Modbus 设备配置列表查询 Request DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@Accessors(chain = true)
+public class IotModbusDeviceConfigListReqDTO {
+
+ /**
+ * 状态
+ */
+ private Integer status;
+
+ /**
+ * 模式
+ */
+ private Integer mode;
+
+ /**
+ * 协议类型
+ */
+ private String protocolType;
+
+ /**
+ * 设备 ID 集合
+ */
+ private Set deviceIds;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigRespDTO.java
new file mode 100644
index 0000000000..683bcef4c4
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigRespDTO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.iot.core.biz.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * IoT Modbus 设备配置 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class IotModbusDeviceConfigRespDTO {
+
+ /**
+ * 设备编号
+ */
+ private Long deviceId;
+ /**
+ * 产品标识
+ */
+ private String productKey;
+ /**
+ * 设备名称
+ */
+ private String deviceName;
+
+ // ========== Modbus 连接配置 ==========
+
+ /**
+ * Modbus 服务器 IP 地址
+ */
+ private String ip;
+ /**
+ * Modbus 服务器端口
+ */
+ private Integer port;
+ /**
+ * 从站地址
+ */
+ private Integer slaveId;
+ /**
+ * 连接超时时间,单位:毫秒
+ */
+ private Integer timeout;
+ /**
+ * 重试间隔,单位:毫秒
+ */
+ private Integer retryInterval;
+ /**
+ * 模式
+ */
+ private Integer mode;
+ /**
+ * 数据帧格式
+ */
+ private Integer frameFormat;
+
+ // ========== Modbus 点位配置 ==========
+
+ /**
+ * 点位列表
+ */
+ private List points;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java
new file mode 100644
index 0000000000..dd6f9cf370
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.iot.core.biz.dto;
+
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusByteOrderEnum;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusRawDataTypeEnum;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * IoT Modbus 点位配置 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class IotModbusPointRespDTO {
+
+ /**
+ * 点位编号
+ */
+ private Long id;
+ /**
+ * 属性标识符(物模型的 identifier)
+ */
+ private String identifier;
+ /**
+ * 属性名称(物模型的 name)
+ */
+ private String name;
+
+ // ========== Modbus 协议配置 ==========
+
+ /**
+ * Modbus 功能码
+ *
+ * 取值范围:FC01-04(读线圈、读离散输入、读保持寄存器、读输入寄存器)
+ */
+ private Integer functionCode;
+ /**
+ * 寄存器起始地址
+ */
+ private Integer registerAddress;
+ /**
+ * 寄存器数量
+ */
+ private Integer registerCount;
+ /**
+ * 字节序
+ *
+ * 枚举 {@link IotModbusByteOrderEnum}
+ */
+ private String byteOrder;
+ /**
+ * 原始数据类型
+ *
+ * 枚举 {@link IotModbusRawDataTypeEnum}
+ */
+ private String rawDataType;
+ /**
+ * 缩放因子
+ */
+ private BigDecimal scale;
+ /**
+ * 轮询间隔(毫秒)
+ */
+ private Integer pollInterval;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
index d980032842..3b4495e333 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
@@ -64,7 +64,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable {
// ========== OTA 固件 ==========
// 可参考:https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates
- OTA_UPGRADE("thing.ota.upgrade", "OTA 固定信息推送", false),
+ OTA_UPGRADE("thing.ota.upgrade", "OTA 固件信息推送", false),
OTA_PROGRESS("thing.ota.progress", "OTA 升级进度上报", true),
;
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotProtocolTypeEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotProtocolTypeEnum.java
new file mode 100644
index 0000000000..753605426b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotProtocolTypeEnum.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.iot.core.enums;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 协议类型枚举
+ *
+ * 用于定义传输层协议类型
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotProtocolTypeEnum implements ArrayValuable {
+
+ TCP("tcp"),
+ UDP("udp"),
+ WEBSOCKET("websocket"),
+ HTTP("http"),
+ MQTT("mqtt"),
+ EMQX("emqx"),
+ COAP("coap"),
+ MODBUS_TCP_CLIENT("modbus_tcp_client"),
+ MODBUS_TCP_SERVER("modbus_tcp_server");
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotProtocolTypeEnum::getType).toArray(String[]::new);
+
+ /**
+ * 类型
+ */
+ private final String type;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+ public static IotProtocolTypeEnum of(String type) {
+ return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotSerializeTypeEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotSerializeTypeEnum.java
new file mode 100644
index 0000000000..0f9400f362
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotSerializeTypeEnum.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.iot.core.enums;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 序列化类型枚举
+ *
+ * 用于定义设备消息的序列化格式
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotSerializeTypeEnum implements ArrayValuable {
+
+ JSON("json"),
+ BINARY("binary");
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotSerializeTypeEnum::getType).toArray(String[]::new);
+
+ /**
+ * 类型
+ */
+ private final String type;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+ public static IotSerializeTypeEnum of(String type) {
+ return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceStateEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/device/IotDeviceStateEnum.java
similarity index 94%
rename from yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceStateEnum.java
rename to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/device/IotDeviceStateEnum.java
index d0ff8357e7..fd8ca0e310 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceStateEnum.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/device/IotDeviceStateEnum.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.core.enums;
+package cn.iocoder.yudao.module.iot.core.enums.device;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusByteOrderEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusByteOrderEnum.java
new file mode 100644
index 0000000000..229257a17a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusByteOrderEnum.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.iot.core.enums.modbus;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT Modbus 字节序枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@RequiredArgsConstructor
+public enum IotModbusByteOrderEnum implements ArrayValuable {
+
+ AB("AB", "大端序(16位)", 2),
+ BA("BA", "小端序(16位)", 2),
+ ABCD("ABCD", "大端序(32位)", 4),
+ CDAB("CDAB", "大端字交换(32位)", 4),
+ DCBA("DCBA", "小端序(32位)", 4),
+ BADC("BADC", "小端字交换(32位)", 4);
+
+ public static final String[] ARRAYS = Arrays.stream(values())
+ .map(IotModbusByteOrderEnum::getOrder)
+ .toArray(String[]::new);
+
+ /**
+ * 字节序
+ */
+ private final String order;
+ /**
+ * 名称
+ */
+ private final String name;
+ /**
+ * 字节数
+ */
+ private final Integer byteCount;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+ public static IotModbusByteOrderEnum getByOrder(String order) {
+ return Arrays.stream(values())
+ .filter(e -> e.getOrder().equals(order))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusFrameFormatEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusFrameFormatEnum.java
new file mode 100644
index 0000000000..bf1de5414b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusFrameFormatEnum.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.core.enums.modbus;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT Modbus 数据帧格式枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@RequiredArgsConstructor
+public enum IotModbusFrameFormatEnum implements ArrayValuable {
+
+ MODBUS_TCP(1),
+ MODBUS_RTU(2);
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(IotModbusFrameFormatEnum::getFormat)
+ .toArray(Integer[]::new);
+
+ /**
+ * 格式
+ */
+ private final Integer format;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusModeEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusModeEnum.java
new file mode 100644
index 0000000000..ed4b3891e7
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusModeEnum.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.core.enums.modbus;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT Modbus 工作模式枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@RequiredArgsConstructor
+public enum IotModbusModeEnum implements ArrayValuable {
+
+ POLLING(1, "云端轮询"),
+ ACTIVE_REPORT(2, "边缘采集");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(IotModbusModeEnum::getMode)
+ .toArray(Integer[]::new);
+
+ /**
+ * 工作模式
+ */
+ private final Integer mode;
+ /**
+ * 模式名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusRawDataTypeEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusRawDataTypeEnum.java
new file mode 100644
index 0000000000..522b0aeafa
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusRawDataTypeEnum.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.iot.core.enums.modbus;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT Modbus 原始数据类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@RequiredArgsConstructor
+public enum IotModbusRawDataTypeEnum implements ArrayValuable {
+
+ INT16("INT16", "有符号 16 位整数", 1),
+ UINT16("UINT16", "无符号 16 位整数", 1),
+ INT32("INT32", "有符号 32 位整数", 2),
+ UINT32("UINT32", "无符号 32 位整数", 2),
+ FLOAT("FLOAT", "32 位浮点数", 2),
+ DOUBLE("DOUBLE", "64 位浮点数", 4),
+ BOOLEAN("BOOLEAN", "布尔值(用于线圈)", 1),
+ STRING("STRING", "字符串", null); // null 表示可变长度
+
+ public static final String[] ARRAYS = Arrays.stream(values())
+ .map(IotModbusRawDataTypeEnum::getType)
+ .toArray(String[]::new);
+
+ /**
+ * 数据类型
+ */
+ private final String type;
+ /**
+ * 名称
+ */
+ private final String name;
+ /**
+ * 寄存器数量(null 表示可变)
+ */
+ private final Integer registerCount;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+ public static IotModbusRawDataTypeEnum getByType(String type) {
+ return Arrays.stream(values())
+ .filter(e -> e.getType().equals(type))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java
index c621467610..646eb36bc7 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java
@@ -24,4 +24,14 @@ public interface IotMessageBus {
*/
void register(IotMessageSubscriber> subscriber);
+ /**
+ * 取消注册消息订阅者
+ *
+ * @param subscriber 订阅者
+ */
+ default void unregister(IotMessageSubscriber> subscriber) {
+ // TODO 芋艿:暂时不实现,需求量不大,但是
+ // throw new UnsupportedOperationException("取消注册消息订阅者功能,尚未实现");
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java
index 23a055325c..fb5c712396 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java
@@ -26,4 +26,16 @@ public interface IotMessageSubscriber {
*/
void onMessage(T message);
+ /**
+ * 启动订阅
+ */
+ default void start() {
+ }
+
+ /**
+ * 停止订阅
+ */
+ default void stop() {
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
index feed3eb2a2..cc9b138744 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
@@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.iot.core.mq.message;
-import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.topic.state.IotDeviceStateUpdateReqDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -60,7 +60,7 @@ public class IotDeviceMessage {
*/
private String serverId;
- // ========== codec(编解码)字段 ==========
+ // ========== serialize(序列化)相关字段 ==========
/**
* 请求编号
@@ -72,7 +72,7 @@ public class IotDeviceMessage {
* 请求方法
*
* 枚举 {@link IotDeviceMessageMethodEnum}
- * 例如说:thing.property.report 属性上报
+ * 例如说:thing.property.post 属性上报
*/
private String method;
/**
@@ -94,7 +94,7 @@ public class IotDeviceMessage {
*/
private String msg;
- // ========== 基础方法:只传递"codec(编解码)字段" ==========
+ // ========== 基础方法:只传递"serialize(序列化)相关字段" ==========
public static IotDeviceMessage requestOf(String method) {
return requestOf(null, method, null);
@@ -149,20 +149,12 @@ public class IotDeviceMessage {
public static IotDeviceMessage buildStateUpdateOnline() {
return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(),
- MapUtil.of("state", IotDeviceStateEnum.ONLINE.getState()));
+ new IotDeviceStateUpdateReqDTO(IotDeviceStateEnum.ONLINE.getState()));
}
public static IotDeviceMessage buildStateOffline() {
return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(),
- MapUtil.of("state", IotDeviceStateEnum.OFFLINE.getState()));
- }
-
- public static IotDeviceMessage buildOtaUpgrade(String version, String fileUrl, Long fileSize,
- String fileDigestAlgorithm, String fileDigestValue) {
- return requestOf(IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), MapUtil.builder()
- .put("version", version).put("fileUrl", fileUrl).put("fileSize", fileSize)
- .put("fileDigestAlgorithm", fileDigestAlgorithm).put("fileDigestValue", fileDigestValue)
- .build());
+ new IotDeviceStateUpdateReqDTO(IotDeviceStateEnum.OFFLINE.getState()));
}
}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
index b8db15f188..ad938749d3 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
@@ -1,12 +1,15 @@
package cn.iocoder.yudao.module.iot.core.topic.auth;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* IoT 设备动态注册 Request DTO
*
- * 用于直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret
+ * 用于 {@link IotDeviceMessageMethodEnum#DEVICE_REGISTER} 消息的 params 参数
+ *
+ * 直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret
*
* @author 芋道源码
* @see 阿里云 - 一型一密
@@ -27,9 +30,11 @@ public class IotDeviceRegisterReqDTO {
private String deviceName;
/**
- * 产品密钥
+ * 注册签名
+ *
+ * @see cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils#buildSign(String, String, String)
*/
- @NotEmpty(message = "产品密钥不能为空")
- private String productSecret;
+ @NotEmpty(message = "签名不能为空")
+ private String sign;
}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterRespDTO.java
index 707f79890b..681aa72c5c 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterRespDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterRespDTO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.core.topic.auth;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -7,7 +8,7 @@ import lombok.NoArgsConstructor;
/**
* IoT 设备动态注册 Response DTO
*
- * 用于直连设备/网关的一型一密动态注册响应
+ * 用于 {@link IotDeviceMessageMethodEnum#DEVICE_REGISTER} 响应的设备信息
*
* @author 芋道源码
* @see 阿里云 - 一型一密
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java
index cf34a1db2b..e2372e0cb8 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java
@@ -1,13 +1,14 @@
package cn.iocoder.yudao.module.iot.core.topic.auth;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* IoT 子设备动态注册 Request DTO
*
- * 用于 thing.auth.register.sub 消息的 params 数组元素
- *
+ * 用于 {@link IotDeviceMessageMethodEnum#SUB_DEVICE_REGISTER} 消息的 params 数组元素
+ *
* 特殊:网关子设备的动态注册,必须已经创建好该网关子设备(不然哪来的 {@link #deviceName} 字段)。更多的好处,是设备不用提前烧录 deviceSecret 密钥。
*
* @author 芋道源码
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java
index a45f14defe..7da2f4e47b 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.core.topic.auth;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -7,7 +8,7 @@ import lombok.NoArgsConstructor;
/**
* IoT 子设备动态注册 Response DTO
*
- * 用于 thing.auth.register.sub 响应的设备信息
+ * 用于 {@link IotDeviceMessageMethodEnum#SUB_DEVICE_REGISTER} 响应的设备信息
*
* @author 芋道源码
* @see 阿里云 - 动态注册子设备
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/config/IotDeviceConfigPushReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/config/IotDeviceConfigPushReqDTO.java
new file mode 100644
index 0000000000..4828c9917a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/config/IotDeviceConfigPushReqDTO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.iot.core.topic.config;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 设备配置推送 Request DTO
+ *
+ * 用于 {@link IotDeviceMessageMethodEnum#CONFIG_PUSH} 下行消息的 params 参数
+ *
+ * @author 芋道源码
+ * @see 阿里云 - 远程配置
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceConfigPushReqDTO {
+
+ /**
+ * 配置编号
+ */
+ private String configId;
+
+ /**
+ * 配置文件大小(字节)
+ */
+ private Long configSize;
+
+ /**
+ * 签名方法
+ */
+ private String signMethod;
+
+ /**
+ * 签名
+ */
+ private String sign;
+
+ /**
+ * 配置文件下载地址
+ */
+ private String url;
+
+ /**
+ * 获取类型
+ *
+ * file: 文件
+ * content: 内容
+ */
+ private String getType;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/event/IotDeviceEventPostReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/event/IotDeviceEventPostReqDTO.java
index 3b6a7a7d4c..345419231c 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/event/IotDeviceEventPostReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/event/IotDeviceEventPostReqDTO.java
@@ -1,11 +1,12 @@
package cn.iocoder.yudao.module.iot.core.topic.event;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import lombok.Data;
/**
* IoT 设备事件上报 Request DTO
*
- * 用于 thing.event.post 消息的 params 参数
+ * 用于 {@link IotDeviceMessageMethodEnum#EVENT_POST} 消息的 params 参数
*
* @author 芋道源码
* @see 阿里云 - 设备上报事件
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaProgressReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaProgressReqDTO.java
new file mode 100644
index 0000000000..ef16e3e036
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaProgressReqDTO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.iot.core.topic.ota;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 设备 OTA 升级进度上报 Request DTO
+ *
+ * 用于 {@link IotDeviceMessageMethodEnum#OTA_PROGRESS} 上行消息的 params 参数
+ *
+ * @author 芋道源码
+ * @see 阿里云 - OTA 升级
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceOtaProgressReqDTO {
+
+ /**
+ * 固件版本号
+ */
+ private String version;
+
+ /**
+ * 升级状态
+ */
+ private Integer status;
+
+ /**
+ * 描述信息
+ */
+ private String description;
+
+ /**
+ * 升级进度(0-100)
+ */
+ private Integer progress;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaUpgradeReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaUpgradeReqDTO.java
new file mode 100644
index 0000000000..096ac699b8
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaUpgradeReqDTO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.iot.core.topic.ota;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 设备 OTA 固件升级推送 Request DTO
+ *
+ * 用于 {@link IotDeviceMessageMethodEnum#OTA_UPGRADE} 下行消息的 params 参数
+ *
+ * @author 芋道源码
+ * @see 阿里云 - OTA 升级
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceOtaUpgradeReqDTO {
+
+ /**
+ * 固件版本号
+ */
+ private String version;
+
+ /**
+ * 固件文件下载地址
+ */
+ private String fileUrl;
+
+ /**
+ * 固件文件大小(字节)
+ */
+ private Long fileSize;
+
+ /**
+ * 固件文件摘要算法
+ */
+ private String fileDigestAlgorithm;
+
+ /**
+ * 固件文件摘要值
+ */
+ private String fileDigestValue;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java
index 24494984eb..509e457752 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.core.topic.property;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import lombok.Data;
@@ -9,7 +10,7 @@ import java.util.Map;
/**
* IoT 设备属性批量上报 Request DTO
*
- * 用于 thing.event.property.pack.post 消息的 params 参数
+ * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_PACK_POST} 消息的 params 参数
*
* @author 芋道源码
* @see 阿里云 - 网关批量上报数据
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPostReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPostReqDTO.java
index 2e537442d7..98471d1d50 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPostReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPostReqDTO.java
@@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.iot.core.topic.property;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+
import java.util.HashMap;
import java.util.Map;
/**
* IoT 设备属性上报 Request DTO
*
- * 用于 thing.property.post 消息的 params 参数
+ * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_POST} 消息的 params 参数
*
* 本质是一个 Map,key 为属性标识符,value 为属性值
*
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertySetReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertySetReqDTO.java
new file mode 100644
index 0000000000..ba51f1bba1
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertySetReqDTO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.core.topic.property;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * IoT 设备属性设置 Request DTO
+ *
+ * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_SET} 下行消息的 params 参数
+ *
+ * 本质是一个 Map,key 为属性标识符,value 为属性值
+ *
+ * @author 芋道源码
+ */
+public class IotDevicePropertySetReqDTO extends HashMap {
+
+ public IotDevicePropertySetReqDTO() {
+ super();
+ }
+
+ public IotDevicePropertySetReqDTO(Map properties) {
+ super(properties);
+ }
+
+ /**
+ * 创建属性设置 DTO
+ *
+ * @param properties 属性数据
+ * @return DTO 对象
+ */
+ public static IotDevicePropertySetReqDTO of(Map properties) {
+ return new IotDevicePropertySetReqDTO(properties);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/service/IotDeviceServiceInvokeReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/service/IotDeviceServiceInvokeReqDTO.java
new file mode 100644
index 0000000000..dafadd24a2
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/service/IotDeviceServiceInvokeReqDTO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.iot.core.topic.service;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * IoT 设备服务调用 Request DTO
+ *
+ * 用于 {@link IotDeviceMessageMethodEnum#SERVICE_INVOKE} 下行消息的 params 参数
+ *
+ * @author 芋道源码
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceServiceInvokeReqDTO {
+
+ /**
+ * 服务标识符
+ */
+ private String identifier;
+
+ /**
+ * 服务输入参数
+ */
+ private Map inputParams;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/state/IotDeviceStateUpdateReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/state/IotDeviceStateUpdateReqDTO.java
new file mode 100644
index 0000000000..fce44e03b5
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/state/IotDeviceStateUpdateReqDTO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.core.topic.state;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 设备状态更新 Request DTO
+ *
+ * 用于 {@link IotDeviceMessageMethodEnum#STATE_UPDATE} 消息的 params 参数
+ *
+ * @author 芋道源码
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceStateUpdateReqDTO {
+
+ /**
+ * 设备状态
+ */
+ private Integer state;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java
index 97ec33200a..b9444ed6d6 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.iot.core.topic.topo;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
@@ -9,7 +10,7 @@ import java.util.List;
/**
* IoT 设备拓扑添加 Request DTO
*
- * 用于 thing.topo.add 消息的 params 参数
+ * 用于 {@link IotDeviceMessageMethodEnum#TOPO_ADD} 消息的 params 参数
*
* @author 芋道源码
* @see 阿里云 - 添加拓扑关系
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoChangeReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoChangeReqDTO.java
index 0198206fe3..615e509ae6 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoChangeReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoChangeReqDTO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.core.topic.topo;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -10,7 +11,7 @@ import java.util.List;
/**
* IoT 设备拓扑关系变更通知 Request DTO
*
- * 用于 thing.topo.change 下行消息的 params 参数
+ * 用于 {@link IotDeviceMessageMethodEnum#TOPO_CHANGE} 下行消息的 params 参数
*
* @author 芋道源码
* @see 阿里云 - 通知网关拓扑关系变化
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java
index 71ee2bb8b2..6db2b5db8d 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.core.topic.topo;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
@@ -10,7 +11,7 @@ import java.util.List;
/**
* IoT 设备拓扑删除 Request DTO
*
- * 用于 thing.topo.delete 消息的 params 参数
+ * 用于 {@link IotDeviceMessageMethodEnum#TOPO_DELETE} 消息的 params 参数
*
* @author 芋道源码
* @see 阿里云 - 删除拓扑关系
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetReqDTO.java
index 7a61af0a58..1da86c9505 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetReqDTO.java
@@ -1,11 +1,12 @@
package cn.iocoder.yudao.module.iot.core.topic.topo;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import lombok.Data;
/**
* IoT 设备拓扑关系获取 Request DTO
*
- * 用于 thing.topo.get 请求的 params 参数(目前为空,预留扩展)
+ * 用于 {@link IotDeviceMessageMethodEnum#TOPO_GET} 请求的 params 参数(目前为空,预留扩展)
*
* @author 芋道源码
* @see 阿里云 - 获取拓扑关系
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetRespDTO.java
index 69c9b1555e..0aef9c8680 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetRespDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetRespDTO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.core.topic.topo;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import lombok.Data;
@@ -8,7 +9,7 @@ import java.util.List;
/**
* IoT 设备拓扑关系获取 Response DTO
*
- * 用于 thing.topo.get 响应
+ * 用于 {@link IotDeviceMessageMethodEnum#TOPO_GET} 响应
*
* @author 芋道源码
* @see 阿里云 - 获取拓扑关系
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java
index 609d0a60ae..1aa9cfcabf 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java
+++ b/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 {
return String.format("%s.%s", productKey, deviceName);
}
+ public static String buildClientIdFromUsername(String username) {
+ IotDeviceIdentity identity = parseUsername(username);
+ if (identity == null) {
+ return null;
+ }
+ return buildClientId(identity.getProductKey(), identity.getDeviceName());
+ }
+
public static String buildUsername(String productKey, String deviceName) {
return String.format("%s&%s", deviceName, productKey);
}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java
new file mode 100644
index 0000000000..12d1229d10
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.iot.core.util;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import cn.hutool.crypto.digest.HmacAlgorithm;
+
+/**
+ * IoT 产品【动态注册】认证工具类
+ *
+ * 用于一型一密场景,使用 productSecret 生成签名
+ *
+ * @author 芋道源码
+ */
+public class IotProductAuthUtils {
+
+ /**
+ * 生成设备动态注册签名
+ *
+ * @param productKey 产品标识
+ * @param deviceName 设备名称
+ * @param productSecret 产品密钥
+ * @return 签名
+ */
+ public static String buildSign(String productKey, String deviceName, String productSecret) {
+ String content = buildContent(productKey, deviceName);
+ return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(productSecret))
+ .digestHex(content);
+ }
+
+ /**
+ * 验证设备动态注册签名
+ *
+ * @param productKey 产品标识
+ * @param deviceName 设备名称
+ * @param productSecret 产品密钥
+ * @param sign 待验证的签名
+ * @return 是否验证通过
+ */
+ public static boolean verifySign(String productKey, String deviceName, String productSecret, String sign) {
+ String expectedSign = buildSign(productKey, deviceName, productSecret);
+ return expectedSign.equals(sign);
+ }
+
+ /**
+ * 构建签名内容
+ *
+ * @param productKey 产品标识
+ * @param deviceName 设备名称
+ * @return 签名内容
+ */
+ private static String buildContent(String productKey, String deviceName) {
+ return "deviceName" + deviceName + "productKey" + productKey;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/pom.xml b/yudao-module-iot/yudao-module-iot-gateway/pom.xml
index 38ace822d8..0731198fd7 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-gateway/pom.xml
@@ -33,7 +33,7 @@
org.apache.rocketmq
rocketmq-spring-boot-starter
-
+ true
@@ -48,6 +48,13 @@
vertx-mqtt
+
+
+ com.ghgande
+ j2mod
+ 3.2.1
+
+
org.eclipse.californium
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java
deleted file mode 100644
index 94dd309dd1..0000000000
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package cn.iocoder.yudao.module.iot.gateway.codec;
-
-import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
-
-/**
- * {@link cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage} 的编解码器
- *
- * @author 芋道源码
- */
-public interface IotDeviceMessageCodec {
-
- /**
- * 编码消息
- *
- * @param message 消息
- * @return 编码后的消息内容
- */
- byte[] encode(IotDeviceMessage message);
-
- /**
- * 解码消息
- *
- * @param bytes 消息内容
- * @return 解码后的消息内容
- */
- IotDeviceMessage decode(byte[] bytes);
-
- /**
- * @return 数据格式(编码器类型)
- */
- String type();
-
-}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java
deleted file mode 100644
index 5a4e47fe18..0000000000
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package cn.iocoder.yudao.module.iot.gateway.codec.alink;
-
-import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
-import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.springframework.stereotype.Component;
-
-/**
- * 阿里云 Alink {@link IotDeviceMessage} 的编解码器
- *
- * @author 芋道源码
- */
-@Component
-public class IotAlinkDeviceMessageCodec implements IotDeviceMessageCodec {
-
- public static final String TYPE = "Alink";
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- private static class AlinkMessage {
-
- public static final String VERSION_1 = "1.0";
-
- /**
- * 消息 ID,且每个消息 ID 在当前设备具有唯一性
- */
- private String id;
-
- /**
- * 版本号
- */
- private String version;
-
- /**
- * 请求方法
- */
- private String method;
-
- /**
- * 请求参数
- */
- private Object params;
-
- /**
- * 响应结果
- */
- private Object data;
- /**
- * 响应错误码
- */
- private Integer code;
- /**
- * 响应提示
- *
- * 特殊:这里阿里云是 message,为了保持和项目的 {@link CommonResult#getMsg()} 一致。
- */
- private String msg;
-
- }
-
- @Override
- public String type() {
- return TYPE;
- }
-
- @Override
- public byte[] encode(IotDeviceMessage message) {
- AlinkMessage alinkMessage = new AlinkMessage(message.getRequestId(), AlinkMessage.VERSION_1,
- message.getMethod(), message.getParams(), message.getData(), message.getCode(), message.getMsg());
- return JsonUtils.toJsonByte(alinkMessage);
- }
-
- @Override
- @SuppressWarnings("DataFlowIssue")
- public IotDeviceMessage decode(byte[] bytes) {
- AlinkMessage alinkMessage = JsonUtils.parseObject(bytes, AlinkMessage.class);
- Assert.notNull(alinkMessage, "消息不能为空");
- Assert.equals(alinkMessage.getVersion(), AlinkMessage.VERSION_1, "消息版本号必须是 1.0");
- return IotDeviceMessage.of(alinkMessage.getId(), alinkMessage.getMethod(), alinkMessage.getParams(),
- alinkMessage.getData(), alinkMessage.getCode(), alinkMessage.getMsg());
- }
-
-}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java
deleted file mode 100644
index e1dae7707a..0000000000
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 提供设备接入的各种数据(请求、响应)的编解码
- */
-package cn.iocoder.yudao.module.iot.gateway.codec;
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/simple/package-info.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/simple/package-info.java
deleted file mode 100644
index 5bd676ad1a..0000000000
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/simple/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * TODO @芋艿:实现一个 alink 的 xml 版本
- */
-package cn.iocoder.yudao.module.iot.gateway.codec.simple;
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java
deleted file mode 100644
index 7d62ce2e0f..0000000000
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package cn.iocoder.yudao.module.iot.gateway.codec.tcp;
-
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
-import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.springframework.stereotype.Component;
-
-/**
- * TCP/UDP JSON 格式 {@link IotDeviceMessage} 编解码器
- *
- * 采用纯 JSON 格式传输,格式如下:
- * {
- * "id": "消息 ID",
- * "method": "消息方法",
- * "params": {...}, // 请求参数
- * "data": {...}, // 响应结果
- * "code": 200, // 响应错误码
- * "msg": "success", // 响应提示
- * "timestamp": 时间戳
- * }
- *
- * @author 芋道源码
- */
-@Component
-public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec {
-
- public static final String TYPE = "TCP_JSON";
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- private static class TcpJsonMessage {
-
- /**
- * 消息 ID,且每个消息 ID 在当前设备具有唯一性
- */
- private String id;
-
- /**
- * 请求方法
- */
- private String method;
-
- /**
- * 请求参数
- */
- private Object params;
-
- /**
- * 响应结果
- */
- private Object data;
-
- /**
- * 响应错误码
- */
- private Integer code;
-
- /**
- * 响应提示
- */
- private String msg;
-
- /**
- * 时间戳
- */
- private Long timestamp;
-
- }
-
- @Override
- public String type() {
- return TYPE;
- }
-
- @Override
- public byte[] encode(IotDeviceMessage message) {
- TcpJsonMessage tcpJsonMessage = new TcpJsonMessage(
- message.getRequestId(),
- message.getMethod(),
- message.getParams(),
- message.getData(),
- message.getCode(),
- message.getMsg(),
- System.currentTimeMillis());
- return JsonUtils.toJsonByte(tcpJsonMessage);
- }
-
- @Override
- @SuppressWarnings("DataFlowIssue")
- public IotDeviceMessage decode(byte[] bytes) {
- String jsonStr = StrUtil.utf8Str(bytes).trim();
- TcpJsonMessage tcpJsonMessage = JsonUtils.parseObject(jsonStr, TcpJsonMessage.class);
- Assert.notNull(tcpJsonMessage, "消息不能为空");
- Assert.notBlank(tcpJsonMessage.getMethod(), "消息方法不能为空");
- return IotDeviceMessage.of(
- tcpJsonMessage.getId(),
- tcpJsonMessage.getMethod(),
- tcpJsonMessage.getParams(),
- tcpJsonMessage.getData(),
- tcpJsonMessage.getCode(),
- tcpJsonMessage.getMsg());
- }
-
-}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
index a4e93a84fd..5c2fd860e9 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
@@ -1,254 +1,28 @@
package cn.iocoder.yudao.module.iot.gateway.config;
-import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapDownstreamSubscriber;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
-import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;
-import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber;
-import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
-import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;
-import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber;
-import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
-import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpDownstreamSubscriber;
-import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
-import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketDownstreamSubscriber;
-import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;
-import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
-import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
-import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
-import io.vertx.core.Vertx;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager;
+import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+/**
+ * IoT 网关配置类
+ *
+ * @author 芋道源码
+ */
@Configuration
@EnableConfigurationProperties(IotGatewayProperties.class)
-@Slf4j
public class IotGatewayConfiguration {
- /**
- * IoT 网关 HTTP 协议配置类
- */
- @Configuration
- @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.http", name = "enabled", havingValue = "true")
- @Slf4j
- public static class HttpProtocolConfiguration {
-
- @Bean(name = "httpVertx", destroyMethod = "close")
- public Vertx httpVertx() {
- return Vertx.vertx();
- }
-
- @Bean
- public IotHttpUpstreamProtocol iotHttpUpstreamProtocol(IotGatewayProperties gatewayProperties,
- @Qualifier("httpVertx") Vertx httpVertx) {
- return new IotHttpUpstreamProtocol(gatewayProperties.getProtocol().getHttp(), httpVertx);
- }
-
- @Bean
- public IotHttpDownstreamSubscriber iotHttpDownstreamSubscriber(IotHttpUpstreamProtocol httpUpstreamProtocol,
- IotMessageBus messageBus) {
- return new IotHttpDownstreamSubscriber(httpUpstreamProtocol, messageBus);
- }
+ @Bean
+ public IotMessageSerializerManager iotMessageSerializerManager() {
+ return new IotMessageSerializerManager();
}
- /**
- * IoT 网关 EMQX 协议配置类
- */
- @Configuration
- @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.emqx", name = "enabled", havingValue = "true")
- @Slf4j
- public static class EmqxProtocolConfiguration {
-
- @Bean(name = "emqxVertx", destroyMethod = "close")
- public Vertx emqxVertx() {
- return Vertx.vertx();
- }
-
- @Bean
- public IotEmqxAuthEventProtocol iotEmqxAuthEventProtocol(IotGatewayProperties gatewayProperties,
- @Qualifier("emqxVertx") Vertx emqxVertx) {
- return new IotEmqxAuthEventProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx);
- }
-
- @Bean
- public IotEmqxUpstreamProtocol iotEmqxUpstreamProtocol(IotGatewayProperties gatewayProperties,
- @Qualifier("emqxVertx") Vertx emqxVertx) {
- return new IotEmqxUpstreamProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx);
- }
-
- @Bean
- public IotEmqxDownstreamSubscriber iotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol mqttUpstreamProtocol,
- IotMessageBus messageBus) {
- return new IotEmqxDownstreamSubscriber(mqttUpstreamProtocol, messageBus);
- }
- }
-
- /**
- * IoT 网关 TCP 协议配置类
- */
- @Configuration
- @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.tcp", name = "enabled", havingValue = "true")
- @Slf4j
- public static class TcpProtocolConfiguration {
-
- @Bean(name = "tcpVertx", destroyMethod = "close")
- public Vertx tcpVertx() {
- return Vertx.vertx();
- }
-
- @Bean
- public IotTcpUpstreamProtocol iotTcpUpstreamProtocol(IotGatewayProperties gatewayProperties,
- IotDeviceService deviceService,
- IotDeviceMessageService messageService,
- IotTcpConnectionManager connectionManager,
- @Qualifier("tcpVertx") Vertx tcpVertx) {
- return new IotTcpUpstreamProtocol(gatewayProperties.getProtocol().getTcp(),
- deviceService, messageService, connectionManager, tcpVertx);
- }
-
- @Bean
- public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler,
- IotDeviceMessageService messageService,
- IotTcpConnectionManager connectionManager,
- IotMessageBus messageBus) {
- return new IotTcpDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus);
- }
-
- }
-
- /**
- * IoT 网关 MQTT 协议配置类
- */
- @Configuration
- @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.mqtt", name = "enabled", havingValue = "true")
- @Slf4j
- public static class MqttProtocolConfiguration {
-
- @Bean(name = "mqttVertx", destroyMethod = "close")
- public Vertx mqttVertx() {
- return Vertx.vertx();
- }
-
- @Bean
- public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties,
- IotDeviceMessageService messageService,
- IotMqttConnectionManager connectionManager,
- @Qualifier("mqttVertx") Vertx mqttVertx) {
- return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getMqtt(), messageService,
- connectionManager, mqttVertx);
- }
-
- @Bean
- public IotMqttDownstreamHandler iotMqttDownstreamHandler(IotDeviceMessageService messageService,
- IotMqttConnectionManager connectionManager) {
- return new IotMqttDownstreamHandler(messageService, connectionManager);
- }
-
- @Bean
- public IotMqttDownstreamSubscriber iotMqttDownstreamSubscriber(IotMqttUpstreamProtocol mqttUpstreamProtocol,
- IotMqttDownstreamHandler downstreamHandler,
- IotMessageBus messageBus) {
- return new IotMqttDownstreamSubscriber(mqttUpstreamProtocol, downstreamHandler, messageBus);
- }
-
- }
-
- /**
- * IoT 网关 UDP 协议配置类
- */
- @Configuration
- @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.udp", name = "enabled", havingValue = "true")
- @Slf4j
- public static class UdpProtocolConfiguration {
-
- @Bean(name = "udpVertx", destroyMethod = "close")
- public Vertx udpVertx() {
- return Vertx.vertx();
- }
-
- @Bean
- public IotUdpUpstreamProtocol iotUdpUpstreamProtocol(IotGatewayProperties gatewayProperties,
- IotDeviceService deviceService,
- IotDeviceMessageService messageService,
- IotUdpSessionManager sessionManager,
- @Qualifier("udpVertx") Vertx udpVertx) {
- return new IotUdpUpstreamProtocol(gatewayProperties.getProtocol().getUdp(),
- deviceService, messageService, sessionManager, udpVertx);
- }
-
- @Bean
- public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler,
- IotDeviceMessageService messageService,
- IotUdpSessionManager sessionManager,
- IotMessageBus messageBus) {
- return new IotUdpDownstreamSubscriber(protocolHandler, messageService, sessionManager, messageBus);
- }
-
- }
-
- /**
- * IoT 网关 CoAP 协议配置类
- */
- @Configuration
- @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.coap", name = "enabled", havingValue = "true")
- @Slf4j
- public static class CoapProtocolConfiguration {
-
- @Bean
- public IotCoapUpstreamProtocol iotCoapUpstreamProtocol(IotGatewayProperties gatewayProperties) {
- return new IotCoapUpstreamProtocol(gatewayProperties.getProtocol().getCoap());
- }
-
- @Bean
- public IotCoapDownstreamSubscriber iotCoapDownstreamSubscriber(IotCoapUpstreamProtocol coapUpstreamProtocol,
- IotMessageBus messageBus) {
- return new IotCoapDownstreamSubscriber(coapUpstreamProtocol, messageBus);
- }
-
- }
-
- /**
- * IoT 网关 WebSocket 协议配置类
- */
- @Configuration
- @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.websocket", name = "enabled", havingValue = "true")
- @Slf4j
- public static class WebSocketProtocolConfiguration {
-
- @Bean(name = "websocketVertx", destroyMethod = "close")
- public Vertx websocketVertx() {
- return Vertx.vertx();
- }
-
- @Bean
- public IotWebSocketUpstreamProtocol iotWebSocketUpstreamProtocol(IotGatewayProperties gatewayProperties,
- IotDeviceService deviceService,
- IotDeviceMessageService messageService,
- IotWebSocketConnectionManager connectionManager,
- @Qualifier("websocketVertx") Vertx websocketVertx) {
- return new IotWebSocketUpstreamProtocol(gatewayProperties.getProtocol().getWebsocket(),
- deviceService, messageService, connectionManager, websocketVertx);
- }
-
- @Bean
- public IotWebSocketDownstreamSubscriber iotWebSocketDownstreamSubscriber(IotWebSocketUpstreamProtocol protocolHandler,
- IotDeviceMessageService messageService,
- IotWebSocketConnectionManager connectionManager,
- IotMessageBus messageBus) {
- return new IotWebSocketDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus);
- }
-
+ @Bean
+ public IotProtocolManager iotProtocolManager(IotGatewayProperties gatewayProperties) {
+ return new IotProtocolManager(gatewayProperties);
}
}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
index 9a86ee600d..63894dc9df 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -1,5 +1,16 @@
package cn.iocoder.yudao.module.iot.gateway.config;
+import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.IotModbusTcpClientConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.IotModbusTcpServerConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpConfig;
+import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketConfig;
+import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -24,9 +35,9 @@ public class IotGatewayProperties {
private TokenProperties token;
/**
- * 协议配置
+ * 协议实例列表
*/
- private ProtocolProperties protocol;
+ private List protocols;
@Data
public static class RpcProperties {
@@ -65,582 +76,158 @@ public class IotGatewayProperties {
}
+ /**
+ * 协议实例配置
+ */
@Data
public static class ProtocolProperties {
/**
- * HTTP 组件配置
+ * 协议实例 ID,如 "http-alink"、"tcp-binary"
*/
- private HttpProperties http;
-
+ @NotEmpty(message = "协议实例 ID 不能为空")
+ private String id;
/**
- * EMQX 组件配置
+ * 是否启用
*/
- private EmqxProperties emqx;
-
+ @NotNull(message = "是否启用不能为空")
+ private Boolean enabled = true;
/**
- * TCP 组件配置
+ * 协议类型
+ *
+ * @see cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum
*/
- private TcpProperties tcp;
-
- /**
- * MQTT 组件配置
- */
- private MqttProperties mqtt;
-
- /**
- * MQTT WebSocket 组件配置
- */
- private MqttWsProperties mqttWs;
-
- /**
- * UDP 组件配置
- */
- private UdpProperties udp;
-
- /**
- * CoAP 组件配置
- */
- private CoapProperties coap;
-
- /**
- * WebSocket 组件配置
- */
- private WebSocketProperties websocket;
-
- }
-
- @Data
- public static class HttpProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
+ @NotEmpty(message = "协议类型不能为空")
+ private String protocol;
/**
* 服务端口
- */
- private Integer serverPort;
-
- /**
- * 是否开启 SSL
- */
- @NotNull(message = "是否开启 SSL 不能为空")
- private Boolean sslEnabled = false;
-
- /**
- * SSL 证书路径
- */
- private String sslKeyPath;
- /**
- * SSL 证书路径
- */
- private String sslCertPath;
-
- }
-
- @Data
- public static class EmqxProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
-
- /**
- * HTTP 服务端口(默认:8090)
- */
- private Integer httpPort = 8090;
-
- /**
- * MQTT 服务器地址
- */
- @NotEmpty(message = "MQTT 服务器地址不能为空")
- private String mqttHost;
-
- /**
- * MQTT 服务器端口(默认:1883)
- */
- @NotNull(message = "MQTT 服务器端口不能为空")
- private Integer mqttPort = 1883;
-
- /**
- * MQTT 用户名
- */
- @NotEmpty(message = "MQTT 用户名不能为空")
- private String mqttUsername;
-
- /**
- * MQTT 密码
- */
- @NotEmpty(message = "MQTT 密码不能为空")
- private String mqttPassword;
-
- /**
- * MQTT 客户端的 SSL 开关
- */
- @NotNull(message = "MQTT 是否开启 SSL 不能为空")
- private Boolean mqttSsl = false;
-
- /**
- * MQTT 客户端 ID(如果为空,系统将自动生成)
- */
- @NotEmpty(message = "MQTT 客户端 ID 不能为空")
- private String mqttClientId;
-
- /**
- * MQTT 订阅的主题
- */
- @NotEmpty(message = "MQTT 主题不能为空")
- private List<@NotEmpty(message = "MQTT 主题不能为空") String> mqttTopics;
-
- /**
- * 默认 QoS 级别
*
- * 0 - 最多一次
- * 1 - 至少一次
- * 2 - 刚好一次
+ * 不同协议含义不同:
+ * 1. TCP/UDP/HTTP/WebSocket/MQTT/CoAP:对应网关自身监听的服务端口
+ * 2. EMQX:对应网关提供给 EMQX 回调的 HTTP Hook 端口(/mqtt/auth、/mqtt/acl、/mqtt/event)
*/
- private Integer mqttQos = 1;
+ @NotNull(message = "服务端口不能为空")
+ private Integer port;
+ /**
+ * 序列化类型(可选)
+ *
+ * @see cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum
+ *
+ * 为什么是可选的呢?
+ * 1. {@link IotProtocolTypeEnum#HTTP}、{@link IotProtocolTypeEnum#COAP} 协议,目前强制是 JSON 格式
+ * 2. {@link IotProtocolTypeEnum#EMQX} 协议,目前支持根据产品(设备)配置的序列化类型来解析
+ */
+ private String serialize;
+
+ // ========== SSL 配置 ==========
/**
- * 连接超时时间(秒)
+ * SSL 配置(可选,配置文件中不配置则为 null)
*/
- private Integer connectTimeoutSeconds = 10;
+ @Valid
+ private SslConfig ssl;
+
+ // ========== 各协议配置 ==========
/**
- * 重连延迟时间(毫秒)
+ * HTTP 协议配置
*/
- private Long reconnectDelayMs = 5000L;
+ @Valid
+ private IotHttpConfig http;
+ /**
+ * WebSocket 协议配置
+ */
+ @Valid
+ private IotWebSocketConfig websocket;
/**
- * 是否启用 Clean Session (清理会话)
- * true: 每次连接都是新会话,Broker 不保留离线消息和订阅关系。
- * 对于网关这类“永远在线”且会主动重新订阅的应用,建议为 true。
+ * TCP 协议配置
*/
- private Boolean cleanSession = true;
+ @Valid
+ private IotTcpConfig tcp;
+ /**
+ * UDP 协议配置
+ */
+ @Valid
+ private IotUdpConfig udp;
/**
- * 心跳间隔(秒)
- * 用于保持连接活性,及时发现网络中断。
+ * CoAP 协议配置
*/
- private Integer keepAliveIntervalSeconds = 60;
+ @Valid
+ private IotCoapConfig coap;
/**
- * 最大未确认消息队列大小
- * 限制已发送但未收到 Broker 确认的 QoS 1/2 消息数量,用于流量控制。
+ * MQTT 协议配置
*/
- private Integer maxInflightQueue = 10000;
+ @Valid
+ private IotMqttConfig mqtt;
+ /**
+ * EMQX 协议配置
+ */
+ @Valid
+ private IotEmqxConfig emqx;
/**
- * 是否信任所有 SSL 证书
- * 警告:此配置会绕过证书验证,仅建议在开发和测试环境中使用!
- * 在生产环境中,应设置为 false,并配置正确的信任库。
+ * Modbus TCP Client 协议配置
*/
- private Boolean trustAll = false;
+ @Valid
+ private IotModbusTcpClientConfig modbusTcpClient;
/**
- * 遗嘱消息配置 (用于网关异常下线时通知其他系统)
+ * Modbus TCP Server 协议配置
*/
- private final Will will = new Will();
-
- /**
- * 高级 SSL/TLS 配置 (用于生产环境)
- */
- private final Ssl sslOptions = new Ssl();
-
- /**
- * 遗嘱消息 (Last Will and Testament)
- */
- @Data
- public static class Will {
-
- /**
- * 是否启用遗嘱消息
- */
- private boolean enabled = false;
- /**
- * 遗嘱消息主题
- */
- private String topic;
- /**
- * 遗嘱消息内容
- */
- private String payload;
- /**
- * 遗嘱消息 QoS 等级
- */
- private Integer qos = 1;
- /**
- * 遗嘱消息是否作为保留消息发布
- */
- private boolean retain = true;
-
- }
-
- /**
- * 高级 SSL/TLS 配置
- */
- @Data
- public static class Ssl {
-
- /**
- * 密钥库(KeyStore)路径,例如:classpath:certs/client.jks
- * 包含客户端自己的证书和私钥,用于向服务端证明身份(双向认证)。
- */
- private String keyStorePath;
- /**
- * 密钥库密码
- */
- private String keyStorePassword;
- /**
- * 信任库(TrustStore)路径,例如:classpath:certs/trust.jks
- * 包含服务端信任的 CA 证书,用于验证服务端的身份,防止中间人攻击。
- */
- private String trustStorePath;
- /**
- * 信任库密码
- */
- private String trustStorePassword;
-
- }
+ @Valid
+ private IotModbusTcpServerConfig modbusTcpServer;
}
+ /**
+ * SSL 配置
+ */
@Data
- public static class TcpProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
-
- /**
- * 服务器端口
- */
- private Integer port = 8091;
-
- /**
- * 心跳超时时间(毫秒)
- */
- private Long keepAliveTimeoutMs = 30000L;
-
- /**
- * 最大连接数
- */
- private Integer maxConnections = 1000;
-
- /**
- * 是否启用SSL
- */
- private Boolean sslEnabled = false;
-
- /**
- * SSL证书路径
- */
- private String sslCertPath;
-
- /**
- * SSL私钥路径
- */
- private String sslKeyPath;
-
- }
-
- @Data
- public static class MqttProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
-
- /**
- * 服务器端口
- */
- private Integer port = 1883;
-
- /**
- * 最大消息大小(字节)
- */
- private Integer maxMessageSize = 8192;
-
- /**
- * 连接超时时间(秒)
- */
- private Integer connectTimeoutSeconds = 60;
- /**
- * 保持连接超时时间(秒)
- */
- private Integer keepAliveTimeoutSeconds = 300;
+ public static class SslConfig {
/**
* 是否启用 SSL
*/
- private Boolean sslEnabled = false;
- /**
- * SSL 配置
- */
- private SslOptions sslOptions = new SslOptions();
-
- /**
- * SSL 配置选项
- */
- @Data
- public static class SslOptions {
-
- /**
- * 密钥证书选项
- */
- private io.vertx.core.net.KeyCertOptions keyCertOptions;
- /**
- * 信任选项
- */
- private io.vertx.core.net.TrustOptions trustOptions;
- /**
- * SSL 证书路径
- */
- private String certPath;
- /**
- * SSL 私钥路径
- */
- private String keyPath;
- /**
- * 信任存储路径
- */
- private String trustStorePath;
- /**
- * 信任存储密码
- */
- private String trustStorePassword;
-
- }
-
- }
-
- @Data
- public static class MqttWsProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
-
- /**
- * WebSocket 服务器端口(默认:8083)
- */
- private Integer port = 8083;
-
- /**
- * WebSocket 路径(默认:/mqtt)
- */
- @NotEmpty(message = "WebSocket 路径不能为空")
- private String path = "/mqtt";
-
- /**
- * 最大消息大小(字节)
- */
- private Integer maxMessageSize = 8192;
-
- /**
- * 连接超时时间(秒)
- */
- private Integer connectTimeoutSeconds = 60;
-
- /**
- * 保持连接超时时间(秒)
- */
- private Integer keepAliveTimeoutSeconds = 300;
-
- /**
- * 是否启用 SSL(wss://)
- */
- private Boolean sslEnabled = false;
-
- /**
- * SSL 配置
- */
- private SslOptions sslOptions = new SslOptions();
-
- /**
- * WebSocket 子协议(通常为 "mqtt" 或 "mqttv3.1")
- */
- @NotEmpty(message = "WebSocket 子协议不能为空")
- private String subProtocol = "mqtt";
-
- /**
- * 最大帧大小(字节)
- */
- private Integer maxFrameSize = 65536;
-
- /**
- * SSL 配置选项
- */
- @Data
- public static class SslOptions {
-
- /**
- * 密钥证书选项
- */
- private io.vertx.core.net.KeyCertOptions keyCertOptions;
-
- /**
- * 信任选项
- */
- private io.vertx.core.net.TrustOptions trustOptions;
-
- /**
- * SSL 证书路径
- */
- private String certPath;
-
- /**
- * SSL 私钥路径
- */
- private String keyPath;
-
- /**
- * 信任存储路径
- */
- private String trustStorePath;
-
- /**
- * 信任存储密码
- */
- private String trustStorePassword;
-
- }
-
- }
-
- @Data
- public static class UdpProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
-
- /**
- * 服务端口(默认 8093)
- */
- private Integer port = 8093;
-
- /**
- * 接收缓冲区大小(默认 64KB)
- */
- private Integer receiveBufferSize = 65536;
-
- /**
- * 发送缓冲区大小(默认 64KB)
- */
- private Integer sendBufferSize = 65536;
-
- /**
- * 会话超时时间(毫秒,默认 60 秒)
- *
- * 用于清理不活跃的设备地址映射
- */
- private Long sessionTimeoutMs = 60000L;
-
- /**
- * 会话清理间隔(毫秒,默认 30 秒)
- */
- private Long sessionCleanIntervalMs = 30000L;
-
- }
-
- @Data
- public static class CoapProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
-
- /**
- * 服务端口(CoAP 默认端口 5683)
- */
- @NotNull(message = "服务端口不能为空")
- private Integer port = 5683;
-
- /**
- * 最大消息大小(字节)
- */
- @NotNull(message = "最大消息大小不能为空")
- private Integer maxMessageSize = 1024;
-
- /**
- * ACK 超时时间(毫秒)
- */
- @NotNull(message = "ACK 超时时间不能为空")
- private Integer ackTimeout = 2000;
-
- /**
- * 最大重传次数
- */
- @NotNull(message = "最大重传次数不能为空")
- private Integer maxRetransmit = 4;
-
- }
-
- @Data
- public static class WebSocketProperties {
-
- /**
- * 是否开启
- */
- @NotNull(message = "是否开启不能为空")
- private Boolean enabled;
-
- /**
- * 服务器端口(默认:8094)
- */
- private Integer port = 8094;
-
- /**
- * WebSocket 路径(默认:/ws)
- */
- @NotEmpty(message = "WebSocket 路径不能为空")
- private String path = "/ws";
-
- /**
- * 最大消息大小(字节,默认 64KB)
- */
- private Integer maxMessageSize = 65536;
-
- /**
- * 最大帧大小(字节,默认 64KB)
- */
- private Integer maxFrameSize = 65536;
-
- /**
- * 空闲超时时间(秒,默认 60)
- */
- private Integer idleTimeoutSeconds = 60;
-
- /**
- * 是否启用 SSL(wss://)
- */
- private Boolean sslEnabled = false;
+ @NotNull(message = "是否启用 SSL 不能为空")
+ private Boolean ssl = false;
/**
* SSL 证书路径
*/
+ @NotEmpty(message = "SSL 证书路径不能为空")
private String sslCertPath;
/**
* SSL 私钥路径
*/
+ @NotEmpty(message = "SSL 私钥路径不能为空")
private String sslKeyPath;
+ /**
+ * 密钥库(KeyStore)路径
+ *
+ * 包含客户端自己的证书和私钥,用于向服务端证明身份(双向认证)
+ */
+ private String keyStorePath;
+ /**
+ * 密钥库密码
+ */
+ private String keyStorePassword;
+
+ /**
+ * 信任库(TrustStore)路径
+ *
+ * 包含服务端信任的 CA 证书,用于验证服务端的身份
+ */
+ private String trustStorePath;
+ /**
+ * 信任库密码
+ */
+ private String trustStorePassword;
+
}
}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/AbstractIotProtocolDownstreamSubscriber.java
similarity index 57%
rename from yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java
rename to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/AbstractIotProtocolDownstreamSubscriber.java
index 61bf12376b..efd61e13a2 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/AbstractIotProtocolDownstreamSubscriber.java
@@ -1,49 +1,53 @@
-package cn.iocoder.yudao.module.iot.gateway.protocol.emqx;
+package cn.iocoder.yudao.module.iot.gateway.protocol;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
-import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router.IotEmqxDownstreamHandler;
-import jakarta.annotation.PostConstruct;
+import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
- * IoT 网关 EMQX 订阅者:接收下行给设备的消息
+ * IoT 协议下行消息订阅者抽象类
+ *
+ * 负责接收来自消息总线的下行消息,并委托给子类进行业务处理
*
* @author 芋道源码
*/
+@AllArgsConstructor
@Slf4j
-public class IotEmqxDownstreamSubscriber implements IotMessageSubscriber {
+public abstract class AbstractIotProtocolDownstreamSubscriber implements IotMessageSubscriber {
- private final IotEmqxDownstreamHandler downstreamHandler;
+ private final IotProtocol protocol;
private final IotMessageBus messageBus;
- private final IotEmqxUpstreamProtocol protocol;
-
- public IotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol protocol, IotMessageBus messageBus) {
- this.protocol = protocol;
- this.messageBus = messageBus;
- this.downstreamHandler = new IotEmqxDownstreamHandler(protocol);
- }
-
- @PostConstruct
- public void init() {
- messageBus.register(this);
- }
-
@Override
public String getTopic() {
return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
}
+ /**
+ * 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
+ */
@Override
public String getGroup() {
- // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
return getTopic();
}
+ @Override
+ public void start() {
+ messageBus.register(this);
+ log.info("[start][{} 下行消息订阅成功,Topic:{}]", protocol.getType().name(), getTopic());
+ }
+
+ @Override
+ public void stop() {
+ messageBus.unregister(this);
+ log.info("[stop][{} 下行消息订阅已停止,Topic:{}]", protocol.getType().name(), getTopic());
+ }
+
@Override
public void onMessage(IotDeviceMessage message) {
log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
@@ -51,18 +55,25 @@ public class IotEmqxDownstreamSubscriber implements IotMessageSubscriber protocols = new ArrayList<>();
+
+ @Getter
+ private volatile boolean running = false;
+
+ public IotProtocolManager(IotGatewayProperties gatewayProperties) {
+ this.gatewayProperties = gatewayProperties;
+ }
+
+ @Override
+ public void start() {
+ if (running) {
+ return;
+ }
+ List protocolConfigs = gatewayProperties.getProtocols();
+ if (CollUtil.isEmpty(protocolConfigs)) {
+ log.info("[start][没有配置协议实例,跳过启动]");
+ return;
+ }
+
+ for (IotGatewayProperties.ProtocolProperties config : protocolConfigs) {
+ if (BooleanUtil.isFalse(config.getEnabled())) {
+ log.info("[start][协议实例 {} 未启用,跳过]", config.getId());
+ continue;
+ }
+ IotProtocol protocol = createProtocol(config);
+ if (protocol == null) {
+ continue;
+ }
+ protocol.start();
+ protocols.add(protocol);
+ }
+ running = true;
+ log.info("[start][协议管理器启动完成,共启动 {} 个协议实例]", protocols.size());
+ }
+
+ @Override
+ public void stop() {
+ if (!running) {
+ return;
+ }
+ for (IotProtocol protocol : protocols) {
+ try {
+ protocol.stop();
+ } catch (Exception e) {
+ log.error("[stop][协议实例 {} 停止失败]", protocol.getId(), e);
+ }
+ }
+ protocols.clear();
+ running = false;
+ log.info("[stop][协议管理器已停止]");
+ }
+
+ /**
+ * 创建协议实例
+ *
+ * @param config 协议实例配置
+ * @return 协议实例
+ */
+ @SuppressWarnings({"EnhancedSwitchMigration"})
+ private IotProtocol createProtocol(IotGatewayProperties.ProtocolProperties config) {
+ IotProtocolTypeEnum protocolType = IotProtocolTypeEnum.of(config.getProtocol());
+ if (protocolType == null) {
+ log.error("[createProtocol][协议实例 {} 的协议类型 {} 不存在]", config.getId(), config.getProtocol());
+ return null;
+ }
+ switch (protocolType) {
+ case HTTP:
+ return createHttpProtocol(config);
+ case TCP:
+ return createTcpProtocol(config);
+ case UDP:
+ return createUdpProtocol(config);
+ case COAP:
+ return createCoapProtocol(config);
+ case WEBSOCKET:
+ return createWebSocketProtocol(config);
+ case MQTT:
+ return createMqttProtocol(config);
+ case EMQX:
+ return createEmqxProtocol(config);
+ case MODBUS_TCP_CLIENT:
+ return createModbusTcpClientProtocol(config);
+ case MODBUS_TCP_SERVER:
+ return createModbusTcpServerProtocol(config);
+ default:
+ throw new IllegalArgumentException(String.format(
+ "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType));
+ }
+ }
+
+ /**
+ * 创建 HTTP 协议实例
+ *
+ * @param config 协议实例配置
+ * @return HTTP 协议实例
+ */
+ private IotHttpProtocol createHttpProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotHttpProtocol(config);
+ }
+
+ /**
+ * 创建 TCP 协议实例
+ *
+ * @param config 协议实例配置
+ * @return TCP 协议实例
+ */
+ private IotTcpProtocol createTcpProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotTcpProtocol(config);
+ }
+
+ /**
+ * 创建 UDP 协议实例
+ *
+ * @param config 协议实例配置
+ * @return UDP 协议实例
+ */
+ private IotUdpProtocol createUdpProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotUdpProtocol(config);
+ }
+
+ /**
+ * 创建 CoAP 协议实例
+ *
+ * @param config 协议实例配置
+ * @return CoAP 协议实例
+ */
+ private IotCoapProtocol createCoapProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotCoapProtocol(config);
+ }
+
+ /**
+ * 创建 WebSocket 协议实例
+ *
+ * @param config 协议实例配置
+ * @return WebSocket 协议实例
+ */
+ private IotWebSocketProtocol createWebSocketProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotWebSocketProtocol(config);
+ }
+
+ /**
+ * 创建 MQTT 协议实例
+ *
+ * @param config 协议实例配置
+ * @return MQTT 协议实例
+ */
+ private IotMqttProtocol createMqttProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotMqttProtocol(config);
+ }
+
+ /**
+ * 创建 EMQX 协议实例
+ *
+ * @param config 协议实例配置
+ * @return EMQX 协议实例
+ */
+ private IotEmqxProtocol createEmqxProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotEmqxProtocol(config);
+ }
+
+ /**
+ * 创建 Modbus TCP Client 协议实例
+ *
+ * @param config 协议实例配置
+ * @return Modbus TCP Client 协议实例
+ */
+ private IotModbusTcpClientProtocol createModbusTcpClientProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotModbusTcpClientProtocol(config);
+ }
+
+ /**
+ * 创建 Modbus TCP Server 协议实例
+ *
+ * @param config 协议实例配置
+ * @return Modbus TCP Server 协议实例
+ */
+ private IotModbusTcpServerProtocol createModbusTcpServerProtocol(IotGatewayProperties.ProtocolProperties config) {
+ return new IotModbusTcpServerProtocol(config);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapConfig.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapConfig.java
new file mode 100644
index 0000000000..45fe3007e5
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapConfig.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * IoT CoAP 协议配置
+ *
+ * @author 芋道源码
+ */
+@Data
+public class IotCoapConfig {
+
+ /**
+ * 最大消息大小(字节)
+ */
+ @NotNull(message = "最大消息大小不能为空")
+ @Min(value = 64, message = "最大消息大小必须大于 64 字节")
+ private Integer maxMessageSize = 1024;
+
+ /**
+ * ACK 超时时间(毫秒)
+ */
+ @NotNull(message = "ACK 超时时间不能为空")
+ @Min(value = 100, message = "ACK 超时时间必须大于 100 毫秒")
+ private Integer ackTimeoutMs = 2000;
+
+ /**
+ * 最大重传次数
+ */
+ @NotNull(message = "最大重传次数不能为空")
+ @Min(value = 0, message = "最大重传次数必须大于等于 0")
+ private Integer maxRetransmit = 4;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapDownstreamSubscriber.java
deleted file mode 100644
index d01cdc416c..0000000000
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapDownstreamSubscriber.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
-
-import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
-import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
-import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
-import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
-import jakarta.annotation.PostConstruct;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * IoT 网关 CoAP 订阅者:接收下行给设备的消息
- *
- * @author 芋道源码
- */
-@RequiredArgsConstructor
-@Slf4j
-public class IotCoapDownstreamSubscriber implements IotMessageSubscriber {
-
- private final IotCoapUpstreamProtocol protocol;
-
- private final IotMessageBus messageBus;
-
- @PostConstruct
- public void init() {
- messageBus.register(this);
- }
-
- @Override
- public String getTopic() {
- return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
- }
-
- @Override
- public String getGroup() {
- // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
- return getTopic();
- }
-
- @Override
- public void onMessage(IotDeviceMessage message) {
- // 如需支持,可通过 CoAP Observe 模式实现(设备订阅资源,服务器推送变更)
- log.warn("[onMessage][IoT 网关 CoAP 协议暂不支持下行消息,忽略消息:{}]", message);
- }
-
-}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java
new file mode 100644
index 0000000000..4bc8cdbe28
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java
@@ -0,0 +1,175 @@
+package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
+
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
+import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
+import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
+import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties;
+import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.downstream.IotCoapDownstreamSubscriber;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapAuthHandler;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapAuthResource;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterHandler;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterResource;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterSubHandler;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterSubResource;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapUpstreamHandler;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapUpstreamTopicResource;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.californium.core.CoapResource;
+import org.eclipse.californium.core.CoapServer;
+import org.eclipse.californium.core.config.CoapConfig;
+import org.eclipse.californium.elements.config.Configuration;
+import cn.hutool.core.lang.Assert;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * IoT CoAP 协议实现
+ *
+ * 基于 Eclipse Californium 实现,支持:
+ * 1. 认证:POST /auth
+ * 2. 设备动态注册:POST /auth/register/device
+ * 3. 子设备动态注册:POST /auth/register/sub-device/{productKey}/{deviceName}
+ * 4. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post
+ * 5. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotCoapProtocol implements IotProtocol {
+
+ /**
+ * 协议配置
+ */
+ private final ProtocolProperties properties;
+ /**
+ * 服务器 ID(用于消息追踪,全局唯一)
+ */
+ @Getter
+ private final String serverId;
+
+ /**
+ * 运行状态
+ */
+ @Getter
+ private volatile boolean running = false;
+
+ /**
+ * CoAP 服务器
+ */
+ private CoapServer coapServer;
+
+ /**
+ * 下行消息订阅者
+ */
+ private IotCoapDownstreamSubscriber downstreamSubscriber;
+
+ public IotCoapProtocol(ProtocolProperties properties) {
+ IotCoapConfig coapConfig = properties.getCoap();
+ Assert.notNull(coapConfig, "CoAP 协议配置(coap)不能为空");
+ this.properties = properties;
+ this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
+ }
+
+ @Override
+ public String getId() {
+ return properties.getId();
+ }
+
+ @Override
+ public IotProtocolTypeEnum getType() {
+ return IotProtocolTypeEnum.COAP;
+ }
+
+ @Override
+ public void start() {
+ if (running) {
+ log.warn("[start][IoT CoAP 协议 {} 已经在运行中]", getId());
+ return;
+ }
+
+ try {
+ // 1.1 创建 CoAP 配置
+ IotCoapConfig coapConfig = properties.getCoap();
+ Configuration config = Configuration.createStandardWithoutFile();
+ config.set(CoapConfig.COAP_PORT, properties.getPort());
+ config.set(CoapConfig.MAX_MESSAGE_SIZE, coapConfig.getMaxMessageSize());
+ config.set(CoapConfig.ACK_TIMEOUT, coapConfig.getAckTimeoutMs(), TimeUnit.MILLISECONDS);
+ config.set(CoapConfig.MAX_RETRANSMIT, coapConfig.getMaxRetransmit());
+ // 1.2 创建 CoAP 服务器
+ coapServer = new CoapServer(config);
+
+ // 2.1 添加 /auth 认证资源
+ IotCoapAuthHandler authHandler = new IotCoapAuthHandler(serverId);
+ IotCoapAuthResource authResource = new IotCoapAuthResource(authHandler);
+ coapServer.add(authResource);
+ // 2.2 添加 /auth/register/device 设备动态注册资源(一型一密)
+ IotCoapRegisterHandler registerHandler = new IotCoapRegisterHandler();
+ IotCoapRegisterResource registerResource = new IotCoapRegisterResource(registerHandler);
+ // 2.3 添加 /auth/register/sub-device/{productKey}/{deviceName} 子设备动态注册资源
+ IotCoapRegisterSubHandler registerSubHandler = new IotCoapRegisterSubHandler();
+ IotCoapRegisterSubResource registerSubResource = new IotCoapRegisterSubResource(registerSubHandler);
+ authResource.add(new CoapResource("register") {{
+ add(registerResource);
+ add(registerSubResource);
+ }});
+ // 2.4 添加 /topic 根资源(用于上行消息)
+ IotCoapUpstreamHandler upstreamHandler = new IotCoapUpstreamHandler(serverId);
+ IotCoapUpstreamTopicResource topicResource = new IotCoapUpstreamTopicResource(serverId, upstreamHandler);
+ coapServer.add(topicResource);
+
+ // 3. 启动服务器
+ coapServer.start();
+ running = true;
+ log.info("[start][IoT CoAP 协议 {} 启动成功,端口:{},serverId:{}]",
+ getId(), properties.getPort(), serverId);
+
+ // 4. 启动下行消息订阅者
+ IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
+ this.downstreamSubscriber = new IotCoapDownstreamSubscriber(this, messageBus);
+ this.downstreamSubscriber.start();
+ } catch (Exception e) {
+ log.error("[start][IoT CoAP 协议 {} 启动失败]", getId(), e);
+ stop0();
+ throw e;
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (!running) {
+ return;
+ }
+ stop0();
+ }
+
+ private void stop0() {
+ // 1. 停止下行消息订阅者
+ if (downstreamSubscriber != null) {
+ try {
+ downstreamSubscriber.stop();
+ log.info("[stop][IoT CoAP 协议 {} 下行消息订阅者已停止]", getId());
+ } catch (Exception e) {
+ log.error("[stop][IoT CoAP 协议 {} 下行消息订阅者停止失败]", getId(), e);
+ }
+ downstreamSubscriber = null;
+ }
+
+ // 2. 关闭 CoAP 服务器
+ if (coapServer != null) {
+ try {
+ coapServer.stop();
+ coapServer.destroy();
+ coapServer = null;
+ log.info("[stop][IoT CoAP 协议 {} 服务器已停止]", getId());
+ } catch (Exception e) {
+ log.error("[stop][IoT CoAP 协议 {} 服务器停止失败]", getId(), e);
+ }
+ }
+ running = false;
+ log.info("[stop][IoT CoAP 协议 {} 已停止]", getId());
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapUpstreamProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapUpstreamProtocol.java
deleted file mode 100644
index e259aa69c7..0000000000
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapUpstreamProtocol.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
-
-import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
-import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthHandler;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthResource;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapRegisterHandler;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapRegisterResource;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapUpstreamTopicResource;
-import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapUpstreamHandler;
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.PreDestroy;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-import org.eclipse.californium.core.CoapResource;
-import org.eclipse.californium.core.CoapServer;
-import org.eclipse.californium.core.config.CoapConfig;
-import org.eclipse.californium.elements.config.Configuration;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * IoT 网关 CoAP 协议:接收设备上行消息
- *
- * 基于 Eclipse Californium 实现,支持:
- * 1. 认证:POST /auth
- * 2. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post
- * 3. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post
- *
- * @author 芋道源码
- */
-@Slf4j
-public class IotCoapUpstreamProtocol {
-
- private final IotGatewayProperties.CoapProperties coapProperties;
-
- private CoapServer coapServer;
-
- @Getter
- private final String serverId;
-
- public IotCoapUpstreamProtocol(IotGatewayProperties.CoapProperties coapProperties) {
- this.coapProperties = coapProperties;
- this.serverId = IotDeviceMessageUtils.generateServerId(coapProperties.getPort());
- }
-
- @PostConstruct
- public void start() {
- try {
- // 1.1 创建网络配置(Californium 3.x API)
- Configuration config = Configuration.createStandardWithoutFile();
- config.set(CoapConfig.COAP_PORT, coapProperties.getPort());
- config.set(CoapConfig.MAX_MESSAGE_SIZE, coapProperties.getMaxMessageSize());
- config.set(CoapConfig.ACK_TIMEOUT, coapProperties.getAckTimeout(), TimeUnit.MILLISECONDS);
- config.set(CoapConfig.MAX_RETRANSMIT, coapProperties.getMaxRetransmit());
- // 1.2 创建 CoAP 服务器
- coapServer = new CoapServer(config);
-
- // 2.1 添加 /auth 认证资源
- IotCoapAuthHandler authHandler = new IotCoapAuthHandler();
- IotCoapAuthResource authResource = new IotCoapAuthResource(this, authHandler);
- coapServer.add(authResource);
- // 2.2 添加 /auth/register/device 设备动态注册资源(一型一密)
- IotCoapRegisterHandler registerHandler = new IotCoapRegisterHandler();
- IotCoapRegisterResource registerResource = new IotCoapRegisterResource(registerHandler);
- authResource.add(new CoapResource("register") {{
- add(registerResource);
- }});
- // 2.3 添加 /topic 根资源(用于上行消息)
- IotCoapUpstreamHandler upstreamHandler = new IotCoapUpstreamHandler();
- IotCoapUpstreamTopicResource topicResource = new IotCoapUpstreamTopicResource(this, upstreamHandler);
- coapServer.add(topicResource);
-
- // 3. 启动服务器
- coapServer.start();
- log.info("[start][IoT 网关 CoAP 协议启动成功,端口:{},资源:/auth, /auth/register/device, /topic]", coapProperties.getPort());
- } catch (Exception e) {
- log.error("[start][IoT 网关 CoAP 协议启动失败]", e);
- throw e;
- }
- }
-
- @PreDestroy
- public void stop() {
- if (coapServer != null) {
- try {
- coapServer.stop();
- log.info("[stop][IoT 网关 CoAP 协议已停止]");
- } catch (Exception e) {
- log.error("[stop][IoT 网关 CoAP 协议停止失败]", e);
- }
- }
- }
-
-}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/downstream/IotCoapDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/downstream/IotCoapDownstreamSubscriber.java
new file mode 100644
index 0000000000..3309d2cd49
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/downstream/IotCoapDownstreamSubscriber.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.downstream;
+
+import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
+import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * IoT 网关 CoAP 订阅者:接收下行给设备的消息
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotCoapDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
+
+ public IotCoapDownstreamSubscriber(IotCoapProtocol protocol, IotMessageBus messageBus) {
+ super(protocol, messageBus);
+ }
+
+ @Override
+ protected void handleMessage(IotDeviceMessage message) {
+ // 如需支持,可通过 CoAP Observe 模式实现(设备订阅资源,服务器推送变更)
+ log.warn("[handleMessage][IoT 网关 CoAP 协议暂不支持下行消息,忽略消息:{}]", message);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapAbstractHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapAbstractHandler.java
new file mode 100644
index 0000000000..994fb147d2
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapAbstractHandler.java
@@ -0,0 +1,186 @@
+package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
+import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.californium.core.coap.CoAP;
+import org.eclipse.californium.core.coap.MediaTypeRegistry;
+import org.eclipse.californium.core.coap.Option;
+import org.eclipse.californium.core.server.resources.CoapExchange;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * IoT 网关 CoAP 协议的处理器抽象基类:提供通用的前置处理(认证)、请求解析、响应处理、全局的异常捕获等
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public abstract class IotCoapAbstractHandler {
+
+ /**
+ * 自定义 CoAP Option 编号,用于携带 Token
+ *
+ * CoAP Option 范围 2048-65535 属于实验/自定义范围
+ */
+ public static final int OPTION_TOKEN = 2088;
+
+ private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
+
+ /**
+ * 处理 CoAP 请求(模板方法)
+ *
+ * @param exchange CoAP 交换对象
+ */
+ public final void handle(CoapExchange exchange) {
+ try {
+ // 1. 前置处理
+ beforeHandle(exchange);
+
+ // 2. 执行业务逻辑
+ CommonResult