本文以C Link SDK中的Demo文件./demos/task_posix_demo.c为例,介绍如何调用Link SDK的API,展示设备任务功能。
背景信息
-
设备任务功能的更多信息,请参见概述。
- 设备任务功能基于MQTT接入,开发过程中涉及MQTT接入的代码说明,请参见MQTT接入。
步骤一:初始化
- 添加头文件。
……
……
#include "aiot_task_api.h"
- 配置底层依赖和日志输出。
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
aiot_state_set_logcb(demo_state_logcb);
- 调用aiot_task_init(),创建
Task
客户端实例,并初始化默认参数。 task_handle = aiot_task_init();
if (task_handle == NULL) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_task_init failed\n");
return -1;
}
步骤三:设备接收物联网平台推送的设备任务通知
当设备处于在线状态,如果物联网平台下发了任务通知,您可以参照以下步骤处理该通知。
- 登录物联网平台控制台,创建设备任务。
- 设备任务添加后,物联网平台向设备下发设备任务通知,设备接收通知后,触发回调函数
demo_task_recv_handler
。您可以参考以下内容,编写回调函数的处理逻辑:
- 任务通知消息的数据结构类型为aiot_task_recv_t,是回调函数的入参。
- 任务通知消息的类型为AIOT_TASKRECV_NOTIFY。
- 以下是通知消息的示例及其Alink数据格式:
示例 |
Alink数据格式 |
说明 |
{
"task": {
"taskId": "i5Ks6***pF010101",
"status": "SENT",
"jobDocument": {
},
"jobFile": {
"signMethod": "Md5",
"sign": "wssxff56dhdsd***",
"fileUrl": "https://iotx-***.aliyuncs.com/***.zip"
}
}
}
|
{
"id": "7542940",
"version": "1.0",
"params": {
"task": {
"taskId": "i5Ks6***pF010101",
"status": "SENT",
"jobDocument": {
},
"jobFile":{
"signMethod":"Md5",
"sign":"wssxff56dhdsd***",
"fileUrl": "https://iotx-***.aliyuncs.com/***.zip"
}
}
}
}
|
任务通知的消息的内容为JSON格式,是Alink格式数据中。 |
- 根据通知消息内容,执行设备任务下的作业,然后向物联网平台上报作业的状态。
- 示例代码没有执行任务的逻辑,您需根据业务需要,编写处理逻辑。
void demo_task_recv_handler(void *handle, const aiot_task_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_TASKRECV_NOTIFY: {
const task_desc_t *in_desc = &(packet->data.notify);
printf("revice task notify, task_id:[%s],status:[%d],job_document[%s],document_file_url:[%s],\
sign_method:[%s],sign[%s]\r\n",
in_desc->task_id, in_desc->status, in_desc->job_document,
in_desc->document_file_url, in_desc->sign_method, in_desc->sign);
/* 1.如果handle里无任务记录,将物联网平台下发的Task保存到handle里面的default_task_desc字段 */
if (NULL == g_local_task_desc) {
demo_copy_task_to_local_task(&g_local_task_desc, in_desc);
/* 启动任务. 示例代码仅做打印,您可以根据实际情况来适配 */
int res = pthread_create(&g_task_thread, NULL, demo_task_thread, g_local_task_desc);
if (res != 0) {
printf("pthread_create demo_task_thread failed: %d\r\n", res);
} else {
/* 下载线程被设置为 detach 类型, 固件内容获取完毕后可自主退出 */
pthread_detach(g_task_thread);
}
/* 变更任务状态.TODO: 以下代码仅供参考,任务执行完毕时, 您需将状态设置为AIOT_TASK_STATUS_SUCCEEDED*/
g_local_task_desc->status = AIOT_TASK_STATUS_IN_PROGRESS;
aiot_task_update(handle, g_local_task_desc);
demo_free_local_task(&g_local_task_desc);
break;
}
/* 2.如果状态被物联网平台设置为终态, 则在此处将本地的任务清理掉 */
if (in_desc->status == AIOT_TASK_STATUS_CANCELLED || in_desc->status == AIOT_TASK_STATUS_REMOVED
|| in_desc->status == AIOT_TASK_STATUS_TIMED_OUT) {
/* TODO: 清理本地任务, 停下线程 */
/* 如果该任务是记录在handle里面的默认任务, 则将其内存清理掉; 如果是记录在handle外的, 需要您维护内存 */
if (NULL != g_local_task_desc && 0 == strcmp(in_desc->task_id, g_local_task_desc->task_id)) {
/* 释放本地任务内存 */
demo_free_local_task(&g_local_task_desc);
}
break;
}
/* 3.如果本地已有任务记录,物联网平台将更新当前这个任务的描述, 您需要检查更新的内容 */
if (in_desc->status == AIOT_TASK_STATUS_IN_PROGRESS) {
if (NULL != g_local_task_desc && 0 == strcmp(in_desc->task_id, g_local_task_desc->task_id)) {
/* TODO: 更新本地的任务描述. 用户可能要暂停当前的任务再更新, 这一点取决于用户 */
break;
}
}
/* 4.如果不是上述情况, 则收到通知为新的任务。当任务执行中, 又接收到新任务, 您可以在main中创建一个列表 */
/* 将该列表作为userdata传入, 并把任务记录在这个列表里面, 以便维护*/
break;
}
……
……
}
步骤四:设备主动请求设备任务消息
设备上线后,您可以主动获取设备任务信息。
- 调用aiot_task_get_task_detail,向物联网平台发送请求,当参数为NULL的时候,获取未执行的第一个任务的信息。
res = aiot_task_get_task_detail(task_handle, NULL);
if (res < STATE_SUCCESS) {
aiot_task_deinit(&task_handle);
demo_mqtt_stop(&mqtt_handle);
return -1;
}
- 物联网平台接收到设备的请求后,将已创建的设备任务信息,返回至设备。
- 设备接收物联网平台返回的任务消息后,触发回调函数
demo_task_recv_handler
。您可以参考以下内容,编写回调函数的处理逻辑:
- 任务消息的数据结构类型为aiot_task_recv_t,是回调函数的入参。
- 任务消息的类型为AIOT_TASKRECV_GET_DETAIL_REPLY。
- 以下是任务消息的示例及其Alink数据格式:
示例 |
Alink数据格式 |
说明 |
{
"taskId": "i5Ks***F010101",
"status": "IN_PROGRESS",
"jobDocument": {
},
"jobFile":{
"signMethod":"Md5",
"sign":"wssxff56dhdsd***",
"fileUrl": "https://iotx-***.aliyuncs.com/***.zip"
}
}
|
{
"id": "1234",
"code": 200,
"data": {
"taskId": "$next",
"task":{
"taskId": "i5Ks***F010101",
"status": "IN_PROGRESS",
"jobDocument": {
},
"jobFile":{
"signMethod":"Md5",
"sign":"wssxff56dhdsd***",
"fileUrl": "https://iotx-***.aliyuncs.com/***.zip"
}
}
}
}
|
任务的消息内容为JSON格式,是Alink格式数据中params的值。
|
- 根据任务消息内容,执行设备任务下的作业,然后向物联网平台上报作业的状态。
- 示例代码没有执行任务的逻辑,您需根据业务需要,编写处理逻辑。
void demo_task_recv_handler(void *handle, const aiot_task_recv_t *packet, void *userdata)
{
switch (packet->type) {
……
……
case AIOT_TASKRECV_GET_DETAIL_REPLY: {
const task_get_detail_reply_t *in_reply = &(packet->data.get_detail_reply);
printf("revice task get detail, code:[%d]\r\n", in_reply->code);
if (200 == in_reply->code) {
printf("revice task get detail reply, task_id:[%s],status:[%d]\r\n",
in_reply->task.task_id, in_reply->task.status);
if (in_reply->task.status != AIOT_TASK_STATUS_NOT_FOUND) {
printf("job_document[%s],document_file_url:[%s], sign_method:[%s],sign[%s]\r\n",
in_reply->task.job_document, in_reply->task.document_file_url,
in_reply->task.sign_method, in_reply->task.sign);
task_desc_t task;
memset(&task, 0, sizeof(task));
memcpy(&task, &(in_reply->task), sizeof(task));
/* TODO: 执行任务, 可以通过起线程的方式 */
/* 变更任务状态. TODO: 这里仅为参考实现, 用户可以根据实际情况来适配. 任务执行完毕时, 要将状态设置为AIOT_TASK_STATUS_SUCCEEDED */
task.status = AIOT_TASK_STATUS_IN_PROGRESS;
task.progress = 88;
aiot_task_update(handle, &task);
}
}
break;
}
……
……
}
步骤五:更新任务下作业状态
设备获取任务信息后,执行任务下的作业操作后,需向物联网平台返回作业状态。
- 调用aiot_task_update,向物联网平台发起上报作业状态的请求。
上报任务状态时,您需注意:
- 上报作业状态消息的数据结构是task_desc_t。
- aiot_task_update接口的调用方法。
- 对应的Alink报文格式如下:
{
"id": "123",
"version": "1.0",
"params": {
"taskId": "i5Ks***F010101",
"status": "IN_PROGRESS",
"statusDetails": {
"key": "value"
},
"progress": 50
}
}
- 示例代码上报的设备状态为AIOT_TASK_STATUS_IN_PROGRESS,您需根据业务实际场景获取设备的实际作业情况,上报作业状态。
- 步骤三场景的示例代码:
g_local_task_desc->status = AIOT_TASK_STATUS_IN_PROGRESS;
aiot_task_update(handle, g_local_task_desc);
demo_free_local_task(&g_local_task_desc);
- 步骤四场景的示例代码:
task.status = AIOT_TASK_STATUS_IN_PROGRESS;
task.progress = 88;
aiot_task_update(handle, &task);
- 设备作业状态上报后,物联网平台返回应答报文,触发回调函数
demo_task_recv_handler
。您可以参考以下内容,编写回调函数的处理逻辑:
- 应答报文的数据结构类型为aiot_task_recv_t,是回调函数的入参。
- 应答报文消息的类型为AIOT_TASKRECV_UPDATE_REPLY。
- 示例代码没有执行任务的逻辑,您需根据业务需要,编写处理逻辑。
void demo_task_recv_handler(void *handle, const aiot_task_recv_t *packet, void *userdata)
{
switch (packet->type) {
……
……
case AIOT_TASKRECV_UPDATE_REPLY: {
const task_update_reply_t *update_reply = &(packet->data.update_reply);
printf("revice task update reply, code:[%d]\r\n", update_reply->code);
if (200 == update_reply->code) {
printf("revice task update reply, task_id:[%s]\r\n", update_reply->task_id);
}
if (71012 == update_reply->code) {
printf("aiot_task_update task's status_details value must be json format\r\n");
}
/* TODO */
break;
}
……
……
}
步骤六:退出设备任务程序
调用aiot_task_deinit(),销毁Task
客户端实例,释放资源。
res = aiot_task_deinit(&task_handle);
if (res < STATE_SUCCESS) {
demo_mqtt_stop(&mqtt_handle);
printf("aiot_task_deinit failed: -0x%04X\n", -res);
return -1;
}
步骤七:断开连接
说明
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;
}