本文介绍C SDK通过HTTPS方式进行动态注册的使用方法,目前HTTPS方式的动态注册不支持免白名单功能。

API列表

以下是完整的API列表及简要说明 (详见components/dynreg/aiot_dynreg_api.h)。
接口名 说明
aiot_dynreg_init 初始化dynreg实例并设置默认参数。
aiot_dynreg_setopt 配置dynreg实例, 详见选项配置说明
aiot_dynreg_deinit 释放dynreg实例句柄的资源。
aiot_dynreg_send_request 向dynreg服务器发送请求。
aiot_dynreg_recv 从dynreg服务器接收应答。

特别说明

如果设备通过动态注册拿到了设备的DeviceSecret,但是并没有使用该DeviceSecret去连接阿里云物联网平台,这个时候可以再次通过动态注册去获取DeviceSecret,云端将会返回新的DeviceSecret。

如果一个设备通过动态注册获取到了DeviceSecret,并使用该DeviceSecret将设备连接到了阿里云物联网平台,那么再次调用动态注册接口去获取DeviceSecret将会返回失败。因此设备获取到了DeviceSecret之后需要将其持久化保存到设备上,设备重启、重置也不能丢失DeviceSecret

当设备上电时如果发现自己已经获取到了DeviceSecret,直接使用ProductKey、DeviceName、DeviceSecret连接物联网平台即可,无需再次调用动态注册去获取DeviceSecret。

例程讲解

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

API调用流程

这个例程演示了用SDK配置dynreg会话实例的参数,并发起请求和接收应答,之后:

  • 如果接收应答失败了, 销毁实例, 回收资源, 结束程序退出。
  • 如果接收应答成功, 在demo_dynreg_recv_handler()的应答处理回调函数中, 演示解析获取服务端应答的内容。
说明 需要用户关注或修改的部分, 已用 TODO 在注释中标明。
  1. 设置设备的设备证书

    请替换示例程序中如下的TODO部分, 传入客户自己设备的ProductKey、ProductSecret和DeviceName:

    /* TODO: 替换为自己设备的productKey, productSecret和deviceName */
        char *product_key       = "a13FNXXXXXX";
        char *product_secret    = "y7GSILD480XXXXXX";
        char *device_name       = "dynreg_basic_demo";
  2. 给SDK配置全局的底层依赖和日志回调

    底层依赖描述了硬件平台的资源使用方式,比如怎样获取时钟、分配内存等, 日志回调是用户的函数,SDK有log输出的时候会进入这个函数。

    int main(int argc, char *argv[])
    {
        ...
        ...
        /* 配置SDK的底层依赖 */
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        /* 配置SDK的日志输出 */
        aiot_state_set_logcb(demo_state_logcb);
  3. 配置动态注册参数

    这些连接参数包含如何建立TLS连接、服务器地址、连接后的数据及事件回调函数等。

    /* 创建1个dynreg客户端实例并内部初始化默认参数 */
        dynreg_handle = aiot_dynreg_init();
        if (dynreg_handle == NULL) {
            printf("aiot_dynreg_init failed\n");
            return -1;
        }
        /* 配置连接的服务器地址 */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_HOST, (void *)host);
        /* 配置连接的服务器端口 */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_PORT, (void *)&port);
        /* 配置设备productKey */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_PRODUCT_KEY, (void *)product_key);
        /* 配置设备productSecret */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_PRODUCT_SECRET, (void *)product_secret);
        /* 配置设备deviceName */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_DEVICE_NAME, (void *)device_name);
        /* 配置网络连接的安全凭据, 上面已经创建好了 */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_NETWORK_CRED, (void *)&cred);
        /* 配置DYNREG默认消息接收回调函数 */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_RECV_HANDLER, (void *)demo_dynreg_recv_handler);
        /* 设置用户上下文,该上下文会在 demo_dynreg_recv_handler 被调用时传回 */
        aiot_dynreg_setopt(dynreg_handle, AIOT_DYNREGOPT_USERDATA, (void *)&demo_info);
        ...
        ...
  4. 向服务器发送服务请求

    调用aiot_dynreg_send_request()接口,以上面设置的参数,向dynreg服务器发起服务请求。

    /* 发送动态注册请求 */
        res = aiot_dynreg_send_request(dynreg_handle);
        if (res < STATE_SUCCESS) {
            printf("aiot_dynreg_send_request failed: -0x%04X\n", -res);
            return -1;
        }
  5. 以下日志对应被发送的服务请求
    [1580997987.888][LK-040B] > POST /auth/register/device HTTP/1.1
    [1580997987.888][LK-040B] > Host: iot-auth.cn-shanghai.aliyuncs.com
    [1580997987.888][LK-040B] > Accept: text/xml,text/javascript,text/html,application/json
    [1580997987.888][LK-040B] > Content-Type: application/x-www-form-urlencoded
    [1580997987.888][LK-040B] > Content-Length: 160
    [1580997987.888][LK-040B] >
    [LK-040C] > 70 72 6F 64 75 63 74 4B  65 79 3D 61 31 33 46 4E | productKey=a13FN
    [LK-040C] > 35 54 70 6C 4B 71 26 64  65 76 69 63 65 4E 61 6D | 5TplKq&deviceNam
    [LK-040C] > 65 3D 64 79 6E 72 65 67  5F 62 61 73 69 63 5F 64 | e=dynreg_basic_d
    [LK-040C] > 65 6D 6F 26 72 61 6E 64  6F 6D 3D 35 30 35 31 37 | emo&random=50517
    [LK-040C] > 31 39 37 33 26 73 69 67  6E 3D 38 32 41 34 30 33 | 1973&sign=82A403
    [LK-040C] > 38 46 32 35 37 36 38 34  39 31 32 34 34 36 36 36 | 8F25768491244666
    [LK-040C] > 43 30 34 30 46 33 38 46  45 44 42 33 39 30 32 37 | C040F38FEDB39027
    [LK-040C] > 37 30 39 35 46 45 46 37  34 43 46 36 43 37 32 34 | 7095FEF74CF6C724
    [LK-040C] > 39 43 32 42 41 38 45 31  43 34 26 73 69 67 6E 4D | 9C2BA8E1C4&signM
    [LK-040C] > 65 74 68 6F 64 3D 68 6D  61 63 73 68 61 32 35 36 | ethod=hmacsha256
  6. 处理服务应答

    使用aiot_dynreg_recv()接口, 以上面设置的参数, 从dynreg服务器接收服务应答。

    /* 接收动态注册请求 */
        res = aiot_dynreg_recv(dynreg_handle);
        if (res < STATE_SUCCESS) {
            printf("aiot_dynreg_recv failed: -0x%04X\n", -res);
            return -1;
        }

    服务应答后日志展示如下:

    [1580997987.944][LK-040D] < HTTP/1.1 200 OK
    [1580997987.944][LK-040D] < Server: Tengine
    [1580997987.944][LK-040D] < Date: Thu, 06 Feb 2020 14:06:27 GMT
    [1580997987.944][LK-040D] < Content-Type: text/xml
    [1580997987.944][LK-040D] < Content-Length: 151
    [1580997987.944][LK-040D] < Connection: keep-alive
    [1580997987.944][LK-040D] <
    [LK-040E] < 7B 22 63 6F 64 65 22 3A  32 30 30 2C 22 64 61 74 | {"code":200,"dat
    [LK-040E] < 61 22 3A 7B 22 64 65 76  69 63 65 4E 61 6D 65 22 | a":{"deviceName"
    [LK-040E] < 3A 22 64 79 6E 72 65 67  5F 62 61 73 69 63 5F 64 | :"dynreg_basic_d
    [LK-040E] < 65 6D 6F 22 2C 22 64 65  76 69 63 65 53 65 63 72 | emo","deviceSecr
    [LK-040E] < 65 74 22 3A 22 58 6E 59  75 38 36 6B 71 58 54 39 | et":"XnYu8XXXXXX
    [LK-040E] < 34 6A 6A 32 65 72 5A 58  74 7A 4C 56 58 64 45 77 | 4jj2erZXtzLVXdEw
    [LK-040E] < 55 49 43 57 48 22 2C 22  70 72 6F 64 75 63 74 4B | UICWH","productK
    [LK-040E] < 65 79 22 3A 22 61 31 33  46 4E 35 54 70 6C 4B 71 | ey":"a13FN5TplKq
    [LK-040E] < 22 7D 2C 22 6D 65 73 73  61 67 65 22 3A 22 73 75 | "},"message":"su
    [LK-040E] < 63 63 65 73 73 22 7D                             | ccess"}
  7. 应答接收成功后,在回调函数中解析应答内容

    之前已经用AIOT_DYNREGOPT_RECV_HANDLER选项设置了,SDK收到应答时应进入的数据处理函数是demo_dynreg_recv_handler()

    /* 数据处理回调, 当SDK从网络上收到dynreg消息时被调用 */
    void demo_dynreg_recv_handler(void *handle, const aiot_dynreg_recv_t *packet, void *userdata)
    {
        demo_info_t *demo_info = (demo_info_t *)userdata;
        switch (packet->type) {
            case AIOT_DYNREGRECV_STATUS_CODE: {
                demo_info->code = packet->data.status_code.code;
            }
            break;
            /* TODO: 回调中需要将packet指向的空间内容复制保存好, 因为回调返回后, 这些空间就会被SDK释放 */
            case AIOT_DYNREGRECV_DEVICE_INFO: {
                demo_info->device_secret = malloc(strlen(packet->data.device_info.device_secret) + 1);
                if (demo_info->device_secret != NULL) {
                    memset(demo_info->device_secret, 0, strlen(packet->data.device_info.device_secret) + 1);
                    memcpy(demo_info->device_secret, packet->data.device_info.device_secret,
                           strlen(packet->data.device_info.device_secret));
                }
            }
            break;
            default: {
            }
            break;
        }
    }

    动态服务服务应答已经被储存在回调函数的入参packet中, 在回调中需要将它的内容复制到demo_info, 因为回调返回后, SDK就要将相关的内存空间释放掉了。

  8. 打印服务应答的消息内容

    aiot_dynreg_recv()接口返回后(其中经过了触发并执行demo_dynreg_recv_handler()), 服务器应答内容已被保存, 将它们打印出来。

    /* 把服务应答中的 deviceSecret 打印出来 */
        if (demo_info.device_secret != NULL) {
            printf("device secret: %s\n", demo_info.device_secret);
            free(demo_info.device_secret);
        }

    实际的业务使用中, 应答内容自然不只是被打印出来, 对其中的设备共享密钥deviceSecret, 一般会被保存, 然后传给SDK的MQTT接口, 用于建立MQTT长连接。

  9. 销毁会话实例, 释放资源退出

    动态注册的主要功能:获取设备共享密钥(deviceSecret)已经演示完毕, 例程到这里就清理资源, 正常退出了。

    /* 销毁动态注册会话实例 */
        res = aiot_dynreg_deinit(&dynreg_handle);