设备证书是生活物联网为设备颁发的唯一身份凭证。为了快速从生活物联网平台下载的设备证书文件(CSV格式)中读取每条设备证书信息,并烧写到产线上的设备中,生活物联网平台提供设备证书分发工具来帮助您完成CSV文件读取及分发工作。您可以根据本文档改造您的产线,提高量产效率。

方案介绍

生活物联网平台提供设备证书分发工具的方案原理图如下。

jt13

生活物联网的设备身份分发工具采用Server-Client架构,Server与Client交互流程图如下所示。

jt14
  • Server:由生活物联网平台提供,您下载后可直接使用(工具下载地址)。

    您可以使用生活物联网平台提供的NodeJS SDK(参见本文档下方相关内容),来验证Server端链路是否正常。

  • Client:部署在产线烧录仪器或集成到产品固件中(两者对比如下表所示),需要您自行开发。

    为了方便您快速集成Client,生活物联网平台提供了C SDK供您参考(参见本文档下方相关内容)。 您集成C SDK后,再自行集成烧录设备证书的方式到Client中。

    Client部署方式 适用场景 描述 特点
    集成到设备固件中 产线上没有用于将设备证书烧写到芯片/设备的仪器。 该方案设备上电后,Client向Server申请身份信息,Client获取到设备证书后将这些信息写入设备的NVRAM/Flash。

    该方案您需要实现以下内容:

    • 设计如何触发Client去获取设备证书的功能。
    • Client获取设备证书后,需要实现将信息写入NVRAM/Flash的操作。
    • 产线无需部署专门用于烧写设备证书的仪器。
    • 设备需要实现Client的功能,因此会占用设备的部分存储空间。
    • 可以实现批量进行身份信息获取。
    部署在产线烧录仪器上 产线上存在将设备证书烧写到芯片/设备的仪器,如烧录器或编程器等。 该方案由烧录软件实现Client的功能向Server申请身份信息,取得设备证书后,通过烧录器/编程器写入设备的NVRAM/Flash。

    阿里云物联网提供了产线分发App的通信协议,该方案您需要修改产线烧写工具按照该通信协议从产线分发App获取设备证书。

    • 设备无需实现Client的功能,不会为了获取身份信息功能消耗存储空间。
    • 您需要对烧录器/编程器进行软件改造。
    • 可以根据产线上的生产设备数量、规模,配置烧录器/编程器的数量。

Server-Client协议说明

Client与Server采用HTTP接口通讯,Client通过POST方法向Server发送请求,请求协议以json方式放在body里。

表 1. 请求协议格式与参数说明
字段 类型 是否必选 描述
ProductKey string 产品标识(生活物联网后台新建产品是生成)
DeviceID string 设备ID,唯一标识
Action Integer 操作
  • 0:请求分配设备证书
  • 1:上传烧录成功
  • 2:上传烧录失败

请求与响应数据示例如下。

  • Client向Server申请设备证书请求数据
    {
        "ProductKey":"a1xxxxNW",
        "DeviceID":"AAxxxx22",
        "Action":0
    }
  • Server正常分发设备证书给Client数据
    {
        "ProductKey":"a1xxxxNW",
        "ProductSecret":"XmDxxxx9R",
        "DeviceSecret":"NmMExxxxjE5",
        "CRC32":"B5488744",     //CRC32介绍参见下方文档
        "DeviceName":"ZTMxxxxxkUy",
          "DeviceID":"AABxxxx22",
          "Status":"OK"
    }
  • 烧录成功状态更新请求
    {
        "ProductKey":"a1xxxxW",
        "DeviceID":"AAxxxx22",
        "Action":1
    }{
        "ProductKey": "a1xxxxNW",
        "DeviceID": "AAxxxx22",
        "Status": "OK"
    }
  • 烧录成功状态更新返回数据
    {
        "ProductKey": "a1xxxxNW",
        "DeviceID": "AAxxxx22",
        "Status": "OK"
    }

Server分发设备证书给Client的数据中使用的校验算法采用标准CRC32(CRC32计算工具网址),多项式为04C11DB7,初始值与结果异或值均为FFFFFFFF,输入与输出均取反。校验payload为设备凭证(WiFi类设备为ProductKey、ProductSecret、DeviceName、DeviceSecret;蓝牙类设备为ProductKey、ProductSecret、DeviceName、DeviceSecret、ProductID)。校验结果为4字节十六进制。

CRC32计算结果校验工具使用方法的示例如下。

jt14
  • java代码参考实现
    import java.nio.ByteBuffer;
    import java.util.zip.CRC32;
    
    public class example {
    
    public static void main(String[] args){
    
        ///this is user supplied string
        String pk  = "a1xxxxH";
        String dn  = "8jQxxxx0WW6";
        String ds  = "aW60ImxxxxIj61ZbV";
        String ps  = "EBcjxxxxWxdF";
    
        if ( args.length >= 4) {
            pk = args[0];
            dn = args[1];
            ds = args[2];
            ps = args[3];
        }
    
        String payload = pk + dn + ds + ps;
        ByteBuffer bbuffer = ByteBuffer.allocate(payload.length());
        bbuffer.put(payload.getBytes());
    
        //your crc class
        CRC32 crc = new CRC32();
        crc.update(bbuffer.array());
        String enc = Long.toHexString(crc.getValue()).toUpperCase();
        System.out.println("payload: " + payload);
        System.out.println("length: " + Integer.toString(payload.length()));
        System.out.println("crc32: " + enc);
        }
    }
  • python代码参考实现
    import zlib
    
    pk  = "a2xxxx8HZ"
    dn  = "ouxxxxemo"
    ds  = "GD4JauYxxxxsICazhIzb"
    ps  = "4I3xxxxyF96"
    
    def Crc32Hash(input_data):
    
        crc32 = 0
        crc32 = zlib.crc32(input_data, crc32)
    
        return format(crc32 & 0xFFFFFFFF, '08X')
    
    payload = pk + dn + ds + ps
    print "payload: ", payload
    print "length: ", len(payload)
    print "crc32: ", Crc32Hash(payload)
  • 设备端参考代码
    #define CHIP_CPU_BE
    
    #define dwPolynomial_BE            0xEDB88320UL      //CRC32 Polynomial
    #define dwPolynomial               0x04C11DB7UL      //CRC32 Polynomial
    #if defined(CHIP_CPU_BE)
    #define dwPolynomial               dwPolynomial_BE
    #else
    #define dwPolynomial               dwPolynomial_LE
    #endif
    #define CRC_INIT_VALUE             0xFFFFFFFFUL
    
    unsigned int calc_crc32(unsigned char *message) {
       int i, j;
       unsigned int byte, crc, mask;
    
       i = 0;
       crc = CRC_INIT_VALUE;
       while (message[i] != 0) {
          byte = message[i];            // Get next byte.
          crc = crc ^ byte;
          for (j = 7; j >= 0; j--) {    // Do eight times.
             mask = -(crc & 1);
             crc = (crc >> 1) ^ (dwPolynomial_BE & mask);
          }
          i = i + 1;
       }
       return ~crc;
    }

Sever端工具使用

Server端由设备证书分发和设备证书管理两部分组成。

  1. 下载设备证书相关的工具(工具下载地址)。
  2. 启动设备证书分发服务。
    1. 解压下载的zip,并双击server目录下的http_server.exe,来启动分发服务。
      出现以下消息时,表示分发服务启动成功。
      Waiting for client request
      说明 当前版本还未做成系统服务,启动后请您不要关闭窗口。
    2. (可选)修改端口号。
      分发服务默认采用的端口号为8000,若需要更改端口请执行以下命令修改(以修改成50000为例)。
      http_server.exe 50000
  3. 管理产品批次,并导入设备证书。
    1. 双击server目录下的factory.exe,来启动管理工具。
    2. 单击添加设备
      jt17
    3. 配置产品批次及产品信息,并单击选择设备身份文件,选择从生活物联网后台下载的设备身份文件。
      jt18
      说明 若身份文件带ProductSecret字段,程序会自动读取并以身份文件中的内容为准。
    4. 单击添加设备按钮。
      导入完成之后会弹出消息提示导入的详细信息。jt19
  4. 查询产品信息。
    1. 单击设备查询,并在DeviceName输入框中输入待查询的产品名称。
    2. 单击查询

      此时显示设备的详细信息。

      jt20

(可选)使用NodeJS SDK验证Server

在设备证书分发工具(工具下载地址)中提供了NodeJS SDK参考实现,您可以使用NodeJS SDK来验证Server获取设备证书的链路是否正常。

您下载并解压的分发工具后,在client\NodeJS目录下查看NodeJS SDK的相关文件,文件说明如下。

  • test.js:是一个示例文件,该文件调用devReqCert.js获取设备证书。
  • devReqCert.js:提供req_dev_cert()send_burn_result_by_did()两个函数(说明如下)。
    • req_dev_cert('ip',"pk",id,callback)

      向指定的Server申请设备证书。函数中参数说明如下。

      参数 类型 描述
      ip String Server的IP地址或者域名
      pk String 设备希望申请的身份信息对应的产品型号,该参数对应阿里云IoT平台上产品的ProductKey
      id String 设备的唯一标识,可以是MAC地址、SN,只要是设备唯一的即可
      • 如果使用同样的id申请,Server将会返回相同的设备证书
      • 若id值为null,表示设备希望分配一个新的身份信息
      callback function 当Server返回设备证书后调用的回调函数,用于保存设备证书。设备证书该函数需要由您自行实现
    • send_burn_result_by_did('ip',"pk","id")

      向指定的Server发送设备身份信息烧写结果,Server收到Client发送的烧写结果后,将会把对应设备的身份信息状态从已分配修改为已烧写。函数中参数说明如下。

      参数 类型 描述
      ip String Server的IP地址或者域名
      pk String 设备希望申请的身份信息对应的产品型号,该参数对应阿里云IoT平台上产品的ProductKey
      id String 设备的唯一标识,可以是MAC地址、SN,只要是设备唯一的即可
      • 如果使用同样的id申请,Server将会返回相同的设备证书
      • 若id值为null,表示设备希望分配一个新的身份信息

请您根据以下步骤开发并运行示例代码。

  1. 开发示例代码。

    基于文件“test.js”进行示例代码说明。

    var http = require('http'); // 引入对http库的依赖
    var devReq = require('./devReqCert');  //引入对devReqCert的依赖
    
    var did = null;
    
    //下面的函数handler是Client收到Server分配的设备证书后的处理函数,
    //在本示例代码中仅仅将设备的身份信息进行打印输出,
    //实际产品生产时需要将设备的身份信息进行持久化保存
    function handler(devInfo)
    {
        var jsonObj = JSON.parse(devInfo);
        console.log("DevInfo:" + devInfo);
    }
    
    //下面的语句调用req_dev_cert获取设备的身份信息,其中
    //“127.0.0.1”是Server的IP地址,本测试中Server和Client在同一台PC上,所以使用了127.0.0.1地址
    //参数"a1R3WBv1Bm9"是设备希望分配身份信息对应的ProductKey
    //null表示设备没有唯一标识,希望Server分配一个新的设备身份
    devReq.req_dev_cert('127.0.0.1',"a1R3WBv1Bm9",null, handler);
    
    //下面的代码设置了设备的唯一标识,并再次申请设备的唯一标识
    did = "123456";
    devReq.req_dev_cert('127.0.0.1',"a1R3WBv1Bm9",did,handler);
    						
  2. 在运行Client的设备/PC上部署NodeJS环境。
  3. 进入产线分发工具的NodeJS目录,并运行node test.js

    输出的内容如下所示则Server获取设备证书链路正常。其中DeviceName、ProductKey、ProductSecret、DeviceSecret是阿里云IoT平台为设备分配的设备证书,DeviceID是设备申请设备身份时传入的设备标识。

    jt14

使用C SDK开发Client

设备获取身份信息的功能,可采用以下两种实现方式。

  • 产线上将设备的某个GPIO作为输入接口,将其拉高或者拉低,当设备上电运行时发现该GPIO为高/低时,自动连接某个固定的WiFi AP,然后从Server(Server的IP地址需要固定)去申请设备证书。
  • 设备上电后检查是否有设备身份,如没有设备身份信息,则自动连接到一个固定的产线AP(AP的SSID和密码由您自行定义)。此时请参照设备证书分发工具(工具下载地址)提供的C-SDK(调用get_triple)去申请设备身份。

您下载并解压的分发工具后,在client\C-SDK目录下查看C SDK的相关文件,文件说明如下。

  • triple_burn.c:用来存放SDK代码。
  • triple_burn.h:头文件,包含send_req()parse_data()两个函数(说明如下)。
    • char* send_req(act, pk, did, const server_ip)

      向指定的Server申请设备证书,或发送设备证书烧写结果设备证书。其中参数说明如下。

      参数 类型 描述
      act Int
      • 0:Client向Server请求设备身份
      • 1:Client告知Server设备身份写入成功
      pk char * 设备希望申请的身份信息对应的产品型号,该参数对应阿里云IoT平台上产品的ProductKey
      did char * 设备的唯一标识,可以是MAC地址、SN等唯一的设备标识

      特殊情况说明:

      • 使用同样的id申请,Server将返回相同的设备证书
      • 设备不具备唯一的标识,可传入空字符串,但请谨慎操作,可能会造成设备证书浪费
      server_ip char * Server服务所在设备的IP地址

      Server返回的设备身份,格式为JSON。若为NULL表示申请身份失败。

    • int parse_data(const data, pk, dn, ps, ds, did)

      该函数用于解析从Server返回的JSON字符串,并获取其中的ProductKey、ProductSecret、DeviceName、DeviceSecret、DeviceID(如果您还需在Server处为每个设备增加其它的配置信息,可自行修改本函数)。

      参数 类型 描述
      data char * 函数send_req()从Server获取到的设备证书
      pk char * 设备的产品型号,该参数对应阿里云IoT平台上产品的ProductKey
      dn char * 设备的名称,该参数对应阿里云IoT平台上设备的DeviceName
      ps char * 该参数对应阿里云IoT平台上设备的DeviceSecret
      ds char * 该参数对应阿里云IoT平台上设备的DeviceSecret
      did char * 设备的唯一标识,可以是MAC地址、SN等唯一的设备标识。

      特殊情况说明:

      • 使用同样的id申请,Server将返回相同的设备证书
      • 设备不具备唯一的标识,可传入空字符串,但请谨慎操作,可能会造成设备证书浪费
      server_ip char * Server服务所在设备的IP地址

      调用该函数的返回值说明如下。

      • 0:解析成功
      • 非0:无效数据

请您根据以下步骤基于示例代码开发Client。

  1. 开发get_triple()函数。

    通过C SDK去获取某个已集成生活物联网SDK的设备证书为示例,请您根据以下步骤开发示例代码。

    1. 确保设备已经通过DHCP正确的获取了IP地址。
    2. 调用HAL_Wifi_Get_IP()获取设备的IP地址。
    3. 调用send_req()获取设备证书。
      若未获取到数据,会自动再请求一次(若仍然未获取到数据,后续业务逻辑您需根据业务自行实现)。
    4. 调用parse_data()解析数据。
      数据解析成功,将获得设备的ProductKey、ProductSecret、DeviceName,DeviceSecret。

      示例中仅对设备证书进行打印输出(以下代码中LOG中所示),您开发时需要将这些信息存储到Flash/NVRAM中。

      
      static void get_triple(char* server_ip, char* productKey, char* deviceid){
          char ip_addr[16] = {0};
          char pk[PRODUCT_KEY_LEN] = {0};
          char dn[DEVICE_NAME_LEN] = {0};
          char ds[DEVICE_SECRET_LEN] = {0};
          char ps[PRODUCT_SECRET_LEN] = {0};
          char did[PRODUCT_SECRET_LEN] = {0};
      
          HAL_Wifi_Get_IP(ip_addr, 0);
          if (strlen(ip_addr) > 5){
              LOG("wifi_service_event ip=%s", ip_addr);
              char *response_payload = send_req(0, productKey, deviceid, server_ip);
              if (strlen(response_payload) == 0){
                  if (response_payload != NULL)
                      HAL_Free(response_payload);
                  response_payload = send_req(0, productKey, deviceid, server_ip);
              }
              if( response_payload != NULL && 0 == parse_data(response_payload, pk, dn, ps, ds, did)){
                  HAL_Free(response_payload);
                  LOG("triple:pk=%s dn=%s ds=%s ps=%s did=%s", pk,dn,ds,ps,did);
                  // write pk,dn,ds,ps to falsh
      
                  // send write success to server
                  HAL_Free(send_req(1, productKey, did, server_ip));
              } else {
                  if (response_payload != NULL)
                      HAL_Free(response_payload);
              }
          }
    5. 再次调用send_req(),告知Server已成功保存设备证书。
  2. 集成Client SDK示例代码。
    以下集成基于已发布的生活物联网平台SDK。获取生活物联网平台SDK请参见SDK概述与开发环境设置

    以下基于living_platform为例开发。若您基于其他应用开发,需调整相应的路径。

    1. Product/example/living_platfrom/living_platfrom.mk中,新增triple_burn.c文件。
    2. app_entry.c文件中增加include triple_burn.h
      #if defined(OTA_ENABLED) && defined(BUILD_AOS)
      #include "ota_service.h"
      #endif
      
      #include "triple_burn.h"  //triple_burn.h为头文件
      
      #include <aos/network.h>
    3. get_triple放在living_platform应用的app_entry.c文件中。
    4. 参照以下示例代码,实现在handle_linkkey_cmd()中调用get_triple()来获取设备证书。
      更多代码可以参见client\C-SDK\app_entry.c文件。
      static void handle_linkkey_cmd(char *pwbuf, int blen, int argc, char **argv)
      {
          if (argc == 1)
          {
              int len = 0;
              char product_key[PRODUCT_KEY_LEN + 1] = {0};
              char product_secret[PRODUCT_SECRET_LEN + 1] = {0};
              char device_name[DEVICE_NAME_LEN + 1] = {0};
              char device_secret[DEVICE_SECRET_LEN + 1] = { 0 };
      
              len = PRODUCT_KEY_LEN+1;
              aos_kv_get("linkkit_product_key", product_key, &len);
      
              len = PRODUCT_SECRET_LEN+1;
              aos_kv_get("linkkit_product_secret", product_secret, &len);
      
              len = DEVICE_NAME_LEN+1;
              aos_kv_get("linkkit_device_name", device_name, &len);
      
              len = DEVICE_SECRET_LEN+1;
              aos_kv_get("linkkit_device_secret", device_secret, &len);
      
              aos_cli_printf("Product Key=%s.\r\n", product_key);
              aos_cli_printf("Device Name=%s.\r\n", device_name);
              aos_cli_printf("Device Secret=%s.\r\n", device_secret);
              aos_cli_printf("Product Secret=%s.\r\n", product_secret);
      
          }
          else if (argc == 5)
          {
              aos_kv_set("linkkit_product_key", argv[1], strlen(argv[1]) + 1, 1);
              aos_kv_set("linkkit_device_name", argv[2], strlen(argv[2]) + 1, 1);
              aos_kv_set("linkkit_device_secret", argv[3], strlen(argv[3]) + 1, 1);
              aos_kv_set("linkkit_product_secret", argv[4], strlen(argv[4]) + 1, 1);
      
              aos_cli_printf("Done");
      
          }
          else if (argc == 4){
              get_triple(argv[1], argv[2], argv[3]);
          }
          else if (argc == 3){
              get_triple(argv[1], argv[2], "");
          }
          else
          {
              aos_cli_printf("Error: %d\r\n", __LINE__);
              return;
          }
      }
    5. 配置设备批量配网能力。
      该能力可以让没有WiFi热点信息的WiFi设备,自动连接到厂测路由器,详细操作请参见配置SDK新增功能
    6. 编译固件并烧录到设备。
  3. 调试示例代码。
    设备烧写包含以上代码的固件后,在串口终端输入以下命令进行测试。
    • 携带DeviceID申请设备证书
      linkkey ServerIP productKey DID
      //ServerIP是实际环境中Server的IP地址,productKey是产品的ProductKey的值
      //DID是设备的唯一标识,唯一标识可以是设备MAC地址、序列号等可以唯一标识设备的值
    • 不携带DeviceID申请设备证书(该情况请谨慎操作,可能会造成设备证书浪费)
      linkkey ServerIP productKey
      //ServerIP在运行时需要输入Server在实际环境中的IP地址,productKey是您产品的ProductKey的值

    若设备成功从Server取到数据,会得到类似以下的日志。

    resp={"CRC32":3041429316,"ProductKey":"a1xxxxNW",
    "DeviceName":"ZTMxxxxxTUy",
    "DeviceSecret":"NmMzxxxxjE5",
    "ProductSecret":"Xmxxxxb9R","Status":"OK"}.