平台在链路里扮演什么角色
当设备从几十台涨到几万台,你会发现真正难的不再是“怎么把数据发出去”,而是这几件事:谁在什么时候注册过这台设备、它有哪些能力、数据应该存到哪里、异常由谁触发、固件怎么批量升级。物联网平台就是把这些横切关注点集中起来的那一层。
选平台本质上是选“数据模型能力和规则表达能力”。设备接入协议大家都能做到,真正决定你后续开发量的,是物模型能不能描述清楚你的设备,规则引擎能不能替你完成大部分数据流转。
下面围绕四件事展开:设备怎么进来、能力怎么描述、数据怎么流转、以及怎么在能力相近的平台之间做取舍。
设备接入:身份从哪里来
设备接入的第一道关是身份。几乎所有平台都采用“一机一密”的思路:设备出厂时携带唯一标识与密钥,首次连接时用它们换取会话凭据。常见的字段命名是产品标识 + 设备名 + 设备密钥这组三元组,接入时用它计算签名或换取 Token。
# 设备首次连接:用三元组换会话凭据(示意,字段名按平台文档替换)
import hashlib, hmac, json, time
def sign(product_key, device_name, device_secret, ts):
# 常见做法:对 "deviceName + timestamp" 之类的内容做 HMAC 摘要
msg = ("%s%s" % (device_name, ts)).encode("utf-8")
return hmac.new(device_secret.encode("utf-8"), msg, hashlib.sha256).hexdigest()
ts = int(time.time() * 1000)
auth = {
"productKey": "a1JjyXXXXXX",
"deviceName": "dev-0001",
"timestamp": ts,
"sign": sign("a1JjyXXXXXX", "dev-0001", "s3cr3t-per-device", ts)
}
print(json.dumps(auth, ensure_ascii=False))
- 一机一密每台设备烧录唯一密钥,安全性最好,适合量产设备,代价是产线需要写入与追溯。
- 一型一密同型号共用产品密钥,首次连接时动态注册获取设备密钥;开箱即用,但要为密钥泄漏准备好吊销流程。
- 网关子设备设备本身没有联网能力(如 RS485 设备),由网关代理注册与上下线,平台侧要开启子设备拓扑管理。
物模型:把设备翻译成平台能懂的语言
物模型(各平台叫法不同,有的叫 TSL)规定了三类能力:属性(可读可写的状态)、事件(上报发生的事情)、服务(平台可调用的动作)。它决定了下游一切:数据怎么存、控制指令怎么下、可视化怎么配。
{
"properties": [
{ "identifier": "temperature", "name": "温度", "dataType": { "type": "float", "specs": { "min": -40, "max": 125, "unit": "℃", "step": 0.1 } }, "accessMode": "r" },
{ "identifier": "switchState", "name": "开关状态", "dataType": { "type": "bool" }, "accessMode": "rw" },
{ "identifier": "mode", "name": "运行模式", "dataType": { "type": "enum", "specs": { "0": "待机", "1": "制冷", "2": "制热" } }, "accessMode": "rw" }
],
"events": [
{ "identifier": "highTempAlarm", "name": "高温告警", "type": "alert",
"outputData": [ { "identifier": "value", "dataType": { "type": "float" } } ] }
],
"services": [
{ "identifier": "setMode", "name": "设置模式",
"inputData": [ { "identifier": "mode", "dataType": { "type": "int" } } ] }
]
}
物模型设计有三条经验:属性描述状态、事件描述突变、服务描述动作,不要把“设置温度”做成属性写操作又做成服务,否则日志里会出现两套语义相同的记录;枚举比字符串更好用,平台侧做聚合和告警时更省事;单位与量程一定要写全,它是后续可视化刻度与告警阈值的默认值来源。
规则引擎与数据流转
设备上报的数据如果全部原样入库,存储成本会很快失控。规则引擎的作用是在数据进来时顺手做一次加工:过滤、转换、投递到不同目的地。
-- 典型规则引擎写法(SQL 风格):订阅上报主题,筛选后转发
SELECT
deviceName() AS device,
timestamp() AS ts,
temperature AS temp,
switchState AS sw
FROM
'/a1JjyXXXXXX/+/thing/event/property/post'
WHERE
temperature > 80
-- 命中后按顺序执行动作:
-- 1) 写入时序数据库(长期趋势)
-- 2) 投递到消息队列 / 函数计算(触发业务逻辑与告警通知)
-- 3) 调用另一个服务端 API(如工单系统开单)
-- 补充一条“全部数据落冷存储”的规则,用于回溯与训练
SELECT * FROM '/a1JjyXXXXXX/+/thing/event/property/post'
两个实践要点:把“告警判定”和“数据落库”拆成两条规则,避免一次改动影响两件事;规则里的时间统一用平台时间戳,设备本地时间只能作为附加字段,否则跨时区站点会乱成一团。
平台横向对比:看哪几个维度
平台之间的差异没有宣传里那么大,也不像某些测评写得那么不可比较。抓住下面六个维度,基本能做出适合自己的选择。
| 维度 | 需要确认的问题 | 影响 |
|---|---|---|
| 协议支持 | 是否原生支持 MQTT、CoAP、HTTP、Modbus 网关接入;是否支持自定义协议透传 | 决定现场改造工作量 |
| 物模型能力 | 数据类型是否覆盖 struct/array;是否支持属性读写与事件服务 | 决定后续开发是否需要大量自建映射 |
| 规则引擎 | 是否支持 SQL/函数式加工;可否投递到自有数据库与消息队列 | 决定数据链路是否需要额外中间层 |
| 设备管理 | 批量注册、标签分组、OTA、远程配置、在线诊断 | 决定运维阶段的自动化上限 |
| 成本结构 | 按连接数、消息量还是存储量计费;免费额度怎么算 | 决定规模化后的成本曲线 |
| 可迁移性 | 是否支持私有化部署;导出数据与配置是否容易 | 决定被单家平台锁定的风险 |
一个容易被忽略的维度是“本地化与合规”:如果数据不能出境或不能出厂区,那么支持私有化部署(或提供本地版本)就成了硬性条件,这时候功能清单再漂亮也没用。
接入实践:让真实设备跑起来
大多数平台都兼容标准 MQTT 接入,因此调试阶段用一段 Python 就能跑通“连接—上报—接收控制”的完整闭环。
# 平台接入最小闭环(标准 MQTT 兼容方式)
import json, time, paho.mqtt.client as mqtt
PK, DN, SECRET = "a1JjyXXXXXX", "dev-0001", "s3cr3t-per-device"
HOST, PORT = "iot.example.com", 1883
PROP = "/%s/%s/thing/event/property/post" % (PK, DN) # 上报属性
SET = "/%s/%s/thing/service/property/set" % (PK, DN) # 接收属性设置
REPLY = "/%s/%s/thing/service/property/set_reply" % (PK, DN)
def on_connect(c, u, flags, rc):
c.subscribe(SET, qos=1)
print("已接入平台, rc =", rc)
def on_message(c, u, msg):
req = json.loads(msg.payload.decode())
print("平台下发:", req)
# 执行动作后必须回执,否则平台会认为指令超时
c.publish(REPLY, json.dumps({"id": req.get("id"), "code": 200, "data": {}}), qos=1)
client = mqtt.Client(client_id=DN + "|securemode=3,signmethod=hmacsha256|")
client.username_pw_set(DN + "&" + PK, SECRET)
client.on_connect, client.on_message = on_connect, on_message
client.connect(HOST, PORT, keepalive=60)
client.loop_start()
while True:
client.publish(PROP, json.dumps({"id": int(time.time()), "version": "1.0",
"params": {"temperature": 23.5, "switchState": True, "mode": 1}}), qos=1)
time.sleep(30)
调试顺序建议是:先用平台自带的在线调试工具模拟设备上报,确认物模型与规则没问题;再让真实设备接入;最后才做批量导入。反过来做,会把平台配置问题误判成设备固件问题。
选型与避坑清单
最后汇总一份踩过坑才写出来的清单,接入前逐条确认能省掉很多返工。
- 不要为了省事把所有数据塞进一个属性:字符串里再套 JSON,平台侧的聚合、告警、可视化全都用不上。
- 上报频率要与计费模型对齐:有的平台按消息条数计费,1 秒一次的上报在千台规模下就很可观,能用聚合与死区就用。
- 心跳不要设得过短:Keep Alive 太小会显著增加连接层开销,30 到 120 秒通常足够。
- 指令回执必须实现:平台下发控制指令后没有回执,会把设备标记为超时,进而触发离线策略。
- 设备影子当缓存用,不要当数据库用:它是状态同步工具,不是历史记录存储。
- OTA 先在 1% 设备上灰度:一次错误升级可能让现场设备集体变砖,灰度与回滚能力比升级成功率更重要。
- 导出与备份要提前演练:真正需要迁移时才发现配置导不出来,代价会非常大。
平台的正确用法是“用它最强的部分,绕开它最弱的部分”。把设备身份、物模型、数据流转交给平台,把复杂业务逻辑放在自己的服务或边缘侧,架构的主动权才留在自己手里。
做到这一步,接入就不再是一次性的对接工作,而是一套可以复制到下一个项目的工程方法。