本文以C Link SDK中的Demo文件./demos/data_model_basic_demo.c为例,介绍如何调用Link SDK的API,使设备可使用物模型的功能。
背景信息
步骤一:初始化
- 添加头文件。
…… …… #include "aiot_dm_api.h"
- 配置底层依赖和日志输出。
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile); aiot_state_set_logcb(demo_state_logcb);
- 调用aiot_dm_init,创建
data-model
客户端实例,并初始化默认参数。dm_handle = aiot_dm_init(); if (dm_handle == NULL) { printf("aiot_dm_init failed"); return -1;}
步骤二:配置功能
调用aiot_dm_setopt,配置以下功能。
- 关联MQTT连接的句柄。重要 配置物模型功能参数前,请确保已配置设备认证信息等相关参数。具体操作,请参见MQTT配置连接参数。
- 示例代码:
aiot_dm_setopt(dm_handle, AIOT_DMOPT_MQTT_HANDLE, mqtt_handle);
- 相关参数:
配置项 示例值 说明 AIOT_DMOPT_MQTT_HANDLE mqtt_handle 物模型功能的请求基于MQTT连接,通过该配置项,关联MQTT连接句柄。
- 示例代码:
- 编写物模型消息的回调函数。
- 配置物联网平台是否应答报文。您可以通过以下设置,要求物联网平台在接收到设备消息后,是否应答。
- 示例代码:
uint8_t post_reply = 1; …… …… aiot_dm_setopt(dm_handle, AIOT_DMOPT_POST_REPLY, (void *)&post_reply);
- 相关参数:
配置项/参数 示例 说明 AIOT_DMOPT_POST_REPLY 1 取值: - 1:应答报文。
- 0:不应答报文。
- 如果配置项AIOT_DMOPT_POST_REPLY的值为
1
,需为应答报文编写处理逻辑。您可根据业务需要编写处理逻辑,示例代码仅做打印处理。
- ICA标准数据格式(Alink JSON)
应答报文的消息在
recv->data.generic_reply
中,与Alink数据格式相同,更多信息,请参见设备属性、事件、服务。static void demo_dm_recv_generic_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata){ printf("demo_dm_recv_generic_reply msg_id = %d, code = %d, data = %.*s, message = %.*s\r\n", recv->data.generic_reply.msg_id, recv->data.generic_reply.code, recv->data.generic_reply.data_len, recv->data.generic_reply.data, recv->data.generic_reply.message_len, recv->data.generic_reply.message); }
- 透传/自定义格式
应答报文消息在
recv->data.raw_data
中,消息收发前,您需在物联网平台上传解析脚本,更多信息请参见物模型消息解析。static void demo_dm_recv_raw_data_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_async_service_invoke receive reply for up_raw msg, data len = %d\r\n", recv->data.raw_data.data_len); }
- ICA标准数据格式(Alink JSON)
- 示例代码:
步骤三:上报属性
设备建立MQTT连接后,调用aiot_dm_send,向物联网平台发送属性消息。要发送的属性消息,通过aiot_dm_msg_t结构体,存放于指定位置,作为aiot_dm_send的入参。
ICA标准数据格式(Alink JSON)
- 添加物模型。
- 设置属性消息的内容。
- 示例代码:
- 默认模块:
/* 单个上报属性的内容 */ demo_send_property_post(dm_handle, "{\"LightSwitch\": 0}"); /* 批量上报属性的内容 */ demo_send_property_batch_post(dm_handle, "{\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518}],\"WF\": [{\"value\": 3,\"time\":1612684518}]}}");
- 自定义模块:
demo_send_property_post(dm_handle, "{\"demo_extra_block:NightLightSwitch\": 1}");
- 默认模块:
- 示例消息说明:
- 物模型上报消息为JSON格式。以下为上报消息的示例及其对应的Alink数据格式,更多信息,请参见设备上报属性。说明 为了方便使用,您只需关注设备上报的数据(即Alink数据格式中params中的内容),Link SDK会对上报的消息封装处理。
消息类型 上报消息的示例 对应的Alink格式 单个属性 {\"LightSwitch\": 0}
{ "id": "123", "version": "1.0", "sys":{ "ack":0 }, "params": { "LightSwitch": 0 }, "method": "thing.event.property.post" }
批量属性 {\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518}],\"WF\": [{\"value\": 3,\"time\":1612684518}]}}
{ "id": 123, "version": "1.0", "sys":{ "ack":0 }, "method": "thing.event.property.batch.post", "params": { "properties": { "Power": [{ "value": "on", "time": 1612684518 }, ], "WF": [{ "value": 3, "time": 1612684518 }, ] }, }
- 批量上报属性消息后,物联网平台基于批量上报的消息,发送应答报文。如果您需确认物联网平台已接收到该上报的消息,需订阅Topic
/sys/a18wP******/LightSwitch/thing/event/property/batch/post_reply
。示例代码:
aiot_mqtt_sub(mqtt_handle, "/sys/a18wP******/LightSwitch/thing/event/property/batch/post_reply", NULL, 1, NULL);
其中:a18wP******
为设备的ProductKey。LightSwitch
为设备的DeviceName。
- 示例代码:
- 定义上报属性消息的函数。
/* 单个属性上报 */ int32_t demo_send_property_post(void *dm_handle, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_POST; msg.data.property_post.params = params; return aiot_dm_send(dm_handle, &msg); } /* 批量属性上报 */ int32_t demo_send_property_batch_post(void *dm_handle, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_BATCH_POST; msg.data.property_post.params = params; return aiot_dm_send(dm_handle, &msg); }
透传/自定义格式
- 在物联网平台上传物模型数据解析脚本。需更多信息,请参见透传/自定义格式的消息。
- 编写设备上报二进制消息的代码。示例代码:
{ aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_DATA; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); }
步骤四:设置属性
您可以调用物联网平台的云端API SetDeviceProperty或SetDevicesProperty,向设备发送属性设置指令。设备端调用aiot_mqtt_recv接收该指令,根据回调函数demo_dm_recv_handler
的设置,执行对应处理。
您可以参考以下内容,编写回调函数的处理逻辑:
- 注意事项:
接收的消息作为类型aiot_dm_recv_handler_t的入参,通过aiot_dm_recv_t结构体存放于您指定的位置。
- 不同的设备数据格式,其接收的设置属性指令内容,所在的字段如下表所示:
设备数据格式 接收的消息中的字段 说明 ICA标准数据格式(Alink JSON) recv->data.property_set.params
与Alink格式数据中params的值一致,更多信息,请参见属性消息。
透传/自定义格式 recv->data.raw_data
设备端需解析数据,更多信息,请参见透传/自定义格式的消息。
- 建议的处理逻辑:
- 更新设备属性。
- 上报更新后的属性。
示例代码的处理逻辑:
示例代码仅打印接收的消息,如需演示上报设置的属性,请取消示例代码中
TODO
下代码的注释符号(/*
和*/
),查看效果。- ICA标准数据格式(Alink JSON)
static void demo_dm_recv_property_set(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_property_set msg_id = %ld, params = %.*s\r\n", (unsigned long)recv->data.property_set.msg_id, recv->data.property_set.params_len, recv->data.property_set.params); /* TODO: 以下代码演示如何对物联网平台的属性设置指令进行应答, 您可取消注释查看演示效果 */ /* { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_SET_REPLY; msg.data.property_set_reply.msg_id = recv->data.property_set.msg_id; msg.data.property_set_reply.code = 200; msg.data.property_set_reply.data = "{}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } */ }
- 透传/自定义格式
static void demo_dm_recv_raw_data(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_raw_data raw data len = %d\r\n", recv->data.raw_data.data_len); /* TODO: 以下代码演示如何发送二进制格式数据, 如需使用,需在物联网平台部署对应的数据透传脚本。 */ /* { aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_DATA; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); } */ }
- ICA标准数据格式(Alink JSON)
步骤五:上报事件
设备建立MQTT连接后,调用aiot_dm_send,向物联网平台发送事件消息。要发送的事件消息,通过aiot_dm_msg_t结构体,存放于指定位置,作为aiot_dm_send的入参。
根据以下不同的设备数据格式类型,为上报事件,编写代码。
- 如果您的设备数据格式为透传/自定义格式,设备上报事件与上报属性的接口一致,具体流程,请参见步骤三中的上报透传/自定义格式的消息。
- 如果您的设备数据格式为ICA标准数据格式(Alink JSON),设备端代码编写的流程,请参见下文。
- 添加物模型。
- 设置事件消息内容。
- 示例代码:
demo_send_event_post(dm_handle, "Error", "{\"ErrorCode\": 0}");
- 示例消息说明:
物模型上报消息为JSON格式。以下为上报消息的示例及其对应的Alink数据格式,更多信息,请参见设备上报事件。
说明 为了方便使用,您只需关注设备上报的数据(即Alink数据格式中params中的内容),Link SDK会对上报的消息封装处理。上报事件的示例 对应的Alink格式 {\"ErrorCode\": 0}
{ "id": "123", "version": "1.0", "params": { "ErrorCode": 0 }, "method": "thing.event.alarm.post" }
- 示例代码:
- 定义上报事件消息的函数。
int32_t demo_send_event_post(void *dm_handle, char *event_id, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_EVENT_POST; msg.data.event_post.event_id = event_id; msg.data.event_post.params = params; return aiot_dm_send(dm_handle, &msg); }
步骤六:调用服务
您可以调用物联网平台的云端API InvokeThingService或InvokeThingsService,向设备发送调用服务的指令。设备端调用aiot_mqtt_recv接收该指令,根据回调函数demo_dm_recv_handler
的设置,执行对应处理。
您可以参考以下内容,编写回调函数的处理逻辑:
注意事项:
接收的消息作为类型aiot_dm_recv_handler_t的入参,通过aiot_dm_recv_t结构体存放于您指定的位置。
- 不同的设备数据格式,其接收的调用服务指令内容,所在的字段如下表所示:
设备数据格式 同步消息的字段 异步消息的字段 说明 ICA标准数据格式(Alink JSON) recv->data.sync_service_invoke
recv->data.async_service_invoke
字段中数据的格式与Alink格式数据中params的值一致,更多信息,请参见服务消息。 透传/自定义格式 recv->data.raw_data
recv->data.raw_service_invoke
需自行上传解析脚本,解析指令内容。更多信息,请参见透传/自定义格式的消息。
- 如果您定义的回调函数不对调用服务指令做应答,自行编写应答处理逻辑函数时,请确保应答消息中的以下信息与物联网平台请求中的该参数值一致。
- 同步:
rrpc_id
和msg_id
- 异步:
msg_id
- 同步:
- 向物联网平台返回服务的应答报文时,需注意超时时间:
- 同步:接收同步服务消息后,请在8秒内做出应答。否则,即使设备端已收到消息,也视其失败。
- 异步:您可根据业务需要,自定义异步调用超时的代码逻辑。物联网平台未对此做限制。
建议的处理逻辑:
- 执行服务指令。
- 向物联网平台返回服务的应答报文。
示例代码的处理逻辑:
示例代码仅打印接收的消息,如需演示上报设置的属性,请取消示例代码中
TODO
下代码的注释符号(/*
和*/
),查看效果。- ICA标准数据格式(Alink JSON)
- 同步:
static void demo_dm_recv_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_sync_service_invoke msg_id = %ld, rrpc_id = %s, service_id = %s, params = %.*s\r\n", (unsigned long)recv->data.sync_service_invoke.msg_id, recv->data.sync_service_invoke.rrpc_id, recv->data.sync_service_invoke.service_id, recv->data.sync_service_invoke.params_len, recv->data.sync_service_invoke.params); /* TODO: 以下代码演示如何对来自物联网平台的同步调用服务进行应答。 */ { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_SYNC_SERVICE_REPLY; msg.data.sync_service_reply.rrpc_id = recv->data.sync_service_invoke.rrpc_id; msg.data.sync_service_reply.msg_id = recv->data.sync_service_invoke.msg_id; msg.data.sync_service_reply.code = 200; msg.data.sync_service_reply.service_id = "SetLightSwitchTimer"; msg.data.sync_service_reply.data = "{}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } }
- 异步:
static void demo_dm_recv_async_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_async_service_invoke msg_id = %ld, service_id = %s, params = %.*s\r\n", (unsigned long)recv->data.async_service_invoke.msg_id, recv->data.async_service_invoke.service_id, recv->data.async_service_invoke.params_len, recv->data.async_service_invoke.params); /* TODO: 以下代码演示如何对来自物联网平台的异步调用服务进行应答, 您可取消注释查看演示效果。 */ /* { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_ASYNC_SERVICE_REPLY; msg.data.async_service_reply.msg_id = recv->data.async_service_invoke.msg_id; msg.data.async_service_reply.code = 200; msg.data.async_service_reply.service_id = "ToggleLightSwitch"; msg.data.async_service_reply.data = "{\"dataA\": 20}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } */ }
- 同步:
- 透传/自定义格式
- 二进制异步服务调用的处理逻辑,与二进制设置属性的接口一致。具体操作,请参见自定义Topic消息解析。
- 二进制同步服务调用的处理逻辑,示例代码如下:
static void demo_dm_recv_raw_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_raw_sync_service_invoke raw sync service rrpc_id = %s, data_len = %d\r\n", recv->data.raw_service_invoke.rrpc_id, recv->data.raw_service_invoke.data_len); /* TODO: 以下代码演示如何对来自物联网平台的同步调用服务进行应答, 您可取消注释查看演示效果 */ /* { aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_SERVICE_REPLY; msg.data.raw_service_reply.rrpc_id = recv->data.raw_service_invoke.rrpc_id; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); } */ }
- ICA标准数据格式(Alink JSON)
步骤七:断开连接
MQTT接入常应用于长连接的设备,程序通常不会运行至此。
例程的主线程任务为配置参数并成功建立连接。连接建立后,主线程可进入休眠。
调用aiot_mqtt_disconnect,向物联网平台发送断开连接的报文,然后断开网络连接。
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
步骤八:退出程序
调用aiot_dm_deinit,销毁data-model
客户端实例,释放资源。
res = aiot_dm_deinit(&dm_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dm_deinit failed: -0x%04X\n", -res);
return -1;
}