本文介绍设备如何调用SDK完成对上传至物联网平台固件的下载,进而完成设备厂商对设备的固件升级计划。

背景说明

物联网平台提供固件升级与管理服务。若设备集成了C-SDK提供的OTA能力,用户可以在物联网平台的控制台上传新的固件,并通过SDK将固件下载到设备上,然后由设备厂商完成设备的固件升级。

说明 SDK提供API下载固件,如何将固件写入设备的Flash完成固件升级需要厂商实现。
下面是使用SDK进行OTA的示意图:OTA下载流程

如上图流程说明,设备从MQTT长连接通道可收到阿里云推送的新固件下载URL地址,然后用HTTP或HTTPS连接,从云端下载固件。

API列表

OTA升级包括两部分的内容, 通过MQTT通道接收到固件URL, 及通过HTTP通道下载固件内容的两部分操作,声明部分请参见components/ota/aiot_ota_api.h

  • 固件信息推送

    这部分API主要基于MQTT连接工作,用于完成设备接收云端下推的固件描述信息,比如下载地址。

    接口名 说明
    aiot_ota_init 创建ota客户端实例并设置默认参。数
    aiot_ota_deinit 销毁ota客户端实例, 回收资源。
    aiot_ota_report_version 用MQTT报文向云平台上报自身设备的固件版本号, 这是云端判断升级是否启动和成功的依据。
    aiot_ota_report_version_ext 用MQTT报文向云平台上报指定设备的固件版本号, 用于网关代理子设备上报的场景。
    aiot_ota_setopt 设置ota连接参数, 详见OTA设置参数说明
  • 固件内容下载

    这部分API主要基于HTTP连接工作, 用于完成设备从固件存储服务器下载固件内容。

    接口名 说明
    aiot_download_init 创建download客户端实例并设置默认参数。
    aiot_download_deinit 销毁download客户端实例, 回收资源。
    aiot_download_setopt 设置download连接参数, 详见Download设置参数说明
    aiot_download_report_progress 用MQTT报文向云平台上报下载的进度, 也可以上报升级过程中发生的异常, 例如烧写失败, 网络断开等。
    aiot_download_send_request aiot_download_setopt()指定的固件存储服务器, 发起HTTP GET请求, 要求下载一段固件内容。
    aiot_download_recv 从网络上收取来自服务器的固件内容, 然后进入AIOT_DLOPT_RECV_HANDLER选项设置的用户回调, 将下载内容传给用户。
    注意 SDK收到云端推送的固件URL后并不会自动进行固件下载,用户需要调用SDK提供的API启动固件下载。

API使用概述

OTA模块可用于配合阿里云平台的固件升级服务。

说明

固件升级流程如下:

  1. 设备要使用OTA服务, 必须先以aiot_ota_report_version 向云端上报当前的固件版本号,否则无法接收到固件信息推送。
  2. 设备下载固件时, 设备自行用aiot_download_send_request 从云端获取,SDK不会自动下载固件。
  3. 设备下载中或者下载完成后, 设备需自行将内存buffer中的固件内容写到Flash上,SDK不含有固件烧录的逻辑。
  4. 设备升级完成后, 需以aiot_ota_report_version 接口, 向云端上报升级后的新固件版本号,否则云端不会认为升级完成。
注意 用户在控制台推送固件的下载地址等信息给设备时, 是通过MQTT通道, 所以设备OTA升级的前提是成功建立并保持MQTT长连接通道在线。

下面介绍如何API的使用。

  • aiot_ota_report_version

    如果云端不知道某台设备当前的固件版本号, 就不会为其提供OTA服务,而如果不知道设备的新版本号, 也不会认为它升级成功。

    所以OTA要正常工作,可以当设备连接到物联网平台之后调用这个接口,将当前设备上运行的固件版本号字符串上报给云端,如果用户在物联网平台上传了新的固件,平台将会告诉设备端新固件的下载URL以及固件的相关信息。

    参数的handle通过aiot_ota_init 得到,比如,上报当前版本号为"1.0.0"的代码写作。

    handle = aiot_ota_init();
    ...
    aiot_ota_report_version(handle, "1.0.0");
  • aiot_ota_setopt

    对OTA会话进行配置,常见的配置选项包括。

    • AIOT_OTAOPT_MQTT_HANDLE: 把 aiot_mqtt_init 返回的MQTT会话句柄跟OTA会话关联起来。
    • AIOT_OTAOPT_RECV_HANDLER: 设置OTA消息的数据处理回调, 这个用户回调在有OTA消息的时候, 会被 aiot_mqtt_recv 调用到。
  • aiot_download_setopt

    配置固件下载会话的选项,常见需要设置的选项包括。

    • AIOT_DLOPT_RECV_HANDLER:用户告诉SDK,当SDK收到固件内容的时候,调用哪个用户函数来将其传出固件内容缓冲区。
    • AIOT_DLOPT_NETWORK_CRED:可以配置是走HTTP还是走HTTPS下载固件内容。
    • AIOT_DLOPT_BODY_BUFFER_MAX_LEN:这是个缓冲区的长度,SDK下载中,每当填满这个长度就调用一次用户回调, 所以这里设置的越大,下载越快,内存开销也越大。
  • aiot_download_report_progress

    在设备开始下载固件的过程之后,都可以用这个接口向云端上报进展情况,包括下载进度或出错信息。

    • 如果下载是正常的,可以整数形式上报,当前已下载的内容占固件整体大小的百分比,percent参数,SDK会自动计算好,在回调中传给用户。
    • 如果下载异常或者下载之后固件的烧录异常了,也可以用这个接口把异常告诉云端,设备和云端的协议错误码约定见 aiot_ota_protocol_errcode_t
    • 通过 aiot_download_report_progress 上报的内容都会影响控制台的显示,比如显示OTA升级进度,显示OTA升级失败等。
  • aiot_download_send_request

    设备通过OTA消息回调函数知道了固件下载地址后,就可以调用这个接口,下载一段固件。

    • 这段长度可由AIOT_DLOPT_RANGE_STARTAIOT_DLOPT_RANGE_END选项设置。设置后,下载的范围是[AIOT_DLOPT_RANGE_START,AIOT_DLOPT_RANGE_END],包括这个区间的起始和终止字节。
    • 如果不做设置, 则默认SDK会整段去请求固件内容,但每到填满1次用户buffer就会通知用户1次,buffer长度用AIOT_DLOPT_BODY_BUFFER_MAX_LEN选项配置。

常见问题

  • buffer开太小,导致下载速度太慢

    在下载速度慢的情况下,应首先检查通过aiot_download_setoptAIOT_DLOPT_BODY_BUFFER_MAX_LEN选项配置的值的大小。

    AIOT_DLOPT_BODY_BUFFER_MAX_LEN选项指定了下载过程中用于接收固件报文的buffer的大小。这个buffer越大,就意味着下载固件和填满它通知用户的次数越少,总体耗时越短。过小的buffer, 比如100 Byte左右,会导致整体耗时偏长,受网速波动影响的可能性也越大。同时,在多线程的系统中,如果下载任务执行慢, 会一直占用CPU,影响其他业务的正常运行。

  • 设备升级超时时间设置太短会导致控制台显示升级失败

    控制台在部署OTA任务的时候, 有设备升级超时时间(分钟)这个选项。该值需要根据下载的实际情况来填写, 并充分考虑网络异常带来的波动, 保留一定的余量。

    如果该值设置得太小, 比如设置为2分钟, 那么设备如果没有在2分钟内完成升级并且上报新的版本号的话, 控制台就会提示失败, 此时设备端表现为下载通道直接断开。

  • 控制台在选择定向升级的时候无法找到目标设备

    控制台在设置升级范围为定向升级时,如果设备范围一栏里面没法找到目标设备,需要确认该目标设备是否上报过版本号。

    如果上报过版本号,还需确认它上报过的版本号跟当前的待升级版本号是否一致。

    如果没有上报过版本号,或者上报了但是跟当前的待升级版本号不符合,那么都是无法触发升级任务的。

  • 下载完成,但是没有上报新的版本号,导致云端认为没有升级成功

    控制台是以设备上报的版本号来判断是否升级成功,而非根据上报的下载百分比。

    因此, 在设备下载完固件并成功升级后,务必要确保设备上报新的版本号,否则在控制台不会看到设备升级成功。

  • 分段下载的时候, 由于分段之间有重合,或者没有按顺序下载,导致最终校核失败

    分段下载的时候, 需要保证分段是按照从头到尾这样的顺序来分段的, 同时各个段之间还不能有重叠区域。

    比如:[0,1024][1024, 2048],这样就会出错,会导致第1024个字节重复下载了2次,从而导致最终校验和失败。需要改成[0, 1024][1025, 2048]

  • 在控制台看到进度100%不代表升级完成

    由于升级进度是用户以aiot_download_report_progress上报的,上报了100%的进度后,仅表示这个固件下载完成了100%, 不代表升级完成。

    云端只根据设备是否上报了OTA推送的目标固件版本号来判断是否升级成功,这在设备端通过调用SDK的aiot_ota_report_version接口并传入升级后版本号来完成。

  • 下载过程中网络断开导致下载失败

    下载过程中, 如果出现了网络断开的情况, 会导致设备收取不到报文。常见的错误提示如下:

    download failed, error code is -3864, try to send renewal request

    这时请检查网络连接。在我们的demo代码中,网络恢复后SDK会自动的用断点续传的方式继续下载。

例程讲解

现对照demos/fota_posix_demo.c例程,分步骤讲解如何使用API。

OTA开发流程

这个例程适用于Linux这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建3个线程

  • 一个线程用于保活长连接。
  • 一个线程用于接收消息,并在有消息到达时进入默认的数据回调,在连接状态变化时进入事件回调。
  • 一个线程用于从网络上HTTP下载待升级的固件,这个线程由接收消息线程得到OTA升级的MQTT消息后启动。

需要用户关注或修改的部分, 已用 TODO 在注释中标明。

  1. 设置设备证书

    例程中使用的设备证书是公用的, 所以应用于实际业务时, 请替换如下的TODO部分, 传入用户自己真实的设备证书。

    /* TODO: 替换为自己设备的设备证书 */
    char *product_key       = "a13FNXXXXXX";
    char *device_name       = "ota_demo";
    char *device_secret     = "bIP6G5epJtHuOYvuVcFaTN53wJXXXXXX";
  2. 建立MQTT连接

    OTA过程需要从MQTT通道接收固件推送消息,所以程序的主体中,建立MQTT连接仍是必须的。

    int main(int argc, char *argv[])
    {
        ...
        /* 创建1个MQTT客户端实例并内部初始化默认参数 */
        mqtt_handle = aiot_mqtt_init();
    
        /* 配置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_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
        /* 配置MQTT事件回调函数 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
  3. 创建OTA会话实例

    与单纯MQTT连云场景不同的是,还应为OTA会话创建单独的实例,并将它与MQTT会话实例关联起来,再发起MQTT连接。

    /* 用以下语句, 把OTA会话和MQTT会话关联起来 */
        aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
        /* 用以下语句, 设置OTA会话的数据接收回调, SDK收到OTA相关推送时, 会进入这个回调函数 */
        aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);
    
        /* 与服务器建立MQTT连接 */
        res = aiot_mqtt_connect(mqtt_handle);
        if (res < STATE_SUCCESS) {
            /* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
            aiot_mqtt_deinit(&mqtt_handle);
            aiot_ota_deinit(&ota_handle);
            printf("aiot_mqtt_connect failed: -0x%04X\n", -res);
            return -3;
        }

    需要注意的是, 以上代码段同时也给OTA会话设置了数据接收回调,为 demo_ota_recv_handler 函数。

  4. 上报当前固件版本号

    为了让云平台的控制台正常工作,设备建立MQTT连接后,应准确的、及时的、上报当前所运行固件的版本号,参见代码注释说明。

    /*   TODO: 非常重要!!!
         *
         *   cur_version 要根据用户实际情况, 改成从设备的配置区获取, 要反映真实的版本号, 而不能像示例这样写为固定值
         *
         *   1. 如果设备从未上报过版本号, 在控制台网页将无法部署升级任务
         *   2. 如果设备升级完成后, 上报的不是新的版本号, 在控制台网页将会显示升级失败
         *
         */
    
        /* 演示MQTT连接建立起来之后, 就可以上报当前设备的版本号了 */
        cur_version = "1.0.0";
        res = aiot_ota_report_version(ota_handle, cur_version);
        if (res < STATE_SUCCESS) {
            printf("aiot_ota_report_version failed: -0x%04X\n", -res);
        }
  5. 建立MQTT保活线程和MQTT收包线程

    这些都是使用MQTT连云时候的正常操作,与OTA无关。

    /* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
        res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
        if (res < 0) {
            printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
            return -1;
        }
    
        /* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
        res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
        if (res < 0) {
            printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
            return -1;
        }
  6. OTA升级消息到达时, 启动下载线程

    以上程序主体已讲解完毕,此后,如果SDK从MQTT通道收到了升级的消息,则会开始下载。

    /* 用户通过 aiot_ota_setopt() 注册的OTA消息处理回调, 如果SDK收到了OTA相关的MQTT消息, 会自动识别, 调用这个回调函数 */
    void demo_ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata)
    {
        switch (ota_msg->type) {
            case AIOT_OTARECV_FOTA: {
                uint32_t last_percent = 0;
                uint32_t res = 0;
                uint16_t port = 443;
                uint32_t max_buffer_len = 2048;
                aiot_sysdep_network_cred_t cred;
                void *dl_handle = aiot_download_init();
    
                if (NULL == dl_handle) {
                    return;
                }
                memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
                cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;
                cred.max_tls_fragment = 16384;
                cred.x509_server_cert = ali_ca_crt;
                cred.x509_server_cert_len = strlen(ali_ca_crt);
    
                /* 设置下载时为TLS下载 */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred));
                /* 设置下载时访问的服务器端口号 */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port));
                /* 设置下载的任务信息, 通过输入参数 ota_msg 中的 task_desc 成员得到, 内含下载地址, 固件大小, 固件签名等 */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc));
                /* 设置下载内容到达时, SDK将调用的回调函数 */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler));
                /* 设置单次下载最大的buffer长度, 每当这个长度的内存读满了后会通知用户 */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len));
                /* 设置 AIOT_DLOPT_RECV_HANDLER 的不同次调用之间共享的数据, 比如例程把进度存在这里 */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_USER_DATA, (void *)&last_percent);
    
                /* 启动专用的下载线程, 去完成固件内容的下载 */
                res = pthread_create(&g_download_thread, NULL, demo_ota_download_thread, dl_handle);
                if (res < 0) {
                    printf("pthread_create demo_ota_download_thread failed: %d\n", res);
                    aiot_download_deinit(&dl_handle);
                } else {
                    /* 下载线程被设置为 detach 类型, 固件内容获取完毕后可自主退出 */
                    pthread_detach(g_download_thread);
                }
                break;
            }
    
            default:
                break;
        }
    }
  7. 专用下载线程获取固件内容

    以上代码启动了一个 demo_ota_download_thread 线程,它的逻辑很简单,就是去服务器下载固件的内容。

    /* 执行aiot_download_recv的线程, 实现固件内容的请求和接收 */
    void *demo_ota_download_thread(void *dl_handle)
    {
        int32_t     ret = 0;
    
        printf("starting download thread in 2 seconds ......\n");
        sleep(2);
    
        /* 向固件服务器请求下载 */
        /*
         * TODO: 下面这样的写法, 就是以1个请求, 获取全部的固件内容
         *       设备资源比较少, 或者网络较差时, 也可以分段下载, 需要组合
         *
         *       aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...);
         *       aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...);
         *       aiot_download_send_request(dl_handle);
         *
         *       实现, 这种情况下, 需要把以上组合语句放置到循环中, 多次 send_request 和 recv
         *
         */
        aiot_download_send_request(dl_handle);
        while (1) {
            /* 从网络收取服务器回应的固件内容 */
            ret = aiot_download_recv(dl_handle);
    
            /* 固件全部下载完时, aiot_download_recv() 的返回值会等于 STATE_DOWNLOAD_FINISHED, 否则是当次获取的字节数 */
            if (STATE_DOWNLOAD_FINISHED == ret) {
                printf("download completed\n");
                break;
            }
        }
    
        /* 下载所有固件内容完成, 销毁下载会话, 线程自行退出 */
        aiot_download_deinit(&dl_handle);
        printf("download thread exit\n");
    
        return NULL;
    }
  8. 处理下载内容, 写入Flash

    SDK收到的固件内容,会进入用户通过 aiot_download_setopt() 设置的收包回调,告知用户,用户应在这里保存下载内容。

    /* 下载收包回调, 用户调用 aiot_download_recv() 后, SDK收到数据会进入这个函数, 把下载到的数据交给用户 */
    /* TODO: 一般来说, 设备升级时, 会在这个回调中, 把下载到的数据写到Flash上 */
    void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata)
    {
        uint32_t data_buffer_len = 0;
        uint32_t last_percent = 0;
        int32_t  percent = 0;
    
        /* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
        if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
            return;
        }
        percent = packet->data.percent;
    
        if (userdata) {
            last_percent = *((uint32_t *)(userdata));
        }
        data_buffer_len = packet->data.len;
    
        /* 如果 percent 为负数, 说明发生了收包异常或者digest校验错误 */
        if (percent < 0) {
            printf("exception: percent = %d\n", percent);
            aiot_download_report_progress(handle, -2);
            return;
        }
    
        /*
         * TODO: 下载一段固件成功, 这个时候, 用户应该将
         *       起始地址为 packet->data.buffer, 长度为 packet->data.len 的内存, 保存到flash上
         *
         *       如果烧写flash失败, 还应该调用 aiot_download_report_progress(handle, -4) 将失败上报给云平台
         */
        /* percent 入参的值为 100 时, 说明SDK已经下载固件内容全部完成 */
        if (percent == 100) {
            /*
             * TODO: 这个时候, 一般用户就应该完成所有的固件烧录, 保存当前工作, 重启设备, 切换到新的固件上启动了
                     并且, 新的固件必须要以
    
                     aiot_ota_report_version(ota_handle, new_version);
    
                     这样的操作, 将升级后的新版本号(比如1.0.0升到1.1.0, 则new_version的值是"1.1.0")上报给云平台
                     云平台收到了新的版本号上报后, 才会判定升级成功, 否则会认为本次升级失败了
             */
        }
    
        /* 简化输出, 只有距离上次的下载进度增加5%以上时, 才会打印进度, 并把进度上报给云端 */
        if (percent - last_percent >= 5 || percent == 100) {
            printf("download %03d%% done, +%d bytes\n", percent, data_buffer_len);
            aiot_download_report_progress(handle, percent);
            if (userdata) {
                *((uint32_t *)(userdata)) = percent;
            }
        }
    }
  9. 观察运行日志

    以下就是编译运行 output/fota-posix-demo 后,从控制台网页操作推送固件1.1.0,得到的一次完整运行日志。

    验证固件
    $ ./output/fota-posix-demo
    
    [1579511737.377][LK-0313] MQTT user calls aiot_mqtt_connect api, connect
    [1579511737.377][LK-0317] ota_demo&a13FNXXXXXX
    [1579511737.377][LK-0318] 779ACA0181ED1C0BD4B1BA7A347C9B172E27EAAB4AA18FC1F9FAD4CED1047B51
    [1579511737.377][LK-0319] a13FNXXXXXX.ota_demo|timestamp=2524608000000,_ss=1,_v=sdk-c-4.0.0,securemode=2,signmethod=hmacsha256,ext=1,|
    establish mbedtls connection with server(host='a13FNXXXXXX.iot-as-mqtt.cn-shanghai.aliyuncs.com', port=[443])
    success to establish mbedtls connection, fd = 3(cost 44763 bytes in total, max used 47675 bytes)
    [1579511737.522][LK-0313] MQTT connect success in 152 ms
    AIOT_MQTTEVT_CONNECT
    [1579511737.522][LK-0309] pub: /ota/device/inform/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 30 2C  20 22 70 61 72 61 6D 73 | {"id":0, "params
    [LK-030A] > 22 3A 7B 22 76 65 72 73  69 6F 6E 22 3A 22 31 2E | ":{"version":"1.
    [LK-030A] > 30 2E 30 22 7D 7D                                | 0.0"}}
    
    heartbeat response
    heartbeat response
    heartbeat response
    heartbeat response
    [1579511826.577][LK-0309] pub: /ota/device/upgrade/a13FNXXXXXX/ota_demo
    
    [LK-030A] < 7B 22 63 6F 64 65 22 3A  22 31 30 30 30 22 2C 22 | {"code":"1000","
    [LK-030A] < 64 61 74 61 22 3A 7B 22  73 69 7A 65 22 3A 31 32 | data":{"size":12
    [LK-030A] < 34 38 33 30 36 2C 22 73  69 67 6E 22 3A 22 34 62 | 48306,"sign":"4b
    [LK-030A] < 66 61 63 65 66 35 63 62  61 30 61 35 36 64 63 32 | facef5cba0a56dc2
    [LK-030A] < 62 65 62 38 65 65 30 33  65 32 62 65 30 61 37 61 | beb8ee03e2be0a7a
    [LK-030A] < 65 64 65 65 65 31 38 38  35 61 63 65 30 66 62 35 | edeee1885ace0fb5
    [LK-030A] < 35 65 61 38 62 62 66 64  39 38 64 61 64 63 22 2C | 5ea8bbfd98dadc",
    [LK-030A] < 22 76 65 72 73 69 6F 6E  22 3A 22 31 2E 31 2E 30 | "version":"1.1.0
    [LK-030A] < 22 2C 22 75 72 6C 22 3A  22 68 74 74 70 73 3A 2F | ","url":"https:/
    [LK-030A] < 2F 6F 74 61 2E 69 6F 74  2D 74 68 69 6E 67 2E 63 | /ota.iot-thing.c
    [LK-030A] < 6E 2D 73 68 61 6E 67 68  61 69 2E 61 6C 69 79 75 | n-shanghai.aliyu
    [LK-030A] < 6E 63 73 2E 63 6F 6D 2F  6F 74 61 2F 36 35 64 66 | ncs.com/ota/65df
    [LK-030A] < 63 64 61 30 34 37 33 62  65 32 39 38 33 36 64 66 | cda0473be29836df
    [LK-030A] < 64 65 35 38 35 34 37 32  37 65 31 32 2F 63 6B 35 | de5854727e12/ck5
    [LK-030A] < 32 6E 79 6F 31 7A 30 30  30 30 33 39 37 69 69 79 | 2nyo1z0000397iiy
    [LK-030A] < 76 68 6E 72 32 69 2E 62  69 6E 3F 45 78 70 69 72 | vhnr2i.bin?Expir
    [LK-030A] < 65 73 3D 31 35 37 39 35  39 38 32 32 36 26 4F 53 | es=1579598226&OS
    [LK-030A] < 53 41 63 63 65 73 73 4B  65 79 49 64 3D 63 53 38 | SAccessKeyId=cS8
    [LK-030A] < 75 52 52 79 35 34 52 73  7A 59 57 6E 61 26 53 69 | uRRy54RszYWna&Si
    [LK-030A] < 67 6E 61 74 75 72 65 3D  42 4F 55 46 51 65 34 68 | gnature=BOUFQe4h
    [LK-030A] < 39 6E 36 41 64 5A 57 46  42 45 49 7A 33 5A 6A 70 | 9n6AdZWFBEIz3Zjp
    [LK-030A] < 42 70 59 25 33 44 22 2C  22 73 69 67 6E 4D 65 74 | BpY%3D","signMet
    [LK-030A] < 68 6F 64 22 3A 22 53 48  41 32 35 36 22 7D 2C 22 | hod":"SHA256"},"
    [LK-030A] < 69 64 22 3A 31 35 37 39  35 31 31 38 32 36 35 32 | id":157951182652
    [LK-030A] < 30 2C 22 6D 65 73 73 61  67 65 22 3A 22 73 75 63 | 0,"message":"suc
    [LK-030A] < 63 65 73 73 22 7D                                | cess"}
    
    OTA target firmware version: 1.1.0, size: 1248306 Bytes
    starting download thread in 2 seconds ......
    establish mbedtls connection with server(host='ota.iot-thing.cn-shanghai.aliyuncs.com', port=[443])
    success to establish mbedtls connection, fd = 4(cost 44868 bytes in total, max used 47604 bytes)
    [1579511828.699][LK-040B] > GET /ota/65dfcda0473be29836dfde5854727e12/ck52nyo1z0000397iiyvhnr2i.bin?Expires=1579598226&OSSAccessKeyId=cS8uRRy54RszYWna&Signature
    [1579511828.699][LK-040B] > Host: ota.iot-thing.cn-shanghai.aliyuncs.com
    [1579511828.699][LK-040B] > Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    [1579511828.699][LK-040B] > Range: bytes=0-
    [1579511828.699][LK-040B] > Content-Length: 0
    [1579511828.699][LK-040B] >
    [1579511828.699][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 2C  20 22 70 61 72 61 6D 73 | {"id":1, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 30 22 2C 22 64 | ":{"step":"0","d
    [LK-030A] > 65 73 63 22 3A 22 22 7D  7D                      | esc":""}}
    
    [1579511828.755][LK-040D] < HTTP/1.1 206 Partial Content
    [1579511828.755][LK-040D] < Date: Mon, 20 Jan 2020 09:17:08 GMT
    [1579511828.755][LK-040D] < Content-Type: application/octet-stream
    [1579511828.755][LK-040D] < Content-Length: 1248306
    [1579511828.755][LK-040D] < Connection: keep-alive
    [1579511828.755][LK-040D] < x-oss-request-id: 5E2570140838A332336C4642
    [1579511828.755][LK-040D] < Content-Range: bytes 0-1248305/1248306
    [1579511828.755][LK-040D] < Accept-Ranges: bytes
    [1579511828.755][LK-040D] < ETag: "4818CE191A4E2A93EE2DF17938286F86"
    [1579511828.755][LK-040D] < Last-Modified: Mon, 06 Jan 2020 16:32:55 GMT
    [1579511828.755][LK-040D] < x-oss-object-type: Normal
    [1579511828.755][LK-040D] < x-oss-hash-crc64ecma: 14290028752135618122
    [1579511828.755][LK-040D] < x-oss-storage-class: Standard
    [1579511828.755][LK-040D] < Content-MD5: SBjOGRpOKpPuLfF5OChvhg==
    [1579511828.755][LK-040D] < x-oss-server-time: 47
    [1579511828.755][LK-040D] <
    download 005% done, +8192 bytes
    [1579511828.777][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 32 2C  20 22 70 61 72 61 6D 73 | {"id":2, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 35 22 2C 22 64 | ":{"step":"5","d
    [LK-030A] > 65 73 63 22 3A 22 22 7D  7D                      | esc":""}}
    
    download 010% done, +8192 bytes
    [1579511828.788][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 33 2C  20 22 70 61 72 61 6D 73 | {"id":3, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 31 30 22 2C 22 | ":{"step":"10","
    [LK-030A] > 64 65 73 63 22 3A 22 22  7D 7D                   | desc":""}}
    
    download 015% done, +8192 bytes
    [1579511828.788][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 34 2C  20 22 70 61 72 61 6D 73 | {"id":4, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 31 35 22 2C 22 | ":{"step":"15","
    [LK-030A] > 64 65 73 63 22 3A 22 22  7D 7D                   | desc":""}}
    
    download 020% done, +8192 bytes
    [1579511828.799][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 35 2C  20 22 70 61 72 61 6D 73 | {"id":5, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 32 30 22 2C 22 | ":{"step":"20","
    [LK-030A] > 64 65 73 63 22 3A 22 22  7D 7D                   | desc":""}}
    
    download 025% done, +8192 bytes
    [1579511828.800][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 36 2C  20 22 70 61 72 61 6D 73 | {"id":6, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 32 35 22 2C 22 | ":{"step":"25","
    [LK-030A] > 64 65 73 63 22 3A 22 22  7D 7D                   | desc":""}}
    
    download 030% done, +8192 bytes
    [1579511828.800][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 37 2C  20 22 70 61 72 61 6D 73 | {"id":7, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 33 30 22 2C 22 | ":{"step":"30","
    [LK-030A] > 64 65 73 63 22 3A 22 22  7D 7D                   | desc":""}}
    
    download 035% done, +8192 bytes
    [1579511828.811][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 38 2C  20 22 70 61 72 61 6D 73 | {"id":8, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 33 35 22 2C 22 | ":{"step":"35","
    [LK-030A] > 64 65 73 63 22 3A 22 22  7D 7D                   | desc":""}}
    
    download 040% done, +8192 bytes
    [1579511828.811][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 39 2C  20 22 70 61 72 61 6D 73 | {"id":9, "params
    [LK-030A] > 22 3A 7B 22 73 74 65 70  22 3A 22 34 30 22 2C 22 | ":{"step":"40","
    [LK-030A] > 64 65 73 63 22 3A 22 22  7D 7D                   | desc":""}}
    
    download 045% done, +8192 bytes
    [1579511828.811][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 30  2C 20 22 70 61 72 61 6D | {"id":10, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 34 35 22 2C | s":{"step":"45",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 050% done, +8192 bytes
    [1579511828.822][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 31  2C 20 22 70 61 72 61 6D | {"id":11, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 35 30 22 2C | s":{"step":"50",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 055% done, +8192 bytes
    [1579511828.822][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 32  2C 20 22 70 61 72 61 6D | {"id":12, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 35 35 22 2C | s":{"step":"55",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 060% done, +8192 bytes
    [1579511828.822][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 33  2C 20 22 70 61 72 61 6D | {"id":13, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 36 30 22 2C | s":{"step":"60",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 065% done, +8192 bytes
    [1579511828.822][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 34  2C 20 22 70 61 72 61 6D | {"id":14, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 36 35 22 2C | s":{"step":"65",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 070% done, +8192 bytes
    [1579511828.822][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 35  2C 20 22 70 61 72 61 6D | {"id":15, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 37 30 22 2C | s":{"step":"70",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 075% done, +8192 bytes
    [1579511828.833][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 36  2C 20 22 70 61 72 61 6D | {"id":16, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 37 35 22 2C | s":{"step":"75",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 080% done, +8192 bytes
    [1579511828.833][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 37  2C 20 22 70 61 72 61 6D | {"id":17, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 38 30 22 2C | s":{"step":"80",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 085% done, +8192 bytes
    [1579511828.833][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 38  2C 20 22 70 61 72 61 6D | {"id":18, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 38 35 22 2C | s":{"step":"85",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 090% done, +8192 bytes
    [1579511828.833][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 31 39  2C 20 22 70 61 72 61 6D | {"id":19, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 39 30 22 2C | s":{"step":"90",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    download 095% done, +8192 bytes
    [1579511828.833][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 32 30  2C 20 22 70 61 72 61 6D | {"id":20, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 39 35 22 2C | s":{"step":"95",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    [1579511828.833][LK-0901] digest matched
    download 100% done, +3122 bytes
    [1579511828.833][LK-0309] pub: /ota/device/progress/a13FNXXXXXX/ota_demo
    
    [LK-030A] > 7B 22 69 64 22 3A 32 31  2C 20 22 70 61 72 61 6D | {"id":21, "param
    [LK-030A] > 73 22 3A 7B 22 73 74 65  70 22 3A 22 31 30 30 22 | s":{"step":"100"
    [LK-030A] > 2C 22 64 65 73 63 22 3A  22 22 7D 7D             | ,"desc":""}}
    
    download completed
    download thread exit
    heartbeat response