阿里云物联网平台提供了远程配置功能,让用户可以将产品的配置以文件方式上传到物联网平台,设备上线后将配置文件下载到设备进行解析与处理。

API列表

注意 远程配置不能针对单个设备设置不同的配置文件,请参见远程配置说明了解更多细节。

远程配置包括两部分的内容, 从MQTT通道接收到远程配置的URL的环节, 与从HTTP通道下载远程配置的环节, 是分开的(都在components/ota/aiot_ota_api.h中声明)。

  • 远程配置信息推送

    这部分API主要基于MQTT连接工作, 用于完成设备接收到云端下推的远程配置信息, 比如下载地址。

    aiot_ota_init 创建ota客户端实例并设置默认参数。
    aiot_ota_deinit 销毁ota客户端实例, 回收资源。
    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选项设置的用户回调, 将下载内容传给用户。

API使用概述

OTA模块可用于配合阿里云平台的远程配置服务, 有关介绍远程配置的控制操作请参见远程配置

了解设备端配合远程配置的网络交互流程,请参见远程配置

注意
  • 在控制台设置远程配置的内容后, 设备需要主动向控制台发起请求, 或者控制台主动下推, 设备才能收到远程配置的下载信息(包括url等)。
  • 收到远程配置的下载信息后, 设备自行用aiot_download_send_request 向云端发起下载请求, SDK不会自动下载远程配置。
  • 设备下载中或者下载完成后, 设备需自行处理内存buffer中的远程配置, SDK不含有处理远程配置的逻辑。
  • 远程配置内容大小的上限是64KB。
  • 用户在控制台推送远程配置的下载地址等信息给设备时, 是通过MQTT通道, 所以设备获取远程配置的前提是成功建立并保持MQTT长连接通道在线。
  • aiot_ota_setopt

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

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

    用户解析完OTA消息, 知道了远程配置的下载地址之后, 就可以用这个接口以HTTP下载远程配置内容。

    被下载到的内容, 会通过回调函数传递给用户, 用户调用 aiot_download_setopt 把自己的数据处理回调函数设置给SDK。

  • 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_send_request

    设备通过OTA消息回调函数知道了远程配置的下载地址后, 就可以调用这个接口, 下载远程配置。

例程讲解

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

远程配置流程

这个例程适用于演示了用SDK配置MQTT参数并建立连接, 之后向云端发起获取远程配置的请求. 在收到云端的回复后, 根据回复报文中的远程配置的url, 发起下载请求并开始下载。

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

  1. 设置设备证书

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

    /* TODO: 替换为自己设备的设备证书 */
    char *product_key       = "a1dhWXXXXXX";
    char *device_name       = "cota_basic_demo";
    char *device_secret     = "wTvkvSgmYiCULwzi5IqsxEoN6iXXXXXX";
  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) {
            printf("aiot_mqtt_connect failed: -0x%04X\r\n", -res);
            /* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
            goto exit;
        }
    说明 上述代码段同时也给OTA会话设置了数据接收回调, 为 demo_ota_recv_handler 函数。
  4. 设备请求获取远程配置
    /* 发送请求, 获取云端的远程配置 */
        {
            char *topic_string = "/sys/a1dhWKuKqX5/cota_basic_demo/thing/config/get";
            char *payload_string = "{\"id\":\"123\",\"params\":{\"configScope\":\"product\",\"getType\":\"file\"}}";
    
            res = aiot_mqtt_pub(mqtt_handle, topic_string, (uint8_t *)payload_string, strlen(payload_string), 0);
            if (res < STATE_SUCCESS) {
                printf("aiot_mqtt_pub failed: -0x%04X\r\n", -res);
                /* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
                goto exit;
            }
        }
  5. 从网络上轮询mqtt报文和远程配置的报文

    如果g_dl_handle指针会为空, 则只从网络上轮询mqtt报文。

    如果g_dl_handle指针为非空, 会去调用aiot_download_recv函数通过。

    https协议去服务器收取远程配置, 结束后会通过aiot_download_deinit把g_dl_handle句柄置空。

    while (1) {
            aiot_mqtt_process(mqtt_handle);
            res = aiot_mqtt_recv(mqtt_handle);
    
            if (res == STATE_SYS_DEPEND_NWK_CLOSED) {
                sleep(1);
            }
            if (NULL != g_dl_handle) {
                /* 完成远程配置的接收前, 将mqtt的收包超时调整到100ms, 以减少两次远程配置的下载间隔*/
                int32_t ret = aiot_download_recv(g_dl_handle);
                timeout_ms = 100;
    
                if (STATE_DOWNLOAD_FINISHED == ret) {
                    aiot_download_deinit(&g_dl_handle);
                    /* 完成远程配置的接收后, 将mqtt的收包超时调整回到默认值5000ms */
                    timeout_ms = 5000;
                }
                aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_TIMEOUT_MS, (void *)&timeout_ms);
            }
        }
  6. 远程配置消息到达, 开始下载

    以上程序主体已讲解完毕, 此后, 如果SDK从MQTT通道收到了远程配置的消息, 则会调用aiot_download_send_request向服务器发起下载请求。

    把g_dl_handle指针赋一个非空的值, 触发上述while循环从网络上轮询远程配置的报文。

    /* 用户通过 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_COTA: {
                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 || NULL == ota_msg->task_desc) {
                    return;
                }
    
                printf("configId: %s, configSize: %u Bytes\r\n", ota_msg->task_desc->version,
                       ota_msg->task_desc->size_total);
    
                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下载 */
                if ((STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred))) ||
                    /* 设置下载时访问的服务器端口号 */
                    (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port))) ||
                    /* 设置下载的任务信息, 通过输入参数 ota_msg 中的 task_desc 成员得到, 内含下载地址, 远程配置的大小和版本号*/
                    (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc))) ||
                    /* 设置下载内容到达时, SDK将调用的回调函数 */
                    (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(user_download_recv_handler))) ||
                    /* 设置单次下载最大的buffer长度, 每当这个长度的内存读满了后会通知用户 */
                    (STATE_SUCCESS != aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len))) ||
                    /* 发送http的GET请求给http服务器 */
                    (STATE_SUCCESS != aiot_download_send_request(dl_handle))) {
                    if (res != STATE_SUCCESS) {
                        aiot_download_deinit(&dl_handle);
                        break;
                    }
                }
                g_dl_handle = dl_handle;
                break;
            }
            default:
                break;
        }
    }
  7. 处理下载内容

    SDK收到的远程配置内容, 会进入用户通过 aiot_download_setopt() 设置的收包回调, 告知用户, 用户应在这里处理下载内容。

    /* 下载收包回调, 用户调用 aiot_download_recv() 后, SDK收到数据会进入这个函数, 把下载到的数据交给用户 */
    /* TODO: 在用户收到远程配置的数据后, 需要自己决定如何处理 */
    void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata)
    {
        /* 目前只支持 packet->type 为 AIOT_DLRECV_HTTPBODY 的情况 */
        if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
            return;
        }
        int32_t percent = packet->data.percent;
        uint8_t *src_buffer = packet->data.buffer;
        uint32_t src_buffer_len = packet->data.len;
    
        /* 如果 percent 为负数, 说明发生了收包异常或者digest校验错误 */
        if (percent < 0) {
            /* 收包异常或者digest校验错误 */
            printf("exception happend, percent is %d\r\n", percent);
            return;
        }
        aiot_download_report_progress(handle, percent);
        printf("config len is %d, config content is %.*s\r\n", src_buffer_len, src_buffer_len, (char *)src_buffer);
    }
  8. 观察运行日志

    以下就是在控制台网页部署完远程配置信息后, 编译运行 output/cota-basic-demo, 设备向控制台发送远程配置请求从而获得控制台推送的远程配置, 得到的一次完整运行日志。

    上传远程配置
    $./output/cota-basic-demo
    [1582113704.433][LK-0313] MQTT user calls aiot_mqtt_connect api, connect
    [1582113704.433][LK-0317] cota_basic_demo&a1dhWKuKqX5
    [1582113704.433][LK-0318] AD97391F4E5BAA6FFB7F62F9D15EA6FD1ED5AB20E82B73686E21D44110E6BE51
    [1582113704.433][LK-0319] a1dhWKuKqX5.cota_basic_demo|timestamp=2524608000000,_ss=1,_v=sdk-c-4.0.0,securemode=2,signmethod=hmacsha256,ext=1,|
    establish mbedtls connection with server(host='a1dhWKuKqX5.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)
    [1582113704.488][LK-0313] MQTT connect success in 46 ms
    AIOT_MQTTEVT_CONNECT
    [1582113704.488][LK-0309] pub: /sys/a1dhWXXXXXX/cota_basic_demo/thing/config/get
    
    [LK-030A] > 7B 22 69 64 22 3A 22 31  32 33 22 2C 22 70 61 72 | {"id":"123","par
    [LK-030A] > 61 6D 73 22 3A 7B 22 63  6F 6E 66 69 67 53 63 6F | ams":{"configSco
    [LK-030A] > 70 65 22 3A 22 70 72 6F  64 75 63 74 22 2C 22 67 | pe":"product","g
    [LK-030A] > 65 74 54 79 70 65 22 3A  22 66 69 6C 65 22 7D 7D | etType":"file"}}
    
    [1582113704.511][LK-0309] pub: /sys/a1dhWXXXXXX/cota_basic_demo/thing/config/get_reply
    
    [LK-030A] < 7B 22 63 6F 64 65 22 3A  32 30 30 2C 22 64 61 74 | {"code":200,"dat
    [LK-030A] < 61 22 3A 7B 22 63 6F 6E  66 69 67 49 64 22 3A 22 | a":{"configId":"
    [LK-030A] < 31 31 64 32 38 62 34 31  32 36 62 61 34 66 62 33 | 11d28b4126ba4fb3
    [LK-030A] < 61 30 61 63 61 64 31 32  32 63 61 37 31 37 65 61 | a0acad122ca717ea
    [LK-030A] < 22 2C 22 63 6F 6E 66 69  67 53 69 7A 65 22 3A 32 | ","configSize":2
    [LK-030A] < 37 2C 22 67 65 74 54 79  70 65 22 3A 22 66 69 6C | 7,"getType":"fil
    [LK-030A] < 65 22 2C 22 73 69 67 6E  22 3A 22 38 37 39 65 35 | e","sign":"879e5
    [LK-030A] < 39 31 33 34 63 36 61 34  34 39 35 34 37 63 62 33 | 9134c6a449547cb3
    [LK-030A] < 63 65 32 35 66 64 32 38  32 63 64 61 65 66 30 34 | ce25fd282cdaef04
    [LK-030A] < 36 65 38 64 34 38 34 30  65 32 35 63 30 31 63 64 | 6e8d4840e25c01cd
    [LK-030A] < 66 39 66 32 38 64 66 64  66 62 64 22 2C 22 73 69 | f9f28dfdfbd","si
    [LK-030A] < 67 6E 4D 65 74 68 6F 64  22 3A 22 53 68 61 32 35 | gnMethod":"Sha25
    [LK-030A] < 36 22 2C 22 75 72 6C 22  3A 22 68 74 74 70 73 3A | 6","url":"https:
    [LK-030A] < 2F 2F 6F 74 78 2D 64 65  76 69 63 65 63 65 6E 74 | //otx-devicecent
    [LK-030A] < 65 72 2D 74 68 69 6E 67  2D 63 6F 6E 66 69 67 2D | er-thing-config-
    [LK-030A] < 63 6E 2D 73 68 61 6E 67  68 61 69 2D 6F 6E 6C 69 | cn-shanghai-onli
    [LK-030A] < 6E 65 2E 6F 73 73 2D 63  6E 2D 73 68 61 6E 67 68 | ne.oss-cn-shangh
    [LK-030A] < 61 69 2E 61 6C 69 79 75  6E 63 73 2E 63 6F 6D 2F | ai.aliyuncs.com/
    [LK-030A] < 61 31 64 68 57 4B 75 4B  71 58 35 2F 31 35 38 30 | a1dhWKuKqX5/1580
    [LK-030A] < 30 35 33 36 34 33 30 36  31 5F 51 4A 6A 4F 66 43 | 053643061_QJjOfC
    [LK-030A] < 78 43 3F 45 78 70 69 72  65 73 3D 31 35 38 32 31 | xC?Expires=15821
    [LK-030A] < 31 35 35 30 34 26 4F 53  53 41 63 63 65 73 73 4B | 15504&OSSAccessK
    [LK-030A] < 65 79 49 64 3D 4C 54 41  49 52 59 33 72 78 35 64 | eyId=LTAIRY3rx5d
    [LK-030A] < 67 32 4A 42 6D 26 53 69  67 6E 61 74 75 72 65 3D | g2JBm&Signature=
    [LK-030A] < 36 69 50 63 6A 6E 51 30  65 6F 75 50 63 44 56 25 | 6iPcjnQ0eouPcDV%
    [LK-030A] < 32 42 78 77 5A 4B 65 78  30 31 78 51 63 25 33 44 | 2BxwZKex01xQc%3D
    [LK-030A] < 22 7D 2C 22 69 64 22 3A  22 31 32 33 22 2C 22 6D | "},"id":"123","m
    [LK-030A] < 65 74 68 6F 64 22 3A 22  74 68 69 6E 67 2E 63 6F | ethod":"thing.co
    [LK-030A] < 6E 66 69 67 2E 67 65 74  22 2C 22 76 65 72 73 69 | nfig.get","versi
    [LK-030A] < 6F 6E 22 3A 22 31 2E 30  22 7D                   | on":"1.0"}
    
    configId: 11d28b4126ba4fb3a0acad122ca717ea, configSize: 27 Bytes
    establish mbedtls connection with server(host='otx-devicecenter-thing-config-cn-shanghai-online.oss-cn-shanghai.aliyuncs.com', port=[443])
    success to establish mbedtls connection, fd = 4(cost 54383 bytes in total, max used 57119 bytes)
    [1582113704.566][LK-040B] > GET /a1dhWKuKqX5/1580053643061_QJjOfCxC?Expires=1582115504&OSSAccessKeyId=LTAIRY3rx5dg2JBm&Signature=6iPcjnQ0eouPcDV%2BxwZKex01xQc%3
    [1582113704.566][LK-040B] > Host: otx-devicecenter-thing-config-cn-shanghai-online.oss-cn-shanghai.aliyuncs.com
    [1582113704.566][LK-040B] > Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
    [1582113704.566][LK-040B] > Range: bytes=0-
    [1582113704.566][LK-040B] > Content-Length: 0
    [1582113704.566][LK-040B] >
    [1582113704.566][LK-0309] pub: /ota/device/progress/a1dhWKuKqX5/cota_basic_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 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":""}}
    
    [1582113704.588][LK-040D] < HTTP/1.1 206 Partial Content
    [1582113704.588][LK-040D] < Server: AliyunOSS
    [1582113704.588][LK-040D] < Date: Wed, 19 Feb 2020 12:01:44 GMT
    [1582113704.588][LK-040D] < Content-Type: application/octet-stream
    [1582113704.588][LK-040D] < Content-Length: 27
    [1582113704.588][LK-040D] < Connection: keep-alive
    [1582113704.588][LK-040D] < x-oss-request-id: 5E4D23A841C1B03839D1C7A9
    [1582113704.588][LK-040D] < Content-Range: bytes 0-26/27
    [1582113704.588][LK-040D] < Accept-Ranges: bytes
    [1582113704.588][LK-040D] < ETag: "5BBE66A8F10381667E587A8F3CB1877C"
    [1582113704.588][LK-040D] < Last-Modified: Sun, 26 Jan 2020 15:47:23 GMT
    [1582113704.588][LK-040D] < x-oss-object-type: Normal
    [1582113704.588][LK-040D] < x-oss-hash-crc64ecma: 14994493172188797654
    [1582113704.588][LK-040D] < x-oss-storage-class: Standard
    [1582113704.588][LK-040D] < Content-MD5: W75mqPEDgWZ+WHqPPLGHfA==
    [1582113704.588][LK-040D] < x-oss-server-time: 22
    [1582113704.588][LK-040D] <
    [1582113704.588][LK-0901] digest matched
    [1582113704.588][LK-0309] pub: /ota/device/progress/a1dhWKuKqX5/cota_basic_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 31 30 30 22 2C | ":{"step":"100",
    [LK-030A] > 22 64 65 73 63 22 3A 22  22 7D 7D                | "desc":""}}
    
    config len is 27, config content is {"remote_config_demo":1234}
    heartbeat response

    远程配置的关键日志举例:configId: 11d28b4126ba4fb3a0acad122ca717ea, configSize: 27 Bytes

    固件升级的关键日志举例:OTA target firmware version: 1.1.0, size: 1248306 Bytes

    从上述关键关键日志中, 可以区分当前是远程配置还是固件升级。