本文以C Link SDK中的Demo文件./demos/task_posix_demo.c为例,介绍如何调用Link SDK的API,展示设备任务功能。

背景信息

  • 设备任务功能的更多信息,请参见概述

  • 设备任务功能基于MQTT接入,开发过程中涉及MQTT接入的代码说明,请参见MQTT接入

步骤一:初始化

  1. 添加头文件。
    ……
    ……
    
    #include "aiot_task_api.h"
  2. 配置底层依赖和日志输出。
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. 调用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;
        }

步骤二:配置功能

调用aiot_task_setopt(),配置以下功能。

  1. 关联MQTT连接的句柄。
    注意 配置设备任务功能参数前,请确保已配置设备认证信息等相关参数。具体操作,请参见MQTT配置连接参数
    • 示例代码:
          aiot_task_setopt(task_handle, AIOT_TASKOPT_MQTT_HANDLE, mqtt_handle);
    • 相关参数:
      配置项 示例值 说明
      AIOT_TASKOPT_MQTT_HANDLE mqtt_handle 设备任务功能的请求基于MQTT连接,通过该配置项,关联MQTT连接句柄。
  2. 配置消息回调
    • 示例代码:
          aiot_task_setopt(task_handle, AIOT_TASKOPT_RECV_HANDLER, demo_task_recv_handler);
    • 相关参数:
      配置项 示例值 说明
      AIOT_TASKOPT_RECV_HANDLER demo_task_recv_handler 接收设备任务相关消息时,调用该函数。

步骤三:设备接收物联网平台推送的设备任务通知

当设备处于在线状态,如果物联网平台下发了任务通知,您可以参照以下步骤处理该通知。

  1. 登录物联网平台控制台,创建设备任务。
    具体操作,请参见添加自定义任务
  2. 设备任务添加后,物联网平台向设备下发设备任务通知,设备接收通知后,触发回调函数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格式数据中params的值。详细说明,请参见设备任务状态更新通知
    • 根据通知消息内容,执行设备任务下的作业,然后向物联网平台上报作业的状态。更新作业的代码说明,请参见步骤五:更新任务下作业状态
    • 示例代码没有执行任务的逻辑,您需根据业务需要,编写处理逻辑。
      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;
              }
      ……
      …… 
      }

步骤四:设备主动请求设备任务消息

设备上线后,您可以主动获取设备任务信息。

  1. 调用aiot_task_get_task_detail,向物联网平台发送请求,当参数为NULL的时候,获取未执行的第一个任务的信息。
    说明 如果物联网平台向设备下推了多个设备任务,在获取设备任务消息前,您也可以调用aiot_task_get_task_list,获取设备任务列表,然后逐一获取设备任务的信息。
        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;
        }
                            
  2. 物联网平台接收到设备的请求后,将已创建的设备任务信息,返回至设备。
    创建设备任务的具体操作,请参见添加自定义任务
  3. 设备接收物联网平台返回的任务消息后,触发回调函数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;
              }
      ……
      …… 
      }

步骤五:更新任务下作业状态

设备获取任务信息后,执行任务下的作业操作后,需向物联网平台返回作业状态。

  1. 调用aiot_task_update,向物联网平台发起上报作业状态的请求。
    上报任务状态时,您需注意:
    • 上报作业状态消息的数据结构是task_desc_t
    • aiot_task_update接口的调用方法。
    • 对应的Alink报文格式如下:
      说明 任务的消息的内容为JSON格式,是Alink格式数据中params的值。详细说明,请参见更新任务下作业状态
      {
          "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);
  2. 设备作业状态上报后,物联网平台返回应答报文,触发回调函数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;
    }

后续步骤

  • 例程文件配置完成后,需进行编译,生成可执行文件./output/task_posix_demo

    更多信息,请参见编译与运行

  • 关于运行结果的详细说明,请参见运行日志