本文以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连接句柄。
编写物模型消息的回调函数。
定义物模型消息的回调函数。
static void demo_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_handler, type = %d\r\n", recv->type); switch (recv->type) { /* 对属性上报、事件上报、获取期望属性值或者删除期望属性值指令的应答 */ case AIOT_DMRECV_GENERIC_REPLY: { demo_dm_recv_generic_reply(dm_handle, recv, userdata); } break; /* 属性设置 */ case AIOT_DMRECV_PROPERTY_SET: { demo_dm_recv_property_set(dm_handle, recv, userdata); } break; /* 异步调用服务 */ case AIOT_DMRECV_ASYNC_SERVICE_INVOKE: { demo_dm_recv_async_service_invoke(dm_handle, recv, userdata); } break; /* 同步调用服务 */ case AIOT_DMRECV_SYNC_SERVICE_INVOKE: { demo_dm_recv_sync_service_invoke(dm_handle, recv, userdata); } break; /* 下行二进制数据 */ case AIOT_DMRECV_RAW_DATA: { demo_dm_recv_raw_data(dm_handle, recv, userdata); } break; /* 二进制格式的同步调用服务, 比单纯的二进制数据消息多了个rrpc_id */ case AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE: { demo_dm_recv_raw_sync_service_invoke(dm_handle, recv, userdata); } break; /* 上行二进制数据后, 物联网平台的回复报文 */ case AIOT_DMRECV_RAW_DATA_REPLY: { demo_dm_recv_raw_data_reply(dm_handle, recv, userdata); } break; default: break; } }
配置物模型消息的回调函数。
示例代码:
aiot_dm_setopt(dm_handle, AIOT_DMOPT_RECV_HANDLER, (void *)demo_dm_recv_handler);
相关参数:
配置项
示例值
说明
AIOT_DMOPT_RECV_HANDLER
demo_dm_recv_handler
接收到物模型消息时,调用该函数。
配置物联网平台是否应答报文。
您可以通过以下设置,要求物联网平台在接收到设备消息后,是否应答。
示例代码:
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); }
步骤三:上报属性
设备建立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\":1612684518000}],\"WF\": [{\"value\": 3,\"time\":1612684518000}]}}
{ "id": 123, "version": "1.0", "sys":{ "ack":0 }, "method": "thing.event.property.batch.post", "params": { "properties": { "Power": [{ "value": "on", "time": 1612684518000 }, ], "WF": [{ "value": 3, "time": 1612684518000 }, ] }, }
批量上报属性消息后,物联网平台基于批量上报的消息,发送应答报文。如果您需确认物联网平台已接收到该上报的消息,需订阅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); } */ }
步骤五:上报事件
设备建立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); } */ }
步骤七:断开连接
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;
}