本文以C Link SDK中的Demo文件./demos/shadow_basic_demo.c为例,介绍如何调用Link SDK的API,展示设备影子功能。
背景信息
-
设备影子功能的更多信息,请参见概述。
- 设备影子功能基于MQTT接入,开发过程中涉及MQTT接入的代码说明,请参见MQTT接入。
步骤一:初始化
- 添加头文件。
……
……
#include "aiot_shadow_api.h"
- 配置底层依赖和日志输出。
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
aiot_state_set_logcb(demo_state_logcb);
- 调用aiot_shadow_init,创建
Shadow
客户端实例,并初始化默认参数。 shadow_handle = aiot_shadow_init();
if (shadow_handle == NULL) {
printf("aiot_shadow_init failed\n");
return -1;
}
步骤三:设备主动上报状态
设备在线时,主动上报设备状态到影子,应用程序主动获取设备影子状态。
- 设备调用aiot_shadow_send,向物联网平台上报最新状态到影子。
上报状态时,需注意:
- 上报状态消息的数据结构类型为aiot_shadow_msg_t,是
aiot_shadow_send()
的入参。
- 上报状态的消息类型为AIOT_SHADOWMSG_UPDATE。
int32_t demo_update_shadow(void *shadow_handle, char *reported_data, int64_t version)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_UPDATE;
message.data.update.reported = reported_data;
message.data.update.version = version;
return aiot_shadow_send(shadow_handle, &message);
}
- 设置上报状态消息的内容。
- 示例代码:
res = demo_update_shadow(shadow_handle, "{\"LightSwitch\":1}", 0);
if (res < 0) {
printf("demo_delete_shadow_report failed, res = -0x%04x\r\n", -res);
}
- 示例消息内容说明:
示例 |
Alink格式 |
说明 |
{
"LightSwitch": 1
}
|
{
"method": "update",
"state": {
"reported": {
"LightSwitch": 1
}
},
"version": 0
}
|
上报的消息的内容为JSON格式,是Alink格式数据中state的值。详细说明,请参见设备主动上报状态。
示例代码的消息内容为:
- 将属性
LightSwitch 设置为1 。
- 将版本号设置为
0 。
说明
- 后续操作的版本号递增, 否则物联网平台将返回错误。
- 如果将版本号设置为
-1 , 物联网平台则清空设备影子数据,并将版本号更新为0 。
|
- 物联网平台接收到设备影子的消息后,更新影子文件。然后,向设备返回应答报文。
- 设备接收应答报文后,触发回调函数
demo_shadow_recv_handler
。
您可以参考以下内容,编写回调函数的处理逻辑:
- 应答报文的数据结构类型为aiot_shadow_recv_t,是回调函数的入参。
- 应答报文消息的类型为AIOT_SHADOWRECV_GENERIC_REPLY。
- 以下是应答报文示例及其Alink数据格式:
示例 |
Alink格式 |
说明 |
{
"status":"success",
"version":0
}
|
{
"method": "reply",
"payload": {
"status": "success",
"version": 0
},
"timestamp": 1626317187
}
|
应答报文的内容是Alink数据中payload的值。 示例消息表示上报状态成功。
|
- 示例代码仅做打印处理。
void demo_shadow_recv_handler(void *handle, const aiot_shadow_recv_t *recv, void *userdata)
{
printf("demo_shadow_recv_handler, type = %d, productKey = %s, deviceName = %s\r\n",
recv->type, recv->product_key, recv->device_name);
switch (recv->type) {
case AIOT_SHADOWRECV_GENERIC_REPLY: {
const aiot_shadow_recv_generic_reply_t *generic_reply = &recv->data.generic_reply;
printf("payload = \"%.*s\", status = %s, timestamp = %ld\r\n",
generic_reply->payload_len,
generic_reply->payload,
generic_reply->status,
(unsigned long)generic_reply->timestamp);
}
……
……
default:
break;
}
}
步骤四:应用程序改变设备状态
您可以通过应用程序,或登录物联网平台,向设备影子发送期望属性,最终实现改变设备状态。
- 下发期望状态给设备影子。
- 物联网平台根据下发的期望状态消息,更新影子内容。然后,下发该影子内容至设备。
- 设备接收影子文件后,触发回调函数
demo_shadow_recv_handler
。
您可以参考以下内容,编写回调函数的处理逻辑:
- 下发的期望状态消息的数据结构类型为aiot_shadow_recv_t,是回调函数的入参。
- 期望状态的消息类型为AIOT_SHADOWRECV_CONTROL。
- 以下是期望状态消息示例及其Alink数据格式:
示例 |
Alink格式 |
说明 |
{
"state": {
"desired": {
"LightSwitch": 0
}
},
"metadata": {
"desired": {
"LightSwitch": {
"timestamp": 1626319658
}
}
}
}
|
{
"method": "control",
"payload": {
"state": {
"desired": {
"LightSwitch": 0
}
},
"metadata": {
"desired": {
"LightSwitch": {
"timestamp": 1626319658
}
}
}
},
"version": 2,
"timestamp": 1469564576
}
|
期望状态消息的的内容是Alink数据中payload的值。 示例消息为设置LightSwitch 的期望属性值为0 。
|
- 示例代码仅做打印处理。
void demo_shadow_recv_handler(void *handle, const aiot_shadow_recv_t *recv, void *userdata)
{
printf("demo_shadow_recv_handler, type = %d, productKey = %s, deviceName = %s\r\n",
recv->type, recv->product_key, recv->device_name);
switch (recv->type) {
……
……
case AIOT_SHADOWRECV_CONTROL: {
const aiot_shadow_recv_control_t *control = &recv->data.control;
printf("payload = \"%.*s\", version = %ld\r\n",
control->payload_len,
control->payload,
(unsigned long)control->version);
}
……
……
default:
break;
}
}
- 设备更新状态后,上报最新的状态至设备影子,并处理应答报文。
- 上报最新的状态后,调用aiot_shadow_send,删除期望属性。
请求删除期望属性时,需注意:
- 删除期望属性消息的数据结构类型为aiot_shadow_msg_t,是
aiot_shadow_send()
的入参。
- 消息的类型为AIOT_SHADOWMSG_CLEAN_DESIRED。
- 示例代码删除了期望属性的全部内容,并设置版本号为
1
。
int32_t demo_clean_shadow_desired(void *shadow_handle, int64_t version)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_CLEAN_DESIRED;
message.data.clean_desired.version = version;
return aiot_shadow_send(shadow_handle, &message);
}
……
……
res = demo_clean_shadow_desired(shadow_handle, 1);
if (res < 0) {
printf("demo_clean_shadow_desired failed, res = -0x%04x\r\n", -res);
}
- 删除属性的请求发送后,物联网平台返回应答报文,触发回调函数
demo_shadow_recv_handler
。
步骤五:设备主动获取设备影子内容
若应用程序发送指令时,设备离线。设备再次上线后,将主动获取设备影子内容。
- 设备调用aiot_shadow_send,向物联网平台发送查询指令,获取设备影子内容。
查询指令的消息类型为AIOT_SHADOWMSG_GET。int32_t demo_get_shadow(void *shadow_handle)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_GET;
return aiot_shadow_send(shadow_handle, &message);
}
……
……
res = demo_get_shadow(shadow_handle);
if (res < 0) {
printf("demo_get_shadow failed, res = -0x%04x\r\n", -res);
}
- 物联网平台收到查询指令后,返回查询结果。设备接收返回结果后,触发回调函数
demo_shadow_recv_handler
。您可以参考以下内容,编写回调函数的处理逻辑:
- 回调函数处理消息的数据结构类型为aiot_shadow_recv_t,是回调函数的入参。
- 消息的类型为AIOT_SHADOWRECV_GET_REPLY。
- 以下是查询后返回的消息示例及其Alink数据格式:
示例 |
Alink格式 |
说明 |
{
"status": "success",
"state": {
"reported": {
}
},
"metadata": {
"reported": {
}
}
}
|
{
"method": "reply",
"payload": {
"status": "success",
"state": {
"reported": {
}
},
"metadata": {
"reported": {
}
}
},
"version": 5,
"timestamp": 1626320690
}
|
获取属性消息的内容是Alink数据中payload的值。 示例消息表示请求发送成功,获取的消息值未空,未上报过消息。
|
- 示例代码仅做打印处理。
void demo_shadow_recv_handler(void *handle, const aiot_shadow_recv_t *recv, void *userdata)
{
printf("demo_shadow_recv_handler, type = %d, productKey = %s, deviceName = %s\r\n",
recv->type, recv->product_key, recv->device_name);
switch (recv->type) {
……
……
case AIOT_SHADOWRECV_GET_REPLY: {
const aiot_shadow_recv_get_reply_t *get_reply = &recv->data.get_reply;
printf("payload = \"%.*s\", version = %ld\r\n",
get_reply->payload_len,
get_reply->payload,
(unsigned long)get_reply->version);
}
default:
break;
}
}
步骤六:设备主动删除影子属性
若设备端已经是最新状态,设备端可以主动发送指令,删除设备影子中保存的某条属性状态。
- 设备调用aiot_shadow_send,向物联网平台发送删除指令,删除设备影子中的指定属性。
发送删除指令时,需注意:
- 删除指令的数据结构类型为aiot_shadow_msg_t,是
aiot_shadow_send()
的入参。
- 删除指令的消息类型为AIOT_SHADOWMSG_DELETE_REPORTED。
int32_t demo_delete_shadow_report(void *shadow_handle, char *reported, int64_t version)
{
aiot_shadow_msg_t message;
memset(&message, 0, sizeof(aiot_shadow_msg_t));
message.type = AIOT_SHADOWMSG_DELETE_REPORTED;
message.data.delete_reporte.reported = reported;
message.data.delete_reporte.version = version;
return aiot_shadow_send(shadow_handle, &message);
}
- 设置删除指令的内容。
- 示例代码:
res = demo_delete_shadow_report(shadow_handle, "{\"LightSwitch\":\"null\"}", 2);
if (res < 0) {
printf("demo_delete_shadow_report failed, res = -0x%04x\r\n", -res);
}
- 示例消息内容说明:
示例 |
Alink格式 |
说明 |
"{\"LightSwitch\":\"null\"}", 2
|
{
"method": "delete",
"state": {
"reported": {
"LightSwitch": "null",
}
},
"version": 2
}
|
删除指令消息的内容为JSON格式,是Alink格式数据中state的值。详细说明,请参见设备主动删除影子属性。
示例代码的消息内容为:
- 将属性
LightSwitch 设置为null ,表示清除设备影子所有数据。
- 将版本号设置为
2 。
|
- 物联网平台收到删除指令后,返回应答报文。设备接收报文后,触发回调函数
demo_shadow_recv_handler
。
步骤七:退出程序
调用aiot_shadow_deinit,销毁Shadow
客户端实例,释放资源。
res = aiot_shadow_deinit(&shadow_handle);
if (res < STATE_SUCCESS) {
printf("aiot_shadow_deinit failed: -0x%04X\n", -res);
return -1;
}
后续步骤
- 例程文件配置完成后,需进行编译,生成可执行文件./output/shadow-basic-demo。
注意
- 配置Demo文件时,请根据测试场景,取消上报状态、获取属性、删除期望或上报属性的相关代码两边的注释符号(
/*
和*/
),并根据场景更换版本号。
- 更新版本号时,需注意:
- 后续操作的版本号递增, 否则物联网平台将返回错误。
- 如果将版本号设置为
-1
, 物联网平台则清空设备影子数据,并将版本号更新为0
。
-
关于运行结果的详细说明,请参见运行日志。