物联网平台提供设备影子功能, 用于缓存设备上报的状态和云端下发的指令。设备上线后, 可基于MQTT通信, 主动拉取离线期间, 云端曾下发过的指令。本文将介绍如何完成设备影子功能的开发。
背景说明

如上图流程所示, 使用C-SDK中的shadow功能, 在建立了MQTT长连接之后, 设备可将对设备影子的操作上报到云端, 并接收云端下行的回应及主动更新。
- 设备调用
aiot_shadow_send
向云端上报消息太过频繁时, 会被云端限流, 云端限流发生时, 设备端不会收到任何来自网络的通知, 只能人工在控制台页面查看。 - 设备影子的JSON文件大小, JSON嵌套的层级数和JSON中的属性数量也都有一定限制。
数据结构
SDK中的shadow
功能将操作设备影子的消息内容都以结构体描述。
- 所有上行消息的传递, 都以用户调用
aiot_shadow_send()
接口, 从设备发往云平台。 - 所有上行消息的内容, 都以 aiot_shadow_msg_t 结构体存放, 它是
aiot_shadow_send()
接口的入参。 - 所有下行消息的传递, 都以用户调用
aiot_mqtt_recv()
接口, 进而触发用户注册的 aiot_shadow_recv_handler_t 回调, 到达用户侧。 - 所有下行消息的内容, 都以 aiot_shadow_recv_t 结构体存放, 它是上述回调函数的入参。
SDK中的设备影子模块不维护网络会话, 网络连接和数据收发由它所依赖MQTT模块提供。
因此, 用户在创建设备影子实例后, 应正确地为它配置MQTT实例句柄, 在进行设备影子收发前, 也应确保MQTT已经建连成功。
API列表
相关定义位于文件component/shadow/aiot_shadow_api.h中。
接口名 | 说明 |
---|---|
aiot_shadow_init | 初始化shadow会话实例。 |
aiot_shadow_setopt | 设置shadow会话参数, 点击设备影子详细设置参数了解所有参数。 |
aiot_shadow_send | 发送一条shadow消息到物联网平台, 消息类型和消息内容由msg参数决定。 |
aiot_shadow_deinit | 销毁shadow实例, 释放资源。 |
API使用概述
请按照以下流程使用API。
- 在使用设备影子模块前, 用户应首先创建好一个MQTT实例。
- 调用
aiot_shadow_init
创建一个设备影子实例, 保存实例句柄。 - 调用
aiot_shadow_setopt
配置AIOT_SHADOWOPT_MQTT_HANDLE
选项以设置MQTT句柄, 此选项为强制配置选项。 - 调用
aiot_shadow_setopt
配置AIOT_SHADOWOPT_RECV_HANDLER
和AIOT_SHADOWOPT_USER_DATA
选项以注册数据接收回调函数和用户上下文数据指针。 - 在使用
aiot_shadow_send
发送消息前, 应先完成MQTT实例的建连。 - 调用
aiot_shadow_send
发送不同类型的消息到云平台, 在注册的回调函数中处理各种类型的云平台下行消息。
shadow
模块通过调用aiot_shadow_send
发送消息到云平台, 云平台下发给设备的消息通过调用aiot_shadow_setopt
注册的回调函数接收。
下面为您介绍如何通过API进行收发操作。
- 更新设备影子中的reported值
以下代码演示了如何用
aiot_shadow_send
发送更新设备影子请求, 其中message的reported
参数为包含键值对的JSON对象字符串, 如例程中的"{\"LightSwitch\":1}"
。说明version
为更新后的设备影子版本号, 更新后的version必须大于当前的version, 否则云端将返回错误。- 如果version设置为
-1
, 表示清空设备影子数据, 设备影子会接收设备端的请求, 并将设备影子版本更新为0
。
/* 发送更新设备影子reported值的请求 */ 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); } /* 将reported中的LigthSwitch值更新为1 */ demo_update_shadow(shadow_handle, "{\"LightSwitch\":1}", 0);
在成功上报请求后, 设备将收到云端下发的应答消息, 消息类型为
AIOT_SHADOWRECV_GENERIC_REPLY
。若
status
字符串为success
则更新成功, 否则更新失败, 用户可以在payload
中查看到错误信息。void demo_shadow_recv_handler(void *shadow_handle, const aiot_shadow_recv_t *recv, void *userdata) { 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); } ... } }
- 删除设备影子中的reported值
以下代码演示了如何用
aiot_shadow_send
发送删除设备影子reported值的请求, 其中message的reported
参数为包含要删除属性的JSON对象字符串。删除reported值时, 被删除属性对应的值必须指定为
null
, 如例程中的"{\"LightSwitch\":\"null\"}"
。version
为更新后的设备影子版本号。说明 若要清除所有的reported属性, 则reported
参数填写"\"null\""
字符串。/* 发送删除设备影子中特定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); } /* 清除reported中的LigthSwitch值 */ demo_delete_shadow_report(shadow_handle, "{\"LightSwitch\":\"null\"}", 2);
在成功上报请求后, 设备将收到云端下发的应答消息, 消息类型为
AIOT_SHADOWRECV_GENERIC_REPLY
, 若status
字符串为success
则删除成功, 否则删除失败。... /* 云端返回的通用应答 */ 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); } ... }
- 清除设备影子中的desired值
以下代码演示了如何用
aiot_shadow_send
发送清除设备影子desired值的请求, 其中version
字段应填写清除后的设备影子版本号。/* 发送清除设备影子中所有desired值的请求 */ 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);
同样在成功上报请求后, 设备将收到云端下发的应答消息, 消息类型为
AIOT_SHADOWRECV_GENERIC_REPLY
, 若status
字符串为success
则清除成功, 否则清除失败。 - 获取设备影子
以下代码演示了如何用
aiot_shadow_send
发送获取设备影子的请求, 其中message的reported
参数为包含键值对的JSON对象字符串, 如例程中的"{\"LightSwitch\":1}"
。version
为更新后的设备影子版本号, 更新后的version必须大于当前的version, 否则云端将返回错误。说明如果version设置为
-1
, 表示清空设备影子数据, 设备影子会接收设备端的请求, 并将设备影子版本更新为0。/* 发送获取设备影子的请求 */ 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); } demo_get_shadow(shadow_handle);
在成功上报请求后, 设备将收到云端下发的应答的设备影子, 消息类型为
AIOT_SHADOWRECV_GET_REPLY
。回调函数入参的
payload
参数存放设备影子的JSON结构体字符串, 其中version
为设备影子的版本号。void demo_shadow_recv_handler(void *shadow_handle, const aiot_shadow_recv_t *recv, void *userdata) { switch (recv->type) { /* 当设备发送AIOT_SHADOWMSG_GET消息主动获取设备影子时, 会收到此消息 */ 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); } ... } }
- 云端主动下推设备影子
当设备在线时, 若用户应用程序调用云端APIUpdateDeviceShadow主动更新设备影子, 设备会直接收到此消息。
void demo_shadow_recv_handler(void *shadow_handle, const aiot_shadow_recv_t *recv, void *userdata) { switch (recv->type) { /* 当设备在线时, 若用户应用程序调用云端API主动更新设备影子, 设备便会收到此消息 */ 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); } ... } }
例程讲解
现对照demos/shadow_basic_demo.c
例程, 分步骤讲解如何使用API。

这个例程适用于Linux
这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程。
- 一个线程用于保活长连接。
- 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调。
接着演示了在MQTT连接上进行设备影子更新,删除,拉取等操作,取消这些代码段落的注释即可观察运行效果。
需要用户关注或修改的部分, 已经用 TODO
在注释中标明。
- 设置设备证书
例程使用的设备证书是公用的, 所以应用于实际业务时, 请替换如下的
TODO
部分, 传入用户自己真实的设备证书。/* TODO: 替换为自己设备的设备证书 */ char *product_key = "a18wPXXXXXX"; char *device_name = "data_model_basic_demo"; char *device_secret = "uwMTmVAMnGGHaAkqmeDY6cHxxBXXXXXX";
- 进入程序入口, 给SDK配置全局的底层依赖和日志回调
底层依赖描述了硬件平台的资源使用方式, 比如怎样获取时钟, 分配内存等, 日志回调是用户的函数, SDK有log输出的时候会进入这个函数。
int main(int argc, char *argv[]) { ... ... /* 配置SDK的底层依赖 */ aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile); /* 配置SDK的日志输出 */ aiot_state_set_logcb(demo_state_logcb);
- 给MQTT会话配置参数
这些连接参数包括如何建立TLS连接, 服务器地址在哪, 连接后的数据及事件回调函数是什么等等, 然后用
aiot_mqtt_connect()
建立连接。/* 创建1个MQTT客户端实例并内部初始化默认参数 */ mqtt_handle = aiot_mqtt_init(); if (mqtt_handle == NULL) { printf("aiot_mqtt_init failed\n"); return -1; } snprintf(host, 100, "%s.%s", product_key, url); /* 配置MQTT服务器地址 */ aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)host); /* 配置MQTT服务器端口 */ aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port); /* 配置设备productKey */ aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key); /* 配置设备deviceName */ aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name); /* 配置设备deviceSecret */ aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret); /* 配置网络连接的安全凭据, 上面已经创建好了 */ aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred); /* 配置MQTT事件回调函数 */ aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
- 创建设备影子会话实例并建立MQTT连接
设备影子会话创建后, 可通过其会话句柄组装和解析标准的shadow报文, 这些报文依赖MQTT模块发送和接收, 因此要关联设备影子的会话和MQTT的会话, 并建立MQTT连接。
/* 创建设备影子实例 */ void *shadow_handle = aiot_shadow_init(); if (shadow_handle == NULL) { printf("aiot_shadow_init failed"); return -1; } /* 配置MQTT实例句柄 */ aiot_shadow_setopt(shadow_handle, AIOT_SHADOWOPT_MQTT_HANDLE, mqtt_handle); /* 配置消息接收处理回调函数 */ aiot_shadow_setopt(shadow_handle, AIOT_SHADOWOPT_RECV_HANDLER, (void *)aiot_shadow_recv_handler); /* 与服务器建立MQTT连接 */ res = aiot_mqtt_connect(mqtt_handle);
- 数据收发
请查看API使用概述部分。
- 观察日志
以上各种和云端的交互代码都被注释掉, 用户取消注释后, 重新编译运行例程, 即可看到对设备影子的操作过程。
[1582892115.987][LK-0313] MQTT user calls aiot_mqtt_connect api, connect [1582892115.987][LK-0317] aiot_shadow_test_case_01&a18wPXXXXXX [1582892115.987][LK-0318] 971C83F3DEEBED04EC9FD01FAD993002ACA6FDFA14D55B96B8FBE86FFEF06B42 [1582892115.987][LK-0319] a18wPXXXXXX.aiot_shadow_test_case_01|timestamp=2524608000000,_ss=1,_v=sdk-c-4.0.0,securemode=2,signmethod=hmacsha256,ext=1,| establish mbedtls connection with server(host='a18wPXXXXXX.iot-as-mqtt.cn-shanghai.aliyuncs.com', port=[1883]) success to establish mbedtls connection, fd = 6(cost 44763 bytes in total, max used 47675 bytes) [1582892116.190][LK-0313] MQTT connect success in 203 ms AIOT_MQTTEVT_CONNECT [1582892116.190][LK-0309] pub: /shadow/update/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] > 7B 22 6D 65 74 68 6F 64 22 3A 22 67 65 74 22 7D | {"method":"get"} [1582892116.241][LK-0309] pub: /shadow/get/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] < 7B 22 6D 65 74 68 6F 64 22 3A 22 72 65 70 6C 79 | {"method":"reply [LK-030A] < 22 2C 22 70 61 79 6C 6F 61 64 22 3A 7B 22 73 74 | ","payload":{"st [LK-030A] < 61 74 75 73 22 3A 22 73 75 63 63 65 73 73 22 2C | atus":"success", [LK-030A] < 22 73 74 61 74 65 22 3A 7B 22 72 65 70 6F 72 74 | "state":{"report [LK-030A] < 65 64 22 3A 7B 22 4C 69 67 68 74 53 77 69 74 63 | ed":{"LightSwitc [LK-030A] < 68 22 3A 31 7D 7D 2C 22 6D 65 74 61 64 61 74 61 | h":1}},"metadata [LK-030A] < 22 3A 7B 22 72 65 70 6F 72 74 65 64 22 3A 7B 22 | ":{"reported":{" [LK-030A] < 4C 69 67 68 74 53 77 69 74 63 68 22 3A 7B 22 74 | LightSwitch":{"t [LK-030A] < 69 6D 65 73 74 61 6D 70 22 3A 31 35 38 32 38 38 | imestamp":158288 [LK-030A] < 37 36 34 34 7D 7D 7D 7D 2C 22 74 69 6D 65 73 74 | 7644}}}},"timest [LK-030A] < 61 6D 70 22 3A 31 35 38 32 38 39 32 31 31 36 2C | amp":1582892116, [LK-030A] < 22 76 65 72 73 69 6F 6E 22 3A 30 2C 22 63 6C 69 | "version":0,"cli [LK-030A] < 65 6E 74 54 6F 6B 65 6E 22 3A 22 6E 75 6C 6C 22 | entToken":"null" [LK-030A] < 7D | } [1582892116.241][LK-1304] SHADOW recv generic_reply message demo_shadow_recv_handler, type = 2, productKey = a18wPXXXXXX, deviceName = aiot_shadow_test_case_01 payload = "{"status":"success","state":{"reported":{"LightSwitch":1}},"metadata":{"reported":{"LightSwitch":{"timestamp":1582887644}}}}", version = 0 [1582892118.193][LK-0309] pub: /shadow/update/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] > 7B 22 6D 65 74 68 6F 64 22 3A 22 75 70 64 61 74 | {"method":"updat [LK-030A] > 65 22 2C 22 73 74 61 74 65 22 3A 7B 22 64 65 73 | e","state":{"des [LK-030A] > 69 72 65 64 22 3A 22 6E 75 6C 6C 22 7D 2C 22 76 | ired":"null"},"v [LK-030A] > 65 72 73 69 6F 6E 22 3A 31 7D | ersion":1} [1582892118.219][LK-0309] pub: /shadow/get/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] < 7B 22 6D 65 74 68 6F 64 22 3A 22 72 65 70 6C 79 | {"method":"reply [LK-030A] < 22 2C 22 70 61 79 6C 6F 61 64 22 3A 7B 22 73 74 | ","payload":{"st [LK-030A] < 61 74 75 73 22 3A 22 73 75 63 63 65 73 73 22 2C | atus":"success", [LK-030A] < 22 76 65 72 73 69 6F 6E 22 3A 31 7D 2C 22 63 6C | "version":1},"cl [LK-030A] < 69 65 6E 74 54 6F 6B 65 6E 22 3A 22 6E 75 6C 6C | ientToken":"null [LK-030A] < 22 2C 22 74 69 6D 65 73 74 61 6D 70 22 3A 31 35 | ","timestamp":15 [LK-030A] < 38 32 38 39 32 31 31 38 7D | 82892118} [1582892118.219][LK-1304] SHADOW recv get_reply message demo_shadow_recv_handler, type = 0, productKey = a18wPXXXXXX, deviceName = aiot_shadow_test_case_01 payload = "{"status":"success","version":1}", status = success, timestamp = 1582892118 [1582892120.198][LK-0309] pub: /shadow/update/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] > 7B 22 6D 65 74 68 6F 64 22 3A 22 64 65 6C 65 74 | {"method":"delet [LK-030A] > 65 22 2C 22 73 74 61 74 65 22 3A 7B 22 72 65 70 | e","state":{"rep [LK-030A] > 6F 72 74 65 64 22 3A 7B 22 4C 69 67 68 74 53 77 | orted":{"LightSw [LK-030A] > 69 74 63 68 22 3A 22 6E 75 6C 6C 22 7D 7D 2C 22 | itch":"null"}}," [LK-030A] > 76 65 72 73 69 6F 6E 22 3A 32 7D | version":2} [1582892120.242][LK-0309] pub: /shadow/get/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] < 7B 22 6D 65 74 68 6F 64 22 3A 22 72 65 70 6C 79 | {"method":"reply [LK-030A] < 22 2C 22 70 61 79 6C 6F 61 64 22 3A 7B 22 73 74 | ","payload":{"st [LK-030A] < 61 74 75 73 22 3A 22 73 75 63 63 65 73 73 22 2C | atus":"success", [LK-030A] < 22 76 65 72 73 69 6F 6E 22 3A 32 7D 2C 22 63 6C | "version":2},"cl [LK-030A] < 69 65 6E 74 54 6F 6B 65 6E 22 3A 22 6E 75 6C 6C | ientToken":"null [LK-030A] < 22 2C 22 74 69 6D 65 73 74 61 6D 70 22 3A 31 35 | ","timestamp":15 [LK-030A] < 38 32 38 39 32 31 32 30 7D | 82892120} [1582892120.242][LK-1304] SHADOW recv get_reply message demo_shadow_recv_handler, type = 0, productKey = a18wPXXXXXX, deviceName = aiot_shadow_test_case_01 payload = "{"status":"success","version":2}", status = success, timestamp = 1582892120 [1582892122.202][LK-0309] pub: /shadow/update/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] > 7B 22 6D 65 74 68 6F 64 22 3A 22 75 70 64 61 74 | {"method":"updat [LK-030A] > 65 22 2C 22 73 74 61 74 65 22 3A 7B 22 72 65 70 | e","state":{"rep [LK-030A] > 6F 72 74 65 64 22 3A 7B 22 4C 69 67 68 74 53 77 | orted":{"LightSw [LK-030A] > 69 74 63 68 22 3A 31 7D 7D 2C 22 76 65 72 73 69 | itch":1}},"versi [LK-030A] > 6F 6E 22 3A 30 7D | on":0} [1582892122.230][LK-0309] pub: /shadow/get/a18wPXXXXXX/aiot_shadow_test_case_01 [LK-030A] < 7B 22 6D 65 74 68 6F 64 22 3A 22 72 65 70 6C 79 | {"method":"reply [LK-030A] < 22 2C 22 70 61 79 6C 6F 61 64 22 3A 7B 22 73 74 | ","payload":{"st [LK-030A] < 61 74 75 73 22 3A 22 73 75 63 63 65 73 73 22 2C | atus":"success", [LK-030A] < 22 76 65 72 73 69 6F 6E 22 3A 30 7D 2C 22 63 6C | "version":0},"cl [LK-030A] < 69 65 6E 74 54 6F 6B 65 6E 22 3A 22 6E 75 6C 6C | ientToken":"null [LK-030A] < 22 2C 22 74 69 6D 65 73 74 61 6D 70 22 3A 31 35 | ","timestamp":15 [LK-030A] < 38 32 38 39 32 31 32 32 7D | 82892122} [1582892122.230][LK-1304] SHADOW recv get_reply message demo_shadow_recv_handler, type = 0, productKey = a18wPXXXXXX, deviceName = aiot_shadow_test_case_01 payload = "{"status":"success","version":0}", status = success, timestamp = 1582892122
在文档使用中是否遇到以下问题
更多建议
匿名提交