设备证书是生活物联网为设备颁发的唯一身份凭证。为了快速从下载的设备证书文件(CSV格式)中读取每条设备证书信息,并烧写到产线上的设备中,生活物联网平台提供设备证书分发工具来帮助您完成CSV文件读取及分发工作。您可以根据本文档改造您的产线,提高量产效率。
方案介绍
生活物联网平台提供设备证书分发工具的方案原理图如下。
生活物联网的设备身份分发工具采用Server-Client架构,Server与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"
}
Server分发设备证书给Client的数据中使用的校验算法采用标准CRC32(CRC32计算工具网址),多项式为04C11DB7
,初始值与结果异或值均为FFFFFFFF
,输入与输出均取反。校验payload为设备凭证(WiFi类设备为ProductKey、ProductSecret、DeviceName、DeviceSecret;蓝牙类设备为ProductKey、ProductSecret、DeviceName、DeviceSecret、ProductID)。校验结果为4字节十六进制。
CRC32计算结果校验工具使用方法的示例如下。
- 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端由设备证书分发和设备证书管理两部分组成。
- 下载设备证书的工具。
- 启动设备证书分发服务。
- 解压下载的ZIP文件,并双击server目录下的http_server.exe,来启动分发服务。
出现以下消息时,表示分发服务启动成功。
Waiting for client request
说明 当前版本还未做成系统服务,启动后请您不要关闭窗口。
- (可选)修改端口号。
分发服务默认采用的端口号为
8000,若需要更改端口请执行以下命令修改(以修改成
50000为例)。
http_server.exe 50000
- 管理产品批次,并导入设备证书。
- 双击server目录下的factory.exe,启动管理工具。
- 单击添加设备。
- 配置产品批次及产品信息,并单击选择设备身份文件,选择从生活物联网后台下载的设备身份文件。
说明 若身份文件带ProductSecret字段,程序会自动读取并以身份文件中的内容为准。
- 单击添加设备按钮。
导入完成之后会弹出消息提示导入的详细信息。
- 查询产品信息。
- 单击设备查询,并在DeviceName输入框中输入待查询的产品名称。
- 单击查询。
此时显示设备的详细信息。
(可选)使用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()
两个函数。函数说明如下。
- 开发示例代码。
基于文件“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地址
//参数"a1RxxxxBm9"是设备希望分配身份信息对应的ProductKey
//null表示设备没有唯一标识,希望Server分配一个新的设备身份
devReq.req_dev_cert('127.0.0.1',"a1RxxxxBm9",null, handler);
//下面的代码设置了设备的唯一标识,并再次申请设备的唯一标识
did = "123456";
devReq.req_dev_cert('127.0.0.1',"a1RxxxxBm9",did,handler);
- 在运行的PC或Client的设备上部署NodeJS环境。
- 进入产线分发工具的NodeJS目录,并运行node test.js。
输出的内容如下所示则Server获取设备证书链路正常。其中DeviceName、ProductKey、ProductSecret、DeviceSecret是阿里云IoT平台为设备分配的设备证书,DeviceID是设备申请设备身份时传入的设备标识。
使用C SDK开发Client
设备获取身份信息的功能,可采用以下两种实现方式。
- 产线上将设备的某个GPIO作为输入接口,将其拉高或者拉低,当设备上电运行时发现该GPIO为高或低时,自动连接某个固定的WiFi AP,然后从Server(Server的IP地址需要固定)去申请设备证书。
- 设备上电后检查是否有设备身份,如没有设备身份信息,则自动连接到一个固定的产线AP(AP的SSID和密码由您自行定义)。此时请参照设备证书分发工具提供的C-SDK(调用
get_triple
)去申请设备身份。
请您根据以下步骤基于示例代码开发Client。
- 下载设备证书分发工具。
在
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平台上设备的ProductSecret。 |
ds |
char * |
该参数对应阿里云IoT平台上设备的DeviceSecret。 |
did |
char * |
设备的唯一标识,可以是MAC地址、SN等唯一的设备标识。
特殊情况说明:
- 使用同样的ID申请,Server将返回相同的设备证书。
- 设备不具备唯一的标识,可传入空字符串,但请谨慎操作,可能会造成设备证书浪费。
|
server_ip |
char * |
Server服务所在设备的IP地址。 |
调用该函数的返回值说明如下。
- 开发
get_triple()
函数。
通过C SDK去获取某个已集成生活物联网SDK的设备证书为示例,请您根据以下步骤开发示例代码。
- 确保设备已经通过DHCP正确地获取了IP地址。
- 调用
HAL_Wifi_Get_IP()
获取设备的IP地址。
- 调用
send_req()
获取设备证书。若未获取到数据,会自动再请求一次(若仍然未获取到数据,后续业务逻辑您需根据业务自行实现)。
- 调用
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);
}
}
- 再次调用
send_req()
,告知Server已成功保存设备证书。
- 集成Client SDK示例代码。
以下集成基于已发布的生活物联网平台SDK。获取生活物联网平台SDK,请参见
获取SDK。
以下基于living_platform为例开发。若您基于其他应用开发,需调整相应的路径。
- 在Product/example/living_platfrom/living_platfrom.mk中,新增triple_burn.c文件。
- 在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>
- 将get_triple放在living_platform应用的app_entry.c文件中。
- 参照以下示例代码,实现在
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;
}
}
- 配置设备批量配网能力。
该能力可以让没有WiFi热点信息的WiFi设备,自动连接到厂测路由器,详细操作请参见
SDK新增功能介绍。
- 编译固件并烧录到设备。
- 调试示例代码。
设备烧写包含以上代码的固件后,在串口终端输入以下命令进行测试。
- 携带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"}.