params;
+
+ /**
+ * 设备信息
+ */
+ @Data
+ public static class Device {
+
+ /**
+ * 产品标识
+ */
+ @NotEmpty(message = "产品标识不能为空")
+ private String productKey;
+
+ /**
+ * 设备名称
+ */
+ @NotEmpty(message = "设备名称不能为空")
+ private String deviceName;
+
+ // TODO @芋艿:阿里云还有 sign 签名
+
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java
new file mode 100644
index 000000000..a0c8ce92a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
+
+import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * IoT 设备上行的抽象 Request DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public abstract class IotDeviceUpstreamAbstractReqDTO {
+
+ /**
+ * 请求编号
+ */
+ private String requestId;
+
+ /**
+ * 插件实例的进程编号
+ */
+ private String processId;
+
+ /**
+ * 产品标识
+ */
+ @NotEmpty(message = "产品标识不能为空")
+ private String productKey;
+ /**
+ * 设备名称
+ */
+ @NotEmpty(message = "设备名称不能为空")
+ private String deviceName;
+
+ /**
+ * 上报时间
+ */
+ @JsonSerialize(using = TimestampLocalDateTimeSerializer.class) // 解决 iot plugins 序列化 LocalDateTime 是数组,导致无法解析的问题
+ private LocalDateTime reportTime;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotPluginInstanceHeartbeatReqDTO.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotPluginInstanceHeartbeatReqDTO.java
new file mode 100644
index 000000000..9125b5f24
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotPluginInstanceHeartbeatReqDTO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * IoT 插件实例心跳 Request DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class IotPluginInstanceHeartbeatReqDTO {
+
+ /**
+ * 请求编号
+ */
+ @NotEmpty(message = "请求编号不能为空")
+ private String processId;
+
+ /**
+ * 插件包标识符
+ */
+ @NotEmpty(message = "插件包标识符不能为空")
+ private String pluginKey;
+
+ /**
+ * 插件实例所在 IP
+ */
+ @NotEmpty(message = "插件实例所在 IP 不能为空")
+ private String hostIp;
+ /**
+ * 插件实例的进程编号
+ */
+ @NotNull(message = "插件实例的进程编号不能为空")
+ private Integer downstreamPort;
+
+ /**
+ * 是否在线
+ */
+ @NotNull(message = "是否在线不能为空")
+ private Boolean online;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/package-info.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/package-info.java
new file mode 100644
index 000000000..cb946cd89
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * TODO 芋艿:占位
+ */
+package cn.iocoder.yudao.module.iot.api.device.dto;
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
new file mode 100644
index 000000000..7da0c665b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 占位
+ *
+ * TODO 芋艿:后续删除
+ */
+package cn.iocoder.yudao.module.iot.api;
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ApiConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ApiConstants.java
new file mode 100644
index 000000000..2c4147be1
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ApiConstants.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.iot.enums;
+
+import cn.iocoder.yudao.framework.common.enums.RpcConstants;
+
+/**
+ * API 相关的枚举
+ *
+ * @author 芋道源码
+ */
+public class ApiConstants {
+
+ public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/iot";
+
+ public static final String VERSION = "1.0.0";
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
new file mode 100644
index 000000000..d8f0cc60d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.iot.enums;
+
+/**
+ * IoT 字典类型的枚举类
+ *
+ * @author 芋道源码
+ */
+public class DictTypeConstants {
+
+ public static final String PRODUCT_STATUS = "iot_product_status";
+ public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type";
+ public static final String NET_TYPE = "iot_net_type";
+ public static final String PROTOCOL_TYPE = "iot_protocol_type";
+ public static final String DATA_FORMAT = "iot_data_format";
+ public static final String VALIDATE_TYPE = "iot_validate_type";
+
+ public static final String DEVICE_STATE = "iot_device_state";
+
+ public static final String IOT_DATA_BRIDGE_DIRECTION_ENUM = "iot_data_bridge_direction_enum";
+ public static final String IOT_DATA_BRIDGE_TYPE_ENUM = "iot_data_bridge_type_enum";
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
new file mode 100644
index 000000000..230baca3f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.iot.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * iot 错误码枚举类
+ *
+ * iot 系统,使用 1-050-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 产品相关 1-050-001-000 ============
+ ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
+ ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
+ ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
+ ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
+
+ // ========== 产品物模型 1-050-002-000 ============
+ ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
+ ErrorCode THING_MODEL_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在");
+ ErrorCode THING_MODEL_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。");
+ ErrorCode THING_MODEL_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。");
+ ErrorCode THING_MODEL_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效");
+
+ // ========== 设备 1-050-003-000 ============
+ ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
+ ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
+ ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
+ ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
+ ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
+ ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
+ ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
+ ErrorCode DEVICE_DOWNSTREAM_FAILED = new ErrorCode(1_050_003_007, "执行失败,原因:{}");
+
+ // ========== 产品分类 1-050-004-000 ==========
+ ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
+
+ // ========== 设备分组 1-050-005-000 ==========
+ ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
+ ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
+
+ // ========== 插件配置 1-050-006-000 ==========
+ ErrorCode PLUGIN_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "插件配置不存在");
+ ErrorCode PLUGIN_INSTALL_FAILED = new ErrorCode(1_050_006_001, "插件安装失败");
+ ErrorCode PLUGIN_INSTALL_FAILED_FILE_NAME_NOT_MATCH = new ErrorCode(1_050_006_002, "插件安装失败,文件名与原插件id不匹配");
+ ErrorCode PLUGIN_CONFIG_DELETE_FAILED_RUNNING = new ErrorCode(1_050_006_003, "请先停止插件");
+ ErrorCode PLUGIN_STATUS_INVALID = new ErrorCode(1_050_006_004, "插件状态无效");
+ ErrorCode PLUGIN_CONFIG_KEY_DUPLICATE = new ErrorCode(1_050_006_005, "插件标识已存在");
+ ErrorCode PLUGIN_START_FAILED = new ErrorCode(1_050_006_006, "插件启动失败");
+ ErrorCode PLUGIN_STOP_FAILED = new ErrorCode(1_050_006_007, "插件停止失败");
+
+ // ========== 插件实例 1-050-007-000 ==========
+
+ // ========== 固件相关 1-050-008-000 ==========
+
+ ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
+ ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
+
+ ErrorCode OTA_UPGRADE_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
+ ErrorCode OTA_UPGRADE_TASK_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "升级任务名称重复");
+ ErrorCode OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY = new ErrorCode(1_050_008_102, "设备编号列表不能为空");
+ ErrorCode OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY = new ErrorCode(1_050_008_103, "设备列表不能为空");
+ ErrorCode OTA_UPGRADE_TASK_CANNOT_CANCEL = new ErrorCode(1_050_008_104, "升级任务不能取消");
+
+ ErrorCode OTA_UPGRADE_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
+ ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_201, "升级记录重复");
+ ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_202, "升级记录不能重试");
+
+ // ========== MQTT 通信相关 1-050-009-000 ==========
+ ErrorCode MQTT_TOPIC_ILLEGAL = new ErrorCode(1_050_009_000, "topic illegal");
+
+ // ========== IoT 数据桥梁 1-050-010-000 ==========
+ ErrorCode DATA_BRIDGE_NOT_EXISTS = new ErrorCode(1_050_010_000, "IoT 数据桥梁不存在");
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java
new file mode 100644
index 000000000..6de9359ba
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.iot.enums.device;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+// TODO @芋艿:需要添加对应的 DTO,以及上下行的链路,网关、网关服务、设备等
+/**
+ * IoT 设备消息标识符枚举
+ */
+@Getter
+@RequiredArgsConstructor
+public enum IotDeviceMessageIdentifierEnum {
+
+ PROPERTY_GET("get"), // 下行 TODO 芋艿:【讨论】貌似这个“上行”更合理?device 主动拉取配置。和 IotDevicePropertyGetReqDTO 一样的配置
+ PROPERTY_SET("set"), // 下行
+ PROPERTY_REPORT("report"), // 上行
+
+ STATE_ONLINE("online"), // 上行
+ STATE_OFFLINE("offline"), // 上行
+
+ CONFIG_GET("get"), // 上行 TODO 芋艿:【讨论】暂时没有上行的场景
+ CONFIG_SET("set"), // 下行
+
+ SERVICE_INVOKE("${identifier}"), // 下行
+ SERVICE_REPLY_SUFFIX("_reply"), // 芋艿:TODO 芋艿:【讨论】上行 or 下行
+
+ OTA_UPGRADE("upgrade"), // 下行
+ OTA_PULL("pull"), // 上行
+ OTA_PROGRESS("progress"), // 上行
+ OTA_REPORT("report"), // 上行
+
+ REGISTER_REGISTER("register"), // 上行
+ REGISTER_REGISTER_SUB("register_sub"), // 上行
+ REGISTER_UNREGISTER_SUB("unregister_sub"), // 下行
+
+ TOPOLOGY_ADD("topology_add"), // 下行;
+ ;
+
+ /**
+ * 标志符
+ */
+ private final String identifier;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java
new file mode 100644
index 000000000..0354157ed
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.enums.device;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 设备消息类型枚举
+ */
+@Getter
+@RequiredArgsConstructor
+public enum IotDeviceMessageTypeEnum implements ArrayValuable {
+
+ STATE("state"), // 设备状态
+ PROPERTY("property"), // 设备属性:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
+ EVENT("event"), // 设备事件:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
+ SERVICE("service"), // 设备服务:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
+ CONFIG("config"), // 设备配置:可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置
+ OTA("ota"), // 设备 OTA:可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级
+ REGISTER("register"), // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
+ TOPOLOGY("topology"),; // 设备拓扑:可参考 https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships 设备拓扑
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new);
+
+ /**
+ * 属性
+ */
+ private final String type;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceStateEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceStateEnum.java
new file mode 100644
index 000000000..6ce2677db
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceStateEnum.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.iot.enums.device;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 设备状态枚举
+ *
+ * @author haohao
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotDeviceStateEnum implements ArrayValuable {
+
+ INACTIVE(0, "未激活"),
+ ONLINE(1, "在线"),
+ OFFLINE(2, "离线");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDeviceStateEnum::getState).toArray(Integer[]::new);
+
+ /**
+ * 状态
+ */
+ private final Integer state;
+ /**
+ * 状态名
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean isOnline(Integer state) {
+ return ONLINE.getState().equals(state);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeRecordStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeRecordStatusEnum.java
new file mode 100644
index 000000000..e809a7e5b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeRecordStatusEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.enums.ota;
+
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT OTA 升级记录的范围枚举
+ *
+ * @author haohao
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotOtaUpgradeRecordStatusEnum implements ArrayValuable {
+
+ PENDING(0), // 待推送
+ PUSHED(10), // 已推送
+ UPGRADING(20), // 升级中
+ SUCCESS(30), // 升级成功
+ FAILURE(40), // 升级失败
+ CANCELED(50),; // 已取消
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotOtaUpgradeRecordStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 范围
+ */
+ private final Integer status;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskScopeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskScopeEnum.java
new file mode 100644
index 000000000..6dccbb041
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskScopeEnum.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.iot.enums.ota;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT OTA 升级任务的范围枚举
+ *
+ * @author haohao
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotOtaUpgradeTaskScopeEnum implements ArrayValuable {
+
+ ALL(1), // 全部设备:只包括当前产品下的设备,不包括未来创建的设备
+ SELECT(2); // 指定设备
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotOtaUpgradeTaskScopeEnum::getScope).toArray(Integer[]::new);
+
+ /**
+ * 范围
+ */
+ private final Integer scope;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskStatusEnum.java
new file mode 100644
index 000000000..78af16cb2
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskStatusEnum.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.enums.ota;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT OTA 升级任务的范围枚举
+ *
+ * @author haohao
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotOtaUpgradeTaskStatusEnum implements ArrayValuable {
+
+ IN_PROGRESS(10), // 进行中:升级中
+ COMPLETED(20), // 已完成:已结束,全部升级完成
+ INCOMPLETE(21), // 未完成:已结束,部分升级完成
+ CANCELED(30),; // 已取消:一般是主动取消任务
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotOtaUpgradeTaskStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 范围
+ */
+ private final Integer status;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java
new file mode 100644
index 000000000..b6ef4f0cc
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.enums.plugin;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 部署方式枚举
+ *
+ * @author haohao
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotPluginDeployTypeEnum implements ArrayValuable {
+
+ JAR(0, "JAR 部署"),
+ STANDALONE(1, "独立部署");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginDeployTypeEnum::getDeployType).toArray(Integer[]::new);
+
+ /**
+ * 部署方式
+ */
+ private final Integer deployType;
+ /**
+ * 部署方式名
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java
new file mode 100644
index 000000000..7e3fa657e
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.enums.plugin;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 插件状态枚举
+ *
+ * @author haohao
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotPluginStatusEnum implements ArrayValuable {
+
+ STOPPED(0, "停止"),
+ RUNNING(1, "运行");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 状态名
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java
new file mode 100644
index 000000000..ec0b72f9f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.enums.plugin;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 插件类型枚举
+ *
+ * @author haohao
+ */
+@AllArgsConstructor
+@Getter
+public enum IotPluginTypeEnum implements ArrayValuable {
+
+ NORMAL(0, "普通插件"),
+ DEVICE(1, "设备插件");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 类型名
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java
new file mode 100644
index 000000000..0cfe1c9f4
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.enums.product;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 产品数据格式枚举类
+ *
+ * @author ahh
+ * @see 阿里云 - 什么是消息解析
+ */
+@AllArgsConstructor
+@Getter
+public enum IotDataFormatEnum implements ArrayValuable {
+
+ JSON(0, "标准数据格式(JSON)"),
+ CUSTOMIZE(1, "透传/自定义");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDataFormatEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 描述
+ */
+ private final String description;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotNetTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotNetTypeEnum.java
new file mode 100644
index 000000000..2a54e489f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotNetTypeEnum.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.enums.product;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 联网方式枚举类
+ *
+ * @author ahh
+ */
+@AllArgsConstructor
+@Getter
+public enum IotNetTypeEnum implements ArrayValuable {
+
+ WIFI(0, "Wi-Fi"),
+ CELLULAR(1, "Cellular"),
+ ETHERNET(2, "Ethernet"),
+ OTHER(3, "其他");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotNetTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 描述
+ */
+ private final String description;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java
new file mode 100644
index 000000000..7910f1b2d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.iot.enums.product;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 产品的设备类型
+ *
+ * @author ahh
+ */
+@AllArgsConstructor
+@Getter
+public enum IotProductDeviceTypeEnum implements ArrayValuable {
+
+ DIRECT(0, "直连设备"),
+ GATEWAY_SUB(1, "网关子设备"),
+ GATEWAY(2, "网关设备");
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+
+ /**
+ * 描述
+ */
+ private final String description;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotProductDeviceTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ /**
+ * 判断是否是网关
+ *
+ * @param type 类型
+ * @return 是否是网关
+ */
+ public static boolean isGateway(Integer type) {
+ return GATEWAY.getType().equals(type);
+ }
+
+ /**
+ * 判断是否是网关子设备
+ *
+ * @param type 类型
+ * @return 是否是网关子设备
+ */
+ public static boolean isGatewaySub(Integer type) {
+ return GATEWAY_SUB.getType().equals(type);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductStatusEnum.java
new file mode 100644
index 000000000..b9bbbeec7
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductStatusEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.enums.product;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 产品的状态枚举类
+ *
+ * @author ahh
+ */
+@AllArgsConstructor
+@Getter
+public enum IotProductStatusEnum implements ArrayValuable {
+
+ UNPUBLISHED(0, "开发中"),
+ PUBLISHED(1, "已发布");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotProductStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer status;
+ /**
+ * 描述
+ */
+ private final String description;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java
new file mode 100644
index 000000000..d24dea92e
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.iot.enums.product;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 接入网关协议枚举类
+ *
+ * @author ahh
+ */
+@AllArgsConstructor
+@Getter
+public enum IotProtocolTypeEnum implements ArrayValuable {
+
+ CUSTOM(0, "自定义"),
+ MODBUS(1, "Modbus"),
+ OPC_UA(2, "OPC UA"),
+ ZIGBEE(3, "ZigBee"),
+ BLE(4, "BLE");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotProtocolTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 描述
+ */
+ private final String description;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java
new file mode 100644
index 000000000..2a15d16a4
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.enums.product;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 数据校验级别枚举类
+ *
+ * @author ahh
+ */
+@AllArgsConstructor
+@Getter
+public enum IotValidateTypeEnum implements ArrayValuable {
+
+ WEAK(0, "弱校验"),
+ NONE(1, "免校验");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotValidateTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 描述
+ */
+ private final String description;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotAlertConfigReceiveTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotAlertConfigReceiveTypeEnum.java
new file mode 100644
index 000000000..3fdd53234
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotAlertConfigReceiveTypeEnum.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.iot.enums.rule;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 告警配置的接收方式枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotAlertConfigReceiveTypeEnum implements ArrayValuable {
+
+ SMS(1), // 短信
+ MAIL(2), // 邮箱
+ NOTIFY(3); // 通知
+
+ private final Integer type;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotAlertConfigReceiveTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataBridgeDirectionEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataBridgeDirectionEnum.java
new file mode 100644
index 000000000..a9d445fd2
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataBridgeDirectionEnum.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.enums.rule;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 数据桥接的方向枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotDataBridgeDirectionEnum implements ArrayValuable {
+
+ INPUT(1), // 输入
+ OUTPUT(2); // 输出
+
+ private final Integer type;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDataBridgeDirectionEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataBridgeTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataBridgeTypeEnum.java
new file mode 100644
index 000000000..78fc8452e
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataBridgeTypeEnum.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.iot.enums.rule;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 数据桥接的类型枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotDataBridgeTypeEnum implements ArrayValuable {
+
+ HTTP(1, "HTTP"),
+ TCP(2, "TCP"),
+ WEBSOCKET(3, "WEBSOCKET"),
+
+ MQTT(10, "MQTT"),
+
+ DATABASE(20, "DATABASE"),
+ REDIS_STREAM(21, "REDIS_STREAM"),
+
+ ROCKETMQ(30, "ROCKETMQ"),
+ RABBITMQ(31, "RABBITMQ"),
+ KAFKA(32, "KAFKA");
+
+ private final Integer type;
+
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDataBridgeTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java
new file mode 100644
index 000000000..2bdf7d0ed
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.iot.enums.rule;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 规则场景的触发类型枚举
+ *
+ * 设备触发,定时触发
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotRuleSceneActionTypeEnum implements ArrayValuable {
+
+ DEVICE_CONTROL(1), // 设备执行
+ ALERT(2), // 告警执行
+ DATA_BRIDGE(3); // 桥接执行
+
+ private final Integer type;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneActionTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerConditionParameterOperatorEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerConditionParameterOperatorEnum.java
new file mode 100644
index 000000000..5ed90ccae
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerConditionParameterOperatorEnum.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.iot.enums.rule;
+
+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 IotRuleSceneTriggerConditionParameterOperatorEnum implements ArrayValuable {
+
+ EQUALS("=", "#source == #value"),
+ NOT_EQUALS("!=", "!(#source == #value)"),
+
+ GREATER_THAN(">", "#source > #value"),
+ GREATER_THAN_OR_EQUALS(">=", "#source >= #value"),
+
+ LESS_THAN("<", "#source < #value"),
+ LESS_THAN_OR_EQUALS("<=", "#source <= #value"),
+
+ IN("in", "#values.contains(#source)"),
+ NOT_IN("not in", "!(#values.contains(#source))"),
+
+ BETWEEN("between", "(#source >= #values.get(0)) && (#source <= #values.get(1))"),
+ NOT_BETWEEN("not between", "(#source < #values.get(0)) || (#source > #values.get(1))"),
+
+ LIKE("like", "#source.contains(#value)"), // 字符串匹配
+ NOT_NULL("not null", "#source != null && #source.length() > 0"); // 非空
+
+ private final String operator;
+ private final String springExpression;
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerConditionParameterOperatorEnum::getOperator).toArray(String[]::new);
+
+ /**
+ * Spring 表达式 - 原始值
+ */
+ public static final String SPRING_EXPRESSION_SOURCE = "source";
+ /**
+ * Spring 表达式 - 目标值
+ */
+ public static final String SPRING_EXPRESSION_VALUE = "value";
+ /**
+ * Spring 表达式 - 目标值数组
+ */
+ public static final String SPRING_EXPRESSION_VALUE_List = "values";
+
+ public static IotRuleSceneTriggerConditionParameterOperatorEnum operatorOf(String operator) {
+ return ArrayUtil.firstMatch(item -> item.getOperator().equals(operator), values());
+ }
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java
new file mode 100644
index 000000000..a420a21d5
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.enums.rule;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 场景流转的触发类型枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable {
+
+ DEVICE(1), // 设备触发
+ TIMER(2); // 定时触发
+
+ private final Integer type;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotDataSpecsDataTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotDataSpecsDataTypeEnum.java
new file mode 100644
index 000000000..5524fdeb4
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotDataSpecsDataTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 数据定义的数据类型枚举类
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum IotDataSpecsDataTypeEnum implements ArrayValuable {
+
+ INT("int"),
+ FLOAT("float"),
+ DOUBLE("double"),
+ ENUM("enum"),
+ BOOL("bool"),
+ TEXT("text"),
+ DATE("date"),
+ STRUCT("struct"),
+ ARRAY("array");
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotDataSpecsDataTypeEnum::getDataType).toArray(String[]::new);
+
+ private final String dataType;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelAccessModeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelAccessModeEnum.java
new file mode 100644
index 000000000..c0a2b329b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelAccessModeEnum.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 产品物模型属性读取类型枚举
+ *
+ * @author ahh
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelAccessModeEnum implements ArrayValuable {
+
+ READ_ONLY("r"),
+ READ_WRITE("rw");
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotThingModelAccessModeEnum::getMode).toArray(String[]::new);
+
+ private final String mode;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelParamDirectionEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelParamDirectionEnum.java
new file mode 100644
index 000000000..4f06cefce
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelParamDirectionEnum.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+
+/**
+ * IoT 产品物模型参数是输入参数还是输出参数枚举
+ *
+ * @author HUIHUI
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelParamDirectionEnum implements ArrayValuable {
+
+ INPUT("input"), // 输入参数
+ OUTPUT("output"); // 输出参数
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotThingModelParamDirectionEnum::getDirection).toArray(String[]::new);
+
+ private final String direction;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceCallTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceCallTypeEnum.java
new file mode 100644
index 000000000..376db6b4a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceCallTypeEnum.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 产品物模型服务调用方式枚举
+ *
+ * @author HUIHUI
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelServiceCallTypeEnum implements ArrayValuable {
+
+ ASYNC("async"), // 异步调用
+ SYNC("sync"); // 同步调用
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotThingModelServiceCallTypeEnum::getType).toArray(String[]::new);
+
+ private final String type;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceEventTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceEventTypeEnum.java
new file mode 100644
index 000000000..c7c74092a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceEventTypeEnum.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 产品物模型事件类型枚举
+ *
+ * @author HUIHUI
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelServiceEventTypeEnum implements ArrayValuable {
+
+ INFO("info"), // 信息
+ ALERT("alert"), // 告警
+ ERROR("error"); // 故障
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotThingModelServiceEventTypeEnum::getType).toArray(String[]::new);
+
+ private final String type;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelTypeEnum.java
new file mode 100644
index 000000000..e0097cfe9
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelTypeEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 产品功能(物模型)类型枚举类
+ *
+ * @author ahh
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelTypeEnum implements ArrayValuable {
+
+ PROPERTY(1, "属性"),
+ SERVICE(2, "服务"),
+ EVENT(3, "事件");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotThingModelTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 描述
+ */
+ private final String description;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml
new file mode 100644
index 000000000..6b4d09fd9
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -0,0 +1,145 @@
+
+
+
+ yudao-module-iot
+ cn.iocoder.cloud
+ ${revision}
+
+ 4.0.0
+ jar
+
+ yudao-module-iot-biz
+
+ ${project.artifactId}
+
+ 物联网 模块,主要实现 产品管理、设备管理、协议管理等功能。
+
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-iot-api
+ ${revision}
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-tenant
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-web
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-security
+
+
+
+
+ com.taosdata.jdbc
+ taos-jdbcdriver
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mybatis
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-redis
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-job
+
+
+
+
+ org.quartz-scheduler
+ quartz
+ 2.3.2
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-test
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-excel
+
+
+
+
+ org.apache.rocketmq
+ rocketmq-spring-boot-starter
+ true
+
+
+ org.springframework.kafka
+ spring-kafka
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+ true
+
+
+
+ org.pf4j
+ pf4j-spring
+
+
+
+
+ org.apache.groovy
+ groovy-all
+ 4.0.25
+ pom
+
+
+
+
+ org.graalvm.js
+ js
+ 24.1.2
+ pom
+
+
+ org.graalvm.js
+ js-scriptengine
+ 24.1.2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/IoTServerApplication.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/IoTServerApplication.java
new file mode 100644
index 000000000..237de169d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/IoTServerApplication.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * 项目的启动类
+ *
+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ *
+ * @author 芋道源码
+ */
+@SpringBootApplication
+public class IoTServerApplication {
+
+ public static void main(String[] args) {
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+
+ SpringApplication.run(IoTServerApplication.class, args);
+
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java
new file mode 100644
index 000000000..9f54d60e8
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.iot;
+
+import cn.hutool.script.ScriptUtil;
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+/**
+ * TODO 芋艿:测试脚本的接入
+ */
+public class ScriptTest {
+
+ public static void main2(String[] args) {
+ // 创建一个 Groovy 脚本引擎
+ ScriptEngine engine = ScriptUtil.createGroovyEngine();
+
+ // 创建绑定参数
+ Bindings bindings = engine.createBindings();
+ bindings.put("name", "Alice");
+ bindings.put("age", 30);
+
+ // 定义一个稍微复杂的 Groovy 脚本
+ String script = "def greeting = 'Hello, ' + name + '!';\n" +
+ "def ageInFiveYears = age + 5;\n" +
+ "def message = greeting + ' In five years, you will be ' + ageInFiveYears + ' years old.';\n" +
+ "return message.toUpperCase();\n";
+
+ try {
+ // 执行脚本并获取结果
+ Object result = engine.eval(script, bindings);
+ System.out.println(result); // 输出: HELLO, ALICE! IN FIVE YEARS, YOU WILL BE 35 YEARS OLD.
+ } catch (ScriptException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) {
+ // 创建一个 JavaScript 脚本引擎
+ ScriptEngine jsEngine = ScriptUtil.createJsEngine();
+
+ // 创建绑定参数
+ Bindings jsBindings = jsEngine.createBindings();
+ jsBindings.put("name", "Bob");
+ jsBindings.put("age", 25);
+
+ // 定义一个简单的 JavaScript 脚本
+ String jsScript = "var greeting = 'Hello, ' + name + '!';\n" +
+ "var ageInTenYears = age + 10;\n" +
+ "var message = greeting + ' In ten years, you will be ' + ageInTenYears + ' years old.';\n" +
+ "message.toUpperCase();\n";
+
+ try {
+ // 执行脚本并获取结果
+ Object jsResult = jsEngine.eval(jsScript, jsBindings);
+ System.out.println(jsResult); // 输出: HELLO, BOB! IN TEN YEARS, YOU WILL BE 35 YEARS OLD.
+ } catch (ScriptException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
new file mode 100644
index 000000000..25faa1a6b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.iot.api.device;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
+import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
+import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * * 设备数据 Upstream 上行 API 实现类
+ */
+@RestController
+@Validated
+public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
+
+ @Resource
+ private IotDeviceUpstreamService deviceUpstreamService;
+ @Resource
+ private IotPluginInstanceService pluginInstanceService;
+
+ // ========== 设备相关 ==========
+
+ @Override
+ public CommonResult updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
+ deviceUpstreamService.updateDeviceState(updateReqDTO);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
+ deviceUpstreamService.reportDeviceProperty(reportReqDTO);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO) {
+ deviceUpstreamService.reportDeviceEvent(reportReqDTO);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
+ deviceUpstreamService.registerDevice(registerReqDTO);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
+ deviceUpstreamService.registerSubDevice(registerReqDTO);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
+ deviceUpstreamService.addDeviceTopology(addReqDTO);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
+ boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
+ return success(result);
+ }
+
+ // ========== 插件相关 ==========
+
+ @Override
+ public CommonResult heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
+ pluginInstanceService.heartbeatPluginInstance(heartbeatReqDTO);
+ return success(true);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
new file mode 100644
index 000000000..07852180d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 占位
+ *
+ * TODO 芋艿:后续删除
+ */
+package cn.iocoder.yudao.module.iot.api;
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.http b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.http
new file mode 100644
index 000000000..c1190cec1
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.http
@@ -0,0 +1,75 @@
+### 请求 /iot/device/downstream 接口(服务调用) => 成功
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenentId}}
+Authorization: Bearer {{token}}
+
+{
+ "id": 25,
+ "type": "service",
+ "identifier": "temperature",
+ "data": {
+ "xx": "yy"
+ }
+}
+
+### 请求 /iot/device/downstream 接口(属性设置) => 成功
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenentId}}
+Authorization: Bearer {{token}}
+
+{
+ "id": 25,
+ "type": "property",
+ "identifier": "set",
+ "data": {
+ "xx": "yy"
+ }
+}
+
+### 请求 /iot/device/downstream 接口(属性获取) => 成功
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenentId}}
+Authorization: Bearer {{token}}
+
+{
+ "id": 25,
+ "type": "property",
+ "identifier": "get",
+ "data": ["xx", "yy"]
+}
+
+### 请求 /iot/device/downstream 接口(配置设置) => 成功
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenentId}}
+Authorization: Bearer {{token}}
+
+{
+ "id": 25,
+ "type": "config",
+ "identifier": "set"
+}
+
+### 请求 /iot/device/downstream 接口(OTA 升级) => 成功
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenentId}}
+Authorization: Bearer {{token}}
+
+{
+ "id": 25,
+ "type": "ota",
+ "identifier": "upgrade",
+ "data": {
+ "firmwareId": 1,
+ "version": "1.0.0",
+ "signMethod": "MD5",
+ "fileSign": "d41d8cd98f00b204e9800998ecf8427e",
+ "fileSize": 1024,
+ "fileUrl": "http://example.com/firmware.bin",
+ "information": "{\"desc\":\"升级到最新版本\"}"
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
new file mode 100644
index 000000000..08fc244b1
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
@@ -0,0 +1,188 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
+import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService;
+import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - IoT 设备")
+@RestController
+@RequestMapping("/iot/device")
+@Validated
+public class IotDeviceController {
+
+ @Resource
+ private IotDeviceService deviceService;
+ @Resource
+ private IotDeviceUpstreamService deviceUpstreamService;
+ @Resource
+ private IotDeviceDownstreamService deviceDownstreamService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建设备")
+ @PreAuthorize("@ss.hasPermission('iot:device:create')")
+ public CommonResult createDevice(@Valid @RequestBody IotDeviceSaveReqVO createReqVO) {
+ return success(deviceService.createDevice(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新设备")
+ @PreAuthorize("@ss.hasPermission('iot:device:update')")
+ public CommonResult updateDevice(@Valid @RequestBody IotDeviceSaveReqVO updateReqVO) {
+ deviceService.updateDevice(updateReqVO);
+ return success(true);
+ }
+
+ // TODO @芋艿:参考阿里云:1)绑定网关;2)解绑网关
+
+ @PutMapping("/update-group")
+ @Operation(summary = "更新设备分组")
+ @PreAuthorize("@ss.hasPermission('iot:device:update')")
+ public CommonResult updateDeviceGroup(@Valid @RequestBody IotDeviceUpdateGroupReqVO updateReqVO) {
+ deviceService.updateDeviceGroup(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除单个设备")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('iot:device:delete')")
+ public CommonResult deleteDevice(@RequestParam("id") Long id) {
+ deviceService.deleteDevice(id);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete-list")
+ @Operation(summary = "删除多个设备")
+ @Parameter(name = "ids", description = "编号数组", required = true)
+ @PreAuthorize("@ss.hasPermission('iot:device:delete')")
+ public CommonResult deleteDeviceList(@RequestParam("ids") Collection ids) {
+ deviceService.deleteDeviceList(ids);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得设备")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('iot:device:query')")
+ public CommonResult getDevice(@RequestParam("id") Long id) {
+ IotDeviceDO device = deviceService.getDevice(id);
+ return success(BeanUtils.toBean(device, IotDeviceRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得设备分页")
+ @PreAuthorize("@ss.hasPermission('iot:device:query')")
+ public CommonResult> getDevicePage(@Valid IotDevicePageReqVO pageReqVO) {
+ PageResult pageResult = deviceService.getDevicePage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, IotDeviceRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出设备 Excel")
+ @PreAuthorize("@ss.hasPermission('iot:device:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportDeviceExcel(@Valid IotDevicePageReqVO exportReqVO,
+ HttpServletResponse response) throws IOException {
+ exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ CommonResult> result = getDevicePage(exportReqVO);
+ // 导出 Excel
+ ExcelUtils.write(response, "设备.xls", "数据", IotDeviceRespVO.class,
+ result.getData().getList());
+ }
+
+ @GetMapping("/count")
+ @Operation(summary = "获得设备数量")
+ @Parameter(name = "productId", description = "产品编号", example = "1")
+ @PreAuthorize("@ss.hasPermission('iot:device:query')")
+ public CommonResult getDeviceCount(@RequestParam("productId") Long productId) {
+ return success(deviceService.getDeviceCountByProductId(productId));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获取设备的精简信息列表", description = "主要用于前端的下拉选项")
+ @Parameter(name = "deviceType", description = "设备类型", example = "1")
+ public CommonResult> getSimpleDeviceList(
+ @RequestParam(value = "deviceType", required = false) Integer deviceType) {
+ List list = deviceService.getDeviceListByDeviceType(deviceType);
+ return success(convertList(list, device -> // 只返回 id、name 字段
+ new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())));
+ }
+
+ @PostMapping("/import")
+ @Operation(summary = "导入设备")
+ @PreAuthorize("@ss.hasPermission('iot:device:import')")
+ public CommonResult importDevice(
+ @RequestParam("file") MultipartFile file,
+ @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport)
+ throws Exception {
+ List list = ExcelUtils.read(file, IotDeviceImportExcelVO.class);
+ return success(deviceService.importDevice(list, updateSupport));
+ }
+
+ @GetMapping("/get-import-template")
+ @Operation(summary = "获得导入设备模板")
+ public void importTemplate(HttpServletResponse response) throws IOException {
+ // 手动创建导出 demo
+ List list = Arrays.asList(
+ IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110")
+ .productKey("1de24640dfe").groupNames("灰度分组,生产分组").build(),
+ IotDeviceImportExcelVO.builder().deviceName("biubiu")
+ .productKey("YzvHxd4r67sT4s2B").groupNames("").build());
+ // 输出
+ ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list);
+ }
+
+ @PostMapping("/upstream")
+ @Operation(summary = "设备上行", description = "可用于设备模拟")
+ @PreAuthorize("@ss.hasPermission('iot:device:upstream')")
+ public CommonResult upstreamDevice(@Valid @RequestBody IotDeviceUpstreamReqVO upstreamReqVO) {
+ deviceUpstreamService.upstreamDevice(upstreamReqVO);
+ return success(true);
+ }
+
+ @PostMapping("/downstream")
+ @Operation(summary = "设备下行", description = "可用于设备模拟")
+ @PreAuthorize("@ss.hasPermission('iot:device:downstream')")
+ public CommonResult downstreamDevice(@Valid @RequestBody IotDeviceDownstreamReqVO downstreamReqVO) {
+ deviceDownstreamService.downstreamDevice(downstreamReqVO);
+ return success(true);
+ }
+
+ // TODO @haohao:是不是默认详情接口,不返回 secret,然后这个接口,用于统一返回。然后接口名可以更通用一点。
+ @GetMapping("/mqtt-connection-params")
+ @Operation(summary = "获取 MQTT 连接参数")
+ @PreAuthorize("@ss.hasPermission('iot:device:mqtt-connection-params')")
+ public CommonResult getMqttConnectionParams(@RequestParam("deviceId") Long deviceId) {
+ return success(deviceService.getMqttConnectionParams(deviceId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java
new file mode 100644
index 000000000..d19cf7fc9
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java
@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.group.IotDeviceGroupPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceGroupService;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - IoT 设备分组")
+@RestController
+@RequestMapping("/iot/device-group")
+@Validated
+public class IotDeviceGroupController {
+
+ @Resource
+ private IotDeviceGroupService deviceGroupService;
+ @Resource
+ private IotDeviceService deviceService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建设备分组")
+ @PreAuthorize("@ss.hasPermission('iot:device-group:create')")
+ public CommonResult createDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO createReqVO) {
+ return success(deviceGroupService.createDeviceGroup(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新设备分组")
+ @PreAuthorize("@ss.hasPermission('iot:device-group:update')")
+ public CommonResult updateDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO updateReqVO) {
+ deviceGroupService.updateDeviceGroup(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除设备分组")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('iot:device-group:delete')")
+ public CommonResult deleteDeviceGroup(@RequestParam("id") Long id) {
+ deviceGroupService.deleteDeviceGroup(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得设备分组")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('iot:device-group:query')")
+ public CommonResult getDeviceGroup(@RequestParam("id") Long id) {
+ IotDeviceGroupDO deviceGroup = deviceGroupService.getDeviceGroup(id);
+ return success(BeanUtils.toBean(deviceGroup, IotDeviceGroupRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得设备分组分页")
+ @PreAuthorize("@ss.hasPermission('iot:device-group:query')")
+ public CommonResult> getDeviceGroupPage(@Valid IotDeviceGroupPageReqVO pageReqVO) {
+ PageResult pageResult = deviceGroupService.getDeviceGroupPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, IotDeviceGroupRespVO.class,
+ group -> group.setDeviceCount(deviceService.getDeviceCountByGroupId(group.getId()))));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获取设备分组的精简信息列表", description = "只包含被开启的分组,主要用于前端的下拉选项")
+ public CommonResult> getSimpleDeviceGroupList() {
+ List list = deviceGroupService.getDeviceGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
+ return success(convertList(list, group -> // 只返回 id、name 字段
+ new IotDeviceGroupRespVO().setId(group.getId()).setName(group.getName())));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceLogController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceLogController.java
new file mode 100644
index 000000000..81d1bff94
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceLogController.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.data.IotDeviceLogPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDeviceLogRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
+import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT 设备日志")
+@RestController
+@RequestMapping("/iot/device/log")
+@Validated
+public class IotDeviceLogController {
+
+ @Resource
+ private IotDeviceLogService deviceLogService;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得设备日志分页")
+ @PreAuthorize("@ss.hasPermission('iot:device:log-query')")
+ public CommonResult> getDeviceLogPage(@Valid IotDeviceLogPageReqVO pageReqVO) {
+ PageResult pageResult = deviceLogService.getDeviceLogPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, IotDeviceLogRespVO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDevicePropertyController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDevicePropertyController.java
new file mode 100644
index 000000000..47bf325dd
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDevicePropertyController.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDevicePropertyHistoryPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDevicePropertyRespVO;
+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.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
+import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
+import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - IoT 设备属性")
+@RestController
+@RequestMapping("/iot/device/property")
+@Validated
+public class IotDevicePropertyController {
+
+ @Resource
+ private IotDevicePropertyService devicePropertyService;
+ @Resource
+ private IotThingModelService thingModelService;
+ @Resource
+ private IotDeviceService deviceService;
+
+ @GetMapping("/latest")
+ @Operation(summary = "获取设备属性最新属性")
+ @Parameters({
+ @Parameter(name = "deviceId", description = "设备编号", required = true),
+ @Parameter(name = "identifier", description = "标识符"),
+ @Parameter(name = "name", description = "名称")
+ })
+ @PreAuthorize("@ss.hasPermission('iot:device:property-query')")
+ public CommonResult> getLatestDeviceProperties(
+ @RequestParam("deviceId") Long deviceId,
+ @RequestParam(value = "identifier", required = false) String identifier,
+ @RequestParam(value = "name", required = false) String name) {
+ Map properties = devicePropertyService.getLatestDeviceProperties(deviceId);
+
+ // 拼接数据
+ IotDeviceDO device = deviceService.getDevice(deviceId);
+ Assert.notNull(device, "设备不存在");
+ List thingModels = thingModelService.getThingModelListByProductId(device.getProductId());
+ return success(convertList(properties.entrySet(), entry -> {
+ IotThingModelDO thingModel = CollUtil.findOne(thingModels,
+ item -> item.getIdentifier().equals(entry.getKey()));
+ if (thingModel == null || thingModel.getProperty() == null) {
+ return null;
+ }
+ if (StrUtil.isNotEmpty(identifier) && !StrUtil.contains(thingModel.getIdentifier(), identifier)) {
+ return null;
+ }
+ if (StrUtil.isNotEmpty(name) && !StrUtil.contains(thingModel.getName(), name)) {
+ return null;
+ }
+ // 构建对象
+ IotDevicePropertyDO property = entry.getValue();
+ return new IotDevicePropertyRespVO().setProperty(thingModel.getProperty())
+ .setValue(property.getValue()).setUpdateTime(LocalDateTimeUtil.toEpochMilli(property.getUpdateTime()));
+ }));
+ }
+
+ @GetMapping("/history-page")
+ @Operation(summary = "获取设备属性历史数据")
+ @PreAuthorize("@ss.hasPermission('iot:device:property-query')")
+ public CommonResult> getHistoryDevicePropertyPage(
+ @Valid IotDevicePropertyHistoryPageReqVO pageReqVO) {
+ Assert.notEmpty(pageReqVO.getIdentifier(), "标识符不能为空");
+ return success(devicePropertyService.getHistoryDevicePropertyPage(pageReqVO));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceDownstreamReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceDownstreamReqVO.java
new file mode 100644
index 000000000..eefaeffeb
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceDownstreamReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.control;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备下行 Request VO") // 服务调用、属性设置、属性获取等
+@Data
+public class IotDeviceDownstreamReqVO {
+
+ @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
+ @NotNull(message = "设备编号不能为空")
+ private Long id;
+
+ @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
+ @NotEmpty(message = "消息类型不能为空")
+ @InEnum(IotDeviceMessageTypeEnum.class)
+ private String type;
+
+ @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
+ @NotEmpty(message = "标识符不能为空")
+ private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举类
+
+ @Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Object data; // 例如说:服务调用的 params、属性设置的 properties
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceUpstreamReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceUpstreamReqVO.java
new file mode 100644
index 000000000..778d75bba
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceUpstreamReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.control;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备上行 Request VO") // 属性上报、事件上报、状态变更等
+@Data
+public class IotDeviceUpstreamReqVO {
+
+ @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
+ @NotNull(message = "设备编号不能为空")
+ private Long id;
+
+ @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
+ @NotEmpty(message = "消息类型不能为空")
+ @InEnum(IotDeviceMessageTypeEnum.class)
+ private String type;
+
+ @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
+ @NotEmpty(message = "标识符不能为空")
+ private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举类
+
+ @Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Object data; // 例如说:属性上报的 properties、事件上报的 params
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogPageReqVO.java
new file mode 100644
index 000000000..fcf36994f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogPageReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备日志分页查询 Request VO")
+@Data
+public class IotDeviceLogPageReqVO extends PageParam {
+
+ @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
+ @NotEmpty(message = "设备标识不能为空")
+ private String deviceKey;
+
+ @Schema(description = "消息类型", example = "property")
+ private String type; // 参见 IotDeviceMessageTypeEnum 枚举,精准匹配
+
+ @Schema(description = "标识符", example = "temperature")
+ private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举,模糊匹配
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogRespVO.java
new file mode 100644
index 000000000..6e6639ede
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogRespVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT 设备日志 Response VO")
+@Data
+public class IotDeviceLogRespVO {
+
+ @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private String id;
+
+ @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123")
+ private String productKey;
+
+ @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
+ private String deviceKey;
+
+ @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
+ private String type;
+
+ @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
+ private String identifier;
+
+ @Schema(description = "日志内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String content;
+
+ @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime reportTime;
+
+ @Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime ts;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyHistoryPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyHistoryPageReqVO.java
new file mode 100644
index 000000000..0de45e4a7
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyHistoryPageReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 设备属性历史分页 Request VO")
+@Data
+public class IotDevicePropertyHistoryPageReqVO extends PageParam {
+
+ @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
+ @NotNull(message = "设备编号不能为空")
+ private Long deviceId;
+
+ @Schema(description = "设备 Key", hidden = true)
+ private String deviceKey; // 非前端传递,后端自己查询设置
+
+ @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "属性标识符不能为空")
+ private String identifier;
+
+ @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @Size(min = 2, max = 2, message = "请选择时间范围")
+ private LocalDateTime[] times;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyRespVO.java
new file mode 100644
index 000000000..dd7a0d6ad
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyRespVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
+
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备属性 Response VO")
+@Data
+public class IotDevicePropertyRespVO {
+
+ @Schema(description = "属性定义", requiredMode = Schema.RequiredMode.REQUIRED)
+ private ThingModelProperty property;
+
+ @Schema(description = "最新值", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Object value;
+
+ @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Long updateTime; // 由于从 TDengine 查询出来的是 Long 类型,所以这里也使用 Long 类型
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java
new file mode 100644
index 000000000..710e74263
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * 设备 Excel 导入 VO
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Accessors(chain = false) // 设置 chain = false,避免设备导入有问题
+public class IotDeviceImportExcelVO {
+
+ @ExcelProperty("设备名称")
+ @NotEmpty(message = "设备名称不能为空")
+ private String deviceName;
+
+ @ExcelProperty("父设备名称")
+ @Schema(description = "父设备名称", example = "网关001")
+ private String parentDeviceName;
+
+ @ExcelProperty("产品标识")
+ @NotEmpty(message = "产品标识不能为空")
+ private String productKey;
+
+ @ExcelProperty("设备分组")
+ private String groupNames;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java
new file mode 100644
index 000000000..bf52b123f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - IoT 设备导入 Response VO")
+@Data
+@Builder
+public class IotDeviceImportRespVO {
+
+ @Schema(description = "创建成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List createDeviceNames;
+
+ @Schema(description = "更新成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List updateDeviceNames;
+
+ @Schema(description = "导入失败的设备集合,key为设备名称,value为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Map failureDeviceNames;
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceMqttConnectionParamsRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceMqttConnectionParamsRespVO.java
new file mode 100644
index 000000000..5ce68c0fe
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceMqttConnectionParamsRespVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备 MQTT 连接参数 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotDeviceMqttConnectionParamsRespVO {
+
+ @Schema(description = "MQTT 客户端 ID", example = "24602")
+ @ExcelProperty("MQTT 客户端 ID")
+ private String mqttClientId;
+
+ @Schema(description = "MQTT 用户名", example = "芋艿")
+ @ExcelProperty("MQTT 用户名")
+ private String mqttUsername;
+
+ @Schema(description = "MQTT 密码")
+ @ExcelProperty("MQTT 密码")
+ private String mqttPassword;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java
new file mode 100644
index 000000000..686267732
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备分页 Request VO")
+@Data
+public class IotDevicePageReqVO extends PageParam {
+
+ @Schema(description = "设备名称", example = "王五")
+ private String deviceName;
+
+ @Schema(description = "备注名称", example = "张三")
+ private String nickname;
+
+ @Schema(description = "产品编号", example = "26202")
+ private Long productId;
+
+ @Schema(description = "设备类型", example = "1")
+ @InEnum(IotProductDeviceTypeEnum.class)
+ private Integer deviceType;
+
+ @Schema(description = "设备状态", example = "1")
+ @InEnum(IotDeviceStateEnum.class)
+ private Integer status;
+
+ @Schema(description = "设备分组编号", example = "1024")
+ private Long groupId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
new file mode 100644
index 000000000..8404ca922
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+import static cn.iocoder.yudao.module.iot.enums.DictTypeConstants.DEVICE_STATE;
+
+@Schema(description = "管理后台 - IoT 设备 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotDeviceRespVO {
+
+ @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
+ private Long id;
+
+ @Schema(description = "设备唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("设备唯一标识符")
+ private String deviceKey;
+
+ @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+ @ExcelProperty("设备名称")
+ private String deviceName;
+
+ @Schema(description = "设备备注名称", example = "张三")
+ @ExcelProperty("设备备注名称")
+ private String nickname;
+
+ @Schema(description = "设备序列号", example = "1024")
+ @ExcelProperty("设备序列号")
+ private String serialNumber;
+
+ @Schema(description = "设备图片", example = "我是一名码农")
+ @ExcelProperty("设备图片")
+ private String picUrl;
+
+ @Schema(description = "设备分组编号数组", example = "1,2")
+ private Set groupIds;
+
+ @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
+ @ExcelProperty("产品编号")
+ private Long productId;
+
+ @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("产品 Key")
+ private String productKey;
+
+ @Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty("设备类型")
+ private Integer deviceType;
+
+ @Schema(description = "网关设备 ID", example = "16380")
+ private Long gatewayId;
+
+ @Schema(description = "设备状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty(value = "设备状态", converter = DictConvert.class)
+ @DictFormat(DEVICE_STATE)
+ private Integer state;
+
+ @Schema(description = "最后上线时间")
+ @ExcelProperty("最后上线时间")
+ private LocalDateTime onlineTime;
+
+ @Schema(description = "最后离线时间")
+ @ExcelProperty("最后离线时间")
+ private LocalDateTime offlineTime;
+
+ @Schema(description = "设备激活时间")
+ @ExcelProperty("设备激活时间")
+ private LocalDateTime activeTime;
+
+ @Schema(description = "设备密钥,用于设备认证")
+ @ExcelProperty("设备密钥")
+ private String deviceSecret;
+
+ @Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
+ @ExcelProperty("认证类型(如一机一密、动态注册)")
+ private String authType;
+
+ @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}")
+ private String config;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java
new file mode 100644
index 000000000..b9ea9b99f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.Set;
+
+@Schema(description = "管理后台 - IoT 设备新增/修改 Request VO")
+@Data
+public class IotDeviceSaveReqVO {
+
+ @Schema(description = "设备编号", example = "177")
+ private Long id;
+
+ @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
+ @Size(max = 50, message = "设备编号长度不能超过 50 个字符")
+ private String deviceKey;
+
+ @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.AUTO, example = "王五")
+ private String deviceName;
+
+ @Schema(description = "备注名称", example = "张三")
+ private String nickname;
+
+ @Schema(description = "设备序列号", example = "123456")
+ private String serialNumber;
+
+ @Schema(description = "设备图片", example = "https://iocoder.cn/1.png")
+ private String picUrl;
+
+ @Schema(description = "设备分组编号数组", example = "1,2")
+ private Set groupIds;
+
+ @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
+ private Long productId;
+
+ @Schema(description = "网关设备 ID", example = "16380")
+ private Long gatewayId;
+
+ @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}")
+ private String config;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUpdateGroupReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUpdateGroupReqVO.java
new file mode 100644
index 000000000..bf66fbf98
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUpdateGroupReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.Set;
+
+@Schema(description = "管理后台 - IoT 设备更新分组 Request VO")
+@Data
+public class IotDeviceUpdateGroupReqVO {
+
+ @Schema(description = "设备编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+ @NotEmpty(message = "设备编号列表不能为空")
+ private Set ids;
+
+ @Schema(description = "分组编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+ @NotEmpty(message = "分组编号列表不能为空")
+ private Set groupIds;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupPageReqVO.java
new file mode 100644
index 000000000..93b1a1ead
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupPageReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 设备分组分页 Request VO")
+@Data
+public class IotDeviceGroupPageReqVO extends PageParam {
+
+ @Schema(description = "分组名字", example = "李四")
+ private String name;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java
new file mode 100644
index 000000000..4fd541502
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT 设备分组 Response VO")
+@Data
+public class IotDeviceGroupRespVO {
+
+ @Schema(description = "分组 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3583")
+ private Long id;
+
+ @Schema(description = "分组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+ private String name;
+
+ @Schema(description = "分组状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "分组描述", example = "你说的对")
+ private String description;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "设备数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Long deviceCount;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupSaveReqVO.java
new file mode 100644
index 000000000..491cd9366
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupSaveReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备分组新增/修改 Request VO")
+@Data
+public class IotDeviceGroupSaveReqVO {
+
+ @Schema(description = "分组 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3583")
+ private Long id;
+
+ @Schema(description = "分组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+ @NotEmpty(message = "分组名字不能为空")
+ private String name;
+
+ @Schema(description = "分组状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "分组状态不能为空")
+ private Integer status;
+
+ @Schema(description = "分组描述", example = "你说的对")
+ private String description;
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaFirmwareController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaFirmwareController.java
new file mode 100644
index 000000000..6cc3918e8
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaFirmwareController.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.ota.vo.firmware.IotOtaFirmwarePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareCreateReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
+import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT OTA 固件")
+@RestController
+@RequestMapping("/iot/ota-firmware")
+@Validated
+public class IotOtaFirmwareController {
+
+ @Resource
+ private IotOtaFirmwareService otaFirmwareService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建 OTA 固件")
+ @PreAuthorize("@ss.hasPermission('iot:ota-firmware:create')")
+ public CommonResult createOtaFirmware(@Valid @RequestBody IotOtaFirmwareCreateReqVO createReqVO) {
+ return success(otaFirmwareService.createOtaFirmware(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新 OTA 固件")
+ @PreAuthorize("@ss.hasPermission('iot:ota-firmware:update')")
+ public CommonResult updateOtaFirmware(@Valid @RequestBody IotOtaFirmwareUpdateReqVO updateReqVO) {
+ otaFirmwareService.updateOtaFirmware(updateReqVO);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得 OTA 固件")
+ @PreAuthorize("@ss.hasPermission('iot:ota-firmware:query')")
+ public CommonResult getOtaFirmware(@RequestParam("id") Long id) {
+ IotOtaFirmwareDO otaFirmware = otaFirmwareService.getOtaFirmware(id);
+ return success(BeanUtils.toBean(otaFirmware, IotOtaFirmwareRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得 OTA 固件分页")
+ @PreAuthorize("@ss.hasPermission('iot:ota-firmware:query')")
+ public CommonResult> getOtaFirmwarePage(@Valid IotOtaFirmwarePageReqVO pageReqVO) {
+ PageResult pageResult = otaFirmwareService.getOtaFirmwarePage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, IotOtaFirmwareRespVO.class));
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java
new file mode 100644
index 000000000..f6bc526ac
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
+import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT OTA 升级记录")
+@RestController
+@RequestMapping("/iot/ota-upgrade-record")
+@Validated
+public class IotOtaUpgradeRecordController {
+
+ @Resource
+ private IotOtaUpgradeRecordService upgradeRecordService;
+
+ @GetMapping("/get-statistics")
+ @Operation(summary = "固件升级设备统计")
+ @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
+ @Parameter(name = "firmwareId", description = "固件编号", required = true, example = "1024")
+ public CommonResult