1. 为什么不用控制台,而是用API批量创建设备
做物联网平台接入的朋友应该都有这种体会:产品还在原型阶段,设备数量只有三五台的时候,在OneNET控制台手动点“添加设备”完全不觉得有什么问题。但一旦进入小批量测试,比如手里有几十块开发板要同时接入,或者要做一套自动化测试脚本,每次手工创建、复制设备ID、再抄写设备密钥,这个流程就非常折磨人了。更别说后面如果要接MES系统、做产线自动化注册,控制台操作根本没法跟业务流程打通。
我最早接触OneNET是在一个智慧农业项目上,需要给几十个温湿度采集节点做批量入网。当时第一版方案就是纯控制台手工操作,结果发现几个很现实的问题:
第一,效率瓶颈。每个设备创建需要填名称、选产品、设鉴权信息,一套流程走下来快的话也要十几秒,几十台设备就是十几分钟。这还只是创建,后面还要把设备ID和设备密钥一个一个复制出来,填到设备端的配置文件里,中间只要抄错一个字符,设备上线就会鉴权失败,排查半天发现是密钥最后一位复制漏了,非常抓狂。
第二,无法做自动化联动。手动创建天然没法跟前面的设备产测流程打通。比如产线上设备烧录完固件后,需要自动去平台注册并获取凭证,这必须是脚本或程序触发的动作。控制台操作做不到这一点。
第三,动态场景支持不了。有些产品形态里设备不是固定的,用户每买一个硬件就需要在云端动态分配一个新身份。这种情况下必须由后端服务来调用云端API完成注册。
所以这个标题里的问题——用ONENET API创建设备并拿到设备密钥和设备ID——本质上是一个“把人工操作变成程序调用”的过程。核心动作就是向OneNET平台发一个HTTP请求,平台在指定产品下创建一台新设备,然后把该设备的唯一ID和访问密钥返回给你。这两个返回值就是设备后续跟云平台通信时证明自己身份的凭证。
下面我把整个流程拆开来讲,先说清楚API创建设备的底层逻辑,再给一套可以复用的完整实操方案,最后把我在实际项目中踩过的坑和排查思路一并整理出来。
2. API接口拆解:从鉴权到返回数据的完整链路
2.1 OneNET设备与产品的关系:先有产品,后有设备
在动手调用API之前,要先把OneNET的层级关系理清楚。OneNET的资源模型是“产品(Product)”下面挂“设备(Device)”。产品定义了设备的品类、接入协议(MQTT、HTTP、Modbus等)、数据流模板等公共属性,而设备是这个产品下的一个具体实例。
这就像手机品牌和具体手机的关系——产品是“某型号手机”的设计规范,设备是生产线下线的一台具体手机,有自己的IMEI和SN。在OneNET平台里,创建设备必须指定它属于哪个产品,因为设备编辑器、数据流定义、权限策略都是从产品继承下来的。
所以API创建设备的第一步,是先得在控制台建好一个产品(或者你参与的项目里已经有现成的产品)。创建产品时会拿到一个产品ID(ProductID),这个ID要作为请求参数传给创建设备接口。
2.2 鉴权机制:API Key与Token的计算逻辑
OneNET OpenAPI的鉴权方式和很多云平台不一样,它不是简单地把API Key放在Header里就行,而是需要基于API Key动态计算一个签名Token。
这里要先澄清两个经常被混淆的概念:
- 用户API Key:在OneNET控制台“账号中心”或“OpenAPI”管理页面获取,是调用平台级OpenAPI时的全局凭证,相当于你在OneNET平台的“登录密码”。
- 设备API Key(设备密钥):创建设备时平台分配的,属于单个设备的专属密钥,是设备接入时用的身份凭证。
创建接口用的是用户API Key,返回结果里给你的是设备API Key。这两个Key一开始不搞清楚,后面很容易整糊涂。
Token的计算规则是这样的:
- 取当前时间的Unix时间戳(单位:秒),记为
et。 - 随机生成或使用固定字符串作为
signature(签名随机串,用来增加不可预测性)。 - 将
apiKey、et、signature三个字符串按顺序拼接成一个字符串。 - 对拼接后的字符串计算MD5摘要,得到
accessToken。
用Python伪代码表示就是:
import time import hashlib def generate_token(api_key: str, et: int, signature: str) -> str: raw_string = api_key + str(et) + signature token = hashlib.md5(raw_string.encode('utf-8')).hexdigest() return token然后请求时在HTTP Header里带上这么一段:
Authorization: token=accessToken;et=et;signature=signature这个机制的本质是:平台拿到你的请求后,会用保存在服务端的API Key、请求里的et和signature拼接出同样的字符串,再算一次MD5,跟你传过来的token比对。如果一致就说明请求者持有正确的API Key。
这里有个容易被忽略的细节:et是防重放攻击的时间戳。如果请求里的et跟服务端当前时间差太多,平台会直接拒绝。一般建议在发起请求前一秒取时间戳,不要缓存太久。我自己曾经在一个脚本里把et写死了,结果第二次跑的时候全部401,排查半天才反应过来是时间戳过期了。
2.3 创建设备接口的请求与响应格式
OneNET平台创建单个设备的OpenAPI接口如下:
- 请求方式:
POST - 请求URL:
https://iot.heclouds.com/device - 请求头:
Content-Type: application/json - 请求体(JSON):
{ "title": "device_name", "product_id": "your_product_id", "desc": "设备描述信息,可选", "auth_info": "自定义鉴权信息,可选", "data_interval": 15, "private_info": "私有信息,可选", }调用成功后,返回JSON数据里最重要的两个字段就是标题里提到的设备密钥和设备ID:
{ "errno": 0, "data": { "device_id": "50436129", "api_key": "zJvTpttlzH4FHBd9nH9Odm4wBFU=" } }data.device_id是平台分配给该设备的全局唯一ID。后面设备做MQTT连接、HTTP上报、接收平台下发命令,都靠这个ID定位到具体设备。
data.api_key是设备密钥,被平台用来验证设备的合法身份。固件或者设备端SDK接入时,需要把这个值配置进去。比如OneNET MQTT接入的clientId规则通常就是产品ID + 设备ID,而password则会用到设备API Key做签名校验。
另外请求结构里的private_info在很多项目里非常值钱。它是个字符串字段,可以用来存设备SN号、MAC地址、批次号这些业务信息。后续如果要做设备台账查询,用它来关联自己的业务系统很方便。
3. 实操:用Python把设备批量创建跑起来
3.1 环境准备与前置条件
写代码之前先确认三件事:
- OneNET账号已注册,并且已经登录控制台。
- 已创建产品,拿到产品ID。在控制台的“产品开发”页面能看到类似
vO0goMb51S这样的字符串,这就是product_id。 - 已获取用户API Key。在控制台右上角头像菜单里进“用户中心”或“OpenAPI Key管理”,复制那一长串APIKey字符串。
还有一点容易被坑:OneNET OpenAPI的域名到底是https://api.heclouds.com还是https://iot.heclouds.com?这个跟产品创建时的接入协议有关。老版本有些文档写的是api.heclouds.com,新版统一走iot.heclouds.com。我建议以你实际收到的文档为准,如果你在控制台里打开“设备列表”点“添加设备”时看到浏览器请求的域名,那就是最准确的信息源。下面我都用iot.heclouds.com来写。
3.2 完整Python代码:一次性创建单台设备
下面是一段可以直接跑的Python脚本,用requests库实现,先把单台设备的创建流程跑通:
import time import hashlib import random import string import requests import json # 配置区:改成你自己的信息 API_KEY = "你的用户APIKey" PRODUCT_ID = "你的产品ID" def generate_token(api_key: str) -> tuple: et = int(time.time()) # 生成一个8位随机字符串作为signature signature = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) raw = f"{api_key}{et}{signature}" token = hashlib.md5(raw.encode('utf-8')).hexdigest() return token, et, signature def create_device(device_name: str, desc: str = "", private_info: str = ""): token, et, signature = generate_token(API_KEY) url = "https://iot.heclouds.com/device" headers = { "Content-Type": "application/json", "Authorization": f"token={token};et={et};signature={signature}" } payload = { "title": device_name, "product_id": PRODUCT_ID, "desc": desc, "private_info": private_info } resp = requests.post(url, headers=headers, json=payload) result = resp.json() if result.get("errno") == 0: device_id = result["data"]["device_id"] device_api_key = result["data"]["api_key"] print(f"创建设备成功!") print(f"设备ID: {device_id}") print(f"设备密钥: {device_api_key}") return device_id, device_api_key else: print(f"创建设备失败: {result.get('error')}") return None, None if __name__ == "__main__": create_device("test_device_001", "自动化脚本创建的测试设备")运行这段脚本,如果一切正常,控制台会输出设备ID和设备密钥两行信息。接下来可以去OneNET控制台“设备列表”页面刷新看看,新设备已经躺在列表里了,状态显示“未激活”。
3.3 批量创建设备并保存凭证到CSV
单台创建跑通之后,批量就是加个循环的事情。但实际项目里有一个隐藏需求:创建设备后,凭证需要被保存下来,否则程序退出后再想找设备ID和密钥,又得到控制台去翻,等于自动化了个寂寞。
我在项目里常用的做法是:循环调用创建设备接口,每成功一个就往CSV文件里写入一条记录。CSV比Excel好的一点是通用性极强,后续不管是人工查看还是程序二次处理都方便。
import csv import time devices = [ {"name": "sensor_node_001", "desc": "大棚1号温湿度"}, {"name": "sensor_node_002", "desc": "大棚1号光照"}, {"name": "sensor_node_003", "desc": "大棚2号温湿度"}, ] with open("device_credentials.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["设备名称", "设备ID", "设备密钥", "描述"]) for dev in devices: device_id, api_key = create_device(dev["name"], dev["desc"]) if device_id: writer.writerow([dev["name"], device_id, api_key, dev["desc"]]) print(f"已写入: {dev['name']}") else: writer.writerow([dev["name"], "创建失败", "", dev["desc"]]) time.sleep(0.1) # 轻微间隔,避免触发频率限制这里有两个实操要点一定要提:
- CSV用utf-8-sig编码。默认的utf-8编码不带BOM,在Windows上用Excel打开CSV文件时中文会乱码。加
-sig后Excel直接双击打开中文也没问题。这个细节第一次做CSV导出时很容易踩。 - 每次请求间隔至少100毫秒。OneNET OpenAPI有频率限制,连续高频请求会返回
429 Too Many Requests或被临时封禁,脚本里的time.sleep(0.1)是一种保平安的习惯。如果你的设备量特别大(几千台),建议再拉长间隔,或者做多线程并发时也要控制在平台的阈值内。
3.4 产品下的多设备创建注意事项
批量创建时还有一个重要的设计问题:每个设备名称必须唯一吗?OneNET平台对同一产品下的设备名称不强制唯一,也就是说你可以在同一个产品下创建两个都叫test_001的设备。但我不建议这么做,因为后续查看日志、排查问题时,设备名称完全一样会带来极大的困扰。
更好的命名规范是:使用有规则的组合,比如产品编号_设备类型_序号,或者直接用设备的物理标识符(如MAC地址、SN号)作为title。这样既方便人看,也方便脚本处理。我在智慧农业项目里的命名格式就是greenhouse_01_temp_001,一眼就能看出这是1号大棚的温度传感器。
另外auth_info字段值得单独说明一下。你可以把设备的物理标识传给它,比如WiFi模块的MAC地址。之后设备接入平台时,可以选择用auth_info来做校验,这样就形成了“物理设备 ↔ 云端设备”的一一对应关系,防止别人拿你的产品ID和设备ID伪装接入。
4. 踩坑实录:常见问题与排查套路
4.1 401鉴权失败:Token计算最常见的坑
如果你的请求返回了401 Unauthorized,别急着怀疑网络,先按顺序排查以下几项。
第一个查:API Key对不对。控制台复制的API Key是否完整,有没有多复制空格,或者把设备密钥当成用户API Key用了。我见过好几个人拿着创建设备后返回的设备密钥去调创建设备接口,必然401。
第二个查:时间戳et。检查系统当前时间是否准确。有些服务器时区设置错误,导致time.time()返回的Unix时间戳本身就不对。可以在脚本里先print(time.time()),再去一个在线时间戳工具网站比一下,偏差不要超过5分钟。
第三个查:拼接顺序。我的经验是apiKey + et + signature这个顺序各版本文档偶尔会不同,比如早期版本有拼接et + signature + apiKey的变体。如果确认Key和时间都没问题,可以拿官方的在线调试工具(控制台里一般有“API调试”或者“在线签名工具”)先生成一个正确的token,跟脚本生成的结果做对比,一旦发现不一致,基本就是拼接顺序的问题。
4.2 返回成功但控制台看不到设备
API返回errno=0,但控制台设备列表里看不到新设备,这种情况通常不是设备没创建,而是你看错产品了。
OneNET控制台是按产品筛选设备的。左侧产品列表选的是A产品,设备列表里自然看不到B产品下新建的设备。在控制台切换产品再刷新看看,或者用API查询一下设备详情确认。
如果你是用API创建成功后,在“全部设备”列表里也找不到,那就要检查一下请求里的product_id是否正确——是不是传成了别人的产品ID或者一个不存在的ID。不存在的情况下,接口一般会报错,不至于errno=0。所以大概率还是筛选和页面缓存问题。
4.3 返回错误:产品不存在或无权访问
请求返回类似product not found或permission denied时,排查思路是这样的:
- 产品ID大小写问题:OneNET的产品ID是大小写敏感的,复制时不要改大小写。
- API Key与产品不在同一个账号下:很多团队会用子账号或协作者账号开发,但OpenAPI的API Key归属主账号。如果协作者的API Key试图创建主账号产品下的设备,就会因为没有权限而被拒绝。这时候要么用主账号的API Key,要么给协作者分配相应的产品权限。
- 产品类型不对:OneNET有多套产品体系(如旧版多协议接入、新版物联网开发平台等),不同体系的产品对应的OpenAPI接口域名有差异。如果创建API适用的产品类型跟你的产品不匹配,也会报错。
4.4 设备创建成功但设备死活连不上平台
设备密钥和设备ID都拿到了,但设备端接入时报鉴权失败或找不到设备。我在实际项目里遇到过的原因有两类:
一类是设备ID传错了对象。比如MQTT连接时,clientId的结构一般是产品ID_设备ID(注意用下划线连接),有些SDK里还要求填成产品ID_设备ID_安全字节这种特殊格式。如果直接把纯设备ID填进去,连接时平台无法定位到设备。
另一类是设备密钥配置错误。OneNET设备密钥用于密码计算,MQTT接入时username是产品ID,password是基于设备密钥计算出来的签名串。直接把设备密钥原文当password填进去也不行,需要按接入协议文档里的签名算法处理。
遇到这类问题,最好的排查方式是用控制台自带的“在线调试”功能做MQTT模拟连接。它能直观地反馈你的连接参数是否正确,比在单片机上一行行查日志高效多了。
4.5 返回429:触发频率限制
这是批量创建时最常见的限流报错。OneNET OpenAPI对单个账号的单接口调用频率有限制,如果并发过高会返回429,配合的响应头里通常会告诉你retry-after。
遇到429,最直接的办法是把并发请求改成串行,每次之间间隔0.5秒以上。如果确实有大批量创建需求,建议跟OneNET官方或运营人员沟通提高配额,或者采用“串行跑、失败重试3次”的策略。重试时要做退避,别一股脑地在同一秒内重试。
5. 进阶用法:扫码注册与动态设备身份签发
把设备创建接口跑通之后,这个能力能支撑很多更复杂的业务场景。我这里分享一个我在实际项目里做过的“扫码注册”链路,它可以作为一个扩展参考。
场景是这样的:客户买到一台智能硬件,手机App扫码后,后端服务需要为这台硬件动态注册一个新的云端设备身份,并把设备密钥安全地送到硬件端。
这个流程可以拆成以下步骤:
- 硬件设备首次上电后,产生一个随机注册码并通过某种方式展示(屏幕显示或铭牌二维码)。
- 用户用App扫这个注册码,App把注册码和硬件SN号发到自己的业务后端。
- 业务后端调用OneNET创建设备API,把SN号当作
title或private_info写入平台。 - 拿到的设备ID和设备密钥,再加上SN号、注册码一起写入字典表,关联到当前用户账号。
- 设备端通过本地局域网或蓝牙从App拿到云端凭证,或者业务后端将凭证通过安全通道下发到硬件。
- 设备拿到凭证后连接OneNET平台,从“未激活”状态变为“在线”状态。
这套联动的好处在于:硬件流通过程中,云端身份的签发完全自动化,不需要厂商在出厂前预烧录设备ID和密钥。生产线上只需要烧录统一的固件,身份在用户激活的瞬间动态生成,安全性和灵活性都更好。
另外一个可以做的扩展是“设备激活自检”。创建设备后,设备端首次上线时通常会上报一个类似device_init_ok的数据流。业务后端可以定时去查这个数据流是否存在,来判断设备是否成功激活,从而触发后续的业务动作(比如赠送服务时长、发放保修卡等)。
6. 写在最后的几点实操心得
做OneNET设备接入这块时间也不短了,最后分享几条我在实际项目中沉淀下来的经验。
第一,设备凭证的保存和管理要提前设计好。设备ID和密钥不是建完就完事,后续排查问题、运维设备都离不开它们。纸质记录、Excel表格都能应付少量设备,但设备上千台之后,建议建一张数据库表来维护设备ID、设备密钥、产品ID、创建设备时间、激活状态、最后在线时间这些字段。我在项目里就是用一张device_registry表来管理的,后续所有跟设备相关的业务逻辑都会先用这张表做关联。
第二,签名Token的计算逻辑一定要写成独立的函数。项目大了之后,不只是创建设备,获取设备历史数据、命令下发、固件升级这些操作都要用到同样的Token生成逻辑。把Token计算封装成一个公共模块,避免每个脚本里复制同一段代码,否则将来平台升级签名算法时,改起来会想骂人。
第三,对网络异常要有心理预期。任何HTTP接口调用都可能在网络上出问题,代码里必须写超时处理和重试逻辑。我见过太多人只用裸requests.post()调接口,不做超时设置,结果某个下午平台网络抖动,脚本抛出一堆异常,还以为是代码写错了。
最后说一句关于字符串变量编码的事情。如果你在Windows环境下跑Python脚本,请求体里带有中文的title或desc,可能会遇到编码问题导致请求失败。这种情况尽量把Windows终端代码页切到UTF-8,或者在脚本里显式处理字符串编码。这个细节在中文设备命名时几乎必踩,提前设置好能省很多事。
OneNET API的创建设备接口本身并不复杂,核心痛点从来不是接口调用那一行代码,而是把凭证拿回来之后,怎么把它安全地关联到业务流程里。把这个环节想透了,物联网设备的批量接入就不再是手工活,而是一条顺畅的自动化链路。