订阅和解析ONENET平台设备属性设置下行数据
要在 OneNet 平台实现完整的设备控制,核心是搭建「下行指令解析(平台→设备)+ 上行属性上报(设备→平台)+ MQTT 主题订阅(确保消息互通)+ 响应回执(确保消息可靠)」的完整闭环。
一、订阅 OneNet MQTT 协议的核心主题(必须订阅)
OneNet 平台基于 MQTT 协议通信,所有设备控制和数据交互都依赖于指定主题的订阅与发布,你必须提前订阅对应主题,否则无法实现消息互通。
(1)订阅属性设置主题:
接收OneNet下发的设备属性控制指令(平台→设备)
主题:$sys/{pid}/{device-name}/thing/property/set
参数填写: id 为下行数据的 id 一致,code 为 200 代表成功,msg 可以自定义。
为何对于这个主题:设备需订阅这个主题,才能接收平台下发的下行控制指令(如开灯、打开蜂鸣器),执行完成后需反馈响应平台。
(2)订阅属性上报的响应主题:
接收OneNet对设备属性上报的响应 (设备→平台→平台返回是否收到数据)
主题: $sys/{pid}/{device-name}/thing/property/post/reply
参数填写: id 为下行数据的 id 一致,code 为 200 代表成功,msg 可以自定义。
为何对于这个主题:设备完成平台下发的控制指令(如开灯)并反馈响应平台,需上报此时最新状态至平台更新同步;通过订阅对应主题接收平台响应,确认状态同步成功。
(3)订阅主题代码示例
//订阅OneNet平台的系统主题
void onenet_subscribe(void)
{
char topic[128]; // MQTT主题缓冲区
// 订阅属性上报的回复主题:接收OneNet对设备属性上报的响应
snprintf(topic, 128, "$sys/%s/%s/thing/property/post/reply", ONENET_PRODUCT_ID, ONENET_DEVICE_NAME);
// 订阅该主题(QoS=1,确保消息至少送达一次)
esp_mqtt_client_subscribe_single(mqtt_handle, topic, 1); //(参数1:MQTT客户端句柄;参数2:待订阅主题;参数3:QoS等级1)
// 订阅属性设置主题:接收OneNet下发的设备属性控制指令
snprintf(topic, 128, "$sys/%s/%s/thing/property/set", ONENET_PRODUCT_ID, ONENET_DEVICE_NAME);
// 订阅该主题(QoS=1)
esp_mqtt_client_subscribe_single(mqtt_handle, topic, 1);
}
二、解析设备属性设置下行数据(平台→设备)
完整流程
- MQTT 消息回调,筛选下行属性设置主题:
编写 onenet_property_handle_dm() 解析并执行 OneNET 平台向设备下发的「属性设置指令」,设备收到 MQTT 消息后,先判断消息是否来自「属性设置下行主题」,只有匹配才进行解析。 - 调用你的解析函数,处理属性指令:
将消息 payload 转换为 cJSON 对象,传入你现有的解析函数,处理如亮度、RGB、蜂鸣器、风扇等属性。 - 生成执行结果回执,准备发布到响应主题:
解析并执行完成后,构建回执 JSON,告知平台指令执行结果。
关键代码(MQTT 消息回调 + 解析对接)
// 解析并执行 OneNET 平台向设备下发的「属性设置指令」
onenet_property_handle_dm(property);
//响应(执行结果回执发布到响应主题)
// 从JSON对象property中,根据字段名"id"获取对应的cJSON节点句柄
cJSON* id_js = cJSON_GetObjectItem(property,"id");
// 对 OneNET 下行指令的「ACK 回执响应」
if (id_js != NULL && cJSON_IsString(id_js))
{
// 向平台返回ACK响应(200=处理成功,描述信息"success")
// 从JSON对象中获取"id"字段句柄,再提取id字符串值传入ACK函数(保证与平台下发的id一致)
onenet_property_ack(cJSON_GetStringValue(id_js),200,"success"); //响应 onenet 平台
} else {
ESP_LOGE(TAG, "Property set message missing valid 'id' field");
}
设备属性设置请求和响应 JSON 格式
按照onenet平台下发 设备属性设置请求 的 json 格式来解析,并执行设置指令(操控硬件)
按照onenet平台给的 设备属性设置响应 的 json 格式来编写响应内容,响应平台执行完成
下行指令(OneJSON): 云端先向设备发送 property/set(下行指令)
请求Topic: $sys/{pid}/{device-name}/thing/property/set
OneJSON数据格式:
{
"id": "123",
"version": "1.0",
"params": {
"temperature":"30.5"
}
}
请求参数描述
参数 类型 描述
id String 消息id号,用户自定义,String类型的数字,长度限制不超过13位。
version String 物模型版本号,可选字段,不填默认为1.0。
params JsonObject 属性设置参数。如以上示例中,设置属性:{"temperature":"30.5" }
—————————————————————————————————————————————————————————————————————————————————————————
上行响应(OneJSON): 设备处理完成后,向云端返回 property/set_reply(上行响应)
响应Topic: $sys/{pid}/{device-name}/thing/property/set_reply
OneJSON数据格式:
{
"id":"123",
"code":200,
"msg":"xxxx"
}
响应参数描述
参数 类型 描述
id String 消息id号,与平台下发一致,String类型的数字,长度限制不超过13位。
code Integer 结果状态码
msg String 错误信息
注意:OneJSON下发 设备属性设置的 JSON 格式(扁平格式 或 标准格式)
在实际项目中,你无法完全控制平台下发的格式,原因如下:
- 平台升级风险:如果你的设备只兼容扁平格式,当平台升级到新版并强制下发标准格式时,设备会解析失败,导致控制中断。
- 多场景适配:你的设备可能同时被「调试工具」和「规则引擎」调用,前者下发扁平格式,后者下发标准格式。
- 第三方集成:如果设备需要对接多个平台或第三方系统,不同系统的格式可能不一致,兼容两种格式能避免对接失败。
- 扁平格式示例
{
"id": "12345",
"version": "1.0",
"params": {
"Brightness": {
"value": 80
},
"Buzzer": {
"value": true
},
"Fan": {
"value": false
},
"RGBColor": {
"value": {
"Red": 255,
"Green": 0,
"Blue": 0
}
}
}
}
- 标准格式(带
value层级)示例
{
"id": "12345",
"version": "1.0",
"params": {
"Brightness": 80,
"Buzzer": true,
"Fan": false,
"RGBColor": {
"Red": 255,
"Green": 0,
"Blue": 0
}
}
}
同时兼容两种格式,这里我通过 get_effective_node() 实现了兼容,应对格式的不确定性
实现兼容格式是否带 value 层级,最终获取最终有效节点都可以直接取值
// 核心工具函数:兼容带/不带 value 层级,获取最终有效节点
static cJSON* get_effective_node(cJSON* src_node, const char* key)
{
// 步骤 1:优先尝试提取 value 子节点(兼容带 value 格式)
cJSON* effective_node = cJSON_GetObjectItem(src_node, key);
// 步骤 2:若 value 提取失败(NULL),直接使用源节点(兼容不带 value 格式)
if (effective_node == NULL)
{
effective_node = src_node;
}
// 步骤 3:返回最终有效节点(可能是 value 子节点,也可能是源节点)
return effective_node;
}
三、设备属性上报(设备→平台)
设备需要主动向平台上报当前属性状态(如亮度、RGB 颜色、蜂鸣器开关),确保平台显示的状态与设备实际状态一致,核心是「构建标准上报 JSON → 发布到上报主题」。
完整流程
- 构建上报 JSON 数据:
编写 onenet_property_upload_dm() 函数,
生成符合 OneNet 标准的属性 JSON(带 value 层级,格式统一)。cJSON* onenet_property_upload_dm() { // 1. 创建JSON根节点对象(最外层{}),所有数据均嵌套在该根对象内 cJSON* root = cJSON_CreateObject(); // root :根节点 // 2. 向根节点添加 字符串类型字段"id",值为"123"(设备/请求唯一标识,可自定义为动态ID) cJSON_AddStringToObject(root,"id","123"); // 向 root 对象中 添加 键值对(字符串类型) cJSON_AddStringToObject(root,"version","1.0"); // 向 root 对象中 添加 键值对(字符串类型) // 3. 向根节点添加 嵌套对象字段"params"(核心属性集合),并保存该对象句柄用于后续操作 cJSON* params_js = cJSON_AddObjectToObject(root,"params"); // 向 root 对象中 添加对象 params // 向对象 params 添加子节点 // 灯亮度 cJSON* brightness_js = cJSON_AddObjectToObject(params_js,"Brightness"); // 向 params_js 对象中 添加对象 Brightness cJSON_AddNumberToObject(brightness_js,"value",brightness); // 向 brightness_js 对象中 添加键值对(整数类型) // RGB颜色 cJSON* color_js = cJSON_AddObjectToObject(params_js,"RGBColor"); // 向 params_js 对象中 添加对象 RGBColor cJSON* color_value_js = cJSON_AddObjectToObject(color_js,"value"); // 向 color_js 对象中 添加对象 value cJSON_AddNumberToObject(color_value_js,"Red",rgb_red); // 向 color_value_js 对象中 添加键值对(整数类型) cJSON_AddNumberToObject(color_value_js,"Green",rgb_green); // 向 color_value_js 对象中 添加键值对(整数类型) cJSON_AddNumberToObject(color_value_js,"Blue",rgb_blue); // 向 color_value_js 对象中 添加键值对(整数类型) cJSON* Buzzer_js = cJSON_AddObjectToObject(params_js,"Buzzer"); // 向 params_js 对象中 添加对象 Buzzer cJSON_AddBoolToObject(Buzzer_js,"value",buzzer_value); // 向 Buzzer_js 对象中 添加键值对(整数类型) cJSON* Fan_js = cJSON_AddObjectToObject(params_js,"Fan"); // 向 params_js 对象中 添加对象 Fan cJSON_AddBoolToObject(Fan_js,"value",fan_value); // 向 Fan_js 对象中 添加键值对(整数类型) // 返回构建完成的JSON根对象句柄,供调用者转换为字符串并上报OneNet平台 return root; } - 转换 JSON 为字符串,发布到上报主题:
将构建好的 JSON 转换为字符串,通过 MQTT 发布到「属性上报主题」。
- 接收平台上报响应,确认上报成功:
通过订阅的「上报响应主题」,确认平台是否成功接收数据。
关键代码(上报函数调用 + MQTT 发布)
// 设备向 OneNET 平台的属性主动上报(更新实时数据)
// 1. 构建设备属性上报的JSON对象(包含灯光开关、亮度、RGB等状态)
cJSON* property_js1 = onenet_property_upload_dm();
// 2. 将cJSON对象转为无格式压缩JSON字符串(减小传输数据量,适合网络上报)
char* data1 = cJSON_PrintUnformatted(property_js1);
// 3. 发布JSON字符串到OneNet平台,完成设备属性最新上报(上行)
onenet_post_property_data(data1);
// 4. 释放cJSON动态分配的字符串内存,避免内存泄漏
cJSON_free(data1);
// 5. 释放cJSON根对象内存(递归释放所有嵌套节点)
cJSON_Delete(property_js1);
四、订阅与响应的核心价值(为什么必须做)
这2个订阅(订阅设置下行数据 和 订阅上报成功响应),是确保整个通信闭环可靠、可追溯的关键,缺一不可:
- 订阅下行数据:是「设备接收平台控制指令」的唯一途径,不订阅就无法收到平台的控制指令,设备无法被远程控制;
- 发布 / 订阅上行响应:
- 对「属性设置」:设备返回回执给平台,平台能标记指令「执行成功 / 失败」,方便问题排查(如指令下发后设备无响应,可通过回执判断是设备未接收还是执行失败);
- 对「属性上报」:设备接收平台的上报回执,可确认数据是否成功上传,若上传失败可重试,避免平台显示旧状态。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)