❮  Entrixy 控制器

socket 协议规范

为您自己的控制器加上 Entrixy 联网模式所需的一切:WebSocket 消息、端到端加密和测试向量。

BLE不同,socket 控制器 会与 Entrixy 服务器保持长连接 (走 WebSocket),可从任何地方打开。基础模式在本规范中有完整描述。关于端到端加密——在那种模式下服务器无法伪造开门——见 §5–7。量产时如何获取 device_key ,见 §10。参考: esp32-ws-example (MIT).

1. 传输与消息封装

参考版 ESP 固件默认不做服务器证书固定。生产环境请按主机或 CA 做固定;相关材料可来函索取(hello@entrixy.com).

2. 连接与认证

控制器在第一帧中自我介绍:

→ device_hello
{ "type":"device_hello",
  "device_key":    "<32 hex>",
  "device_secret": "<32 hex>",
  "e2ee":          true|false }        // whether a valid ownerSecret exists (§5)

device_key/device_secret ——设备码的两半,各 32 个十六进制字符(可带可选的 ENTX-DEV:前缀)。服务器会核对 sha256(device_secret) 与哈希是否一致,依据 device_key.

❮  device_ok  { "type":"device_ok", "hb_interval":300 }   // success; hb_interval in s (10..3600)
← error      { "type":"error", "reason":"auth" }         // failure → the server closes

3. 开启(基础模式,无端到端加密)

❮  device_command
{ "type":"device_command", "action":"open", "command_id":"<id>", "number_id":<n> }
  // action:"close" — for a bistable drive

控制器给继电器一个脉冲(默认 1000 毫秒)并回复:

→ device_status
{ "type":"device_status", "command_id":"<id>",
  "level":"success"|"warning"|"danger"|"info",
  "message":"...", "final":true
  [, "position":"open"|"closed"|"unknown" ] }

level 不在白名单内的,会被当作 info.

4. 心跳与存活检测

服务器 不会 ping 设备——这种扇出扛不住规模。由设备自己维持连接:

→ { "type":"ping" }    ← { "type":"pong" }     // the device sends this every ~30 s

如果设备沉默超过约 90 秒,服务器会断开它。服务器发来的 ping ,设备会以之回应: pong。对于双稳态驱动装置,还有一路对象状态心跳:设备会自发发送 device_status (不带 command_id ,带 position ),每隔 hb_interval 秒一次。服务器可以修改这个间隔: ❮  { "type":"hb_interval", "seconds":<N> }.

5. 端到端加密:ownerSecret 的预置

端到端加密确保 服务器无法伪造开门 ——指令由应用签名。 ownerSecret (32 字节)并不烧进固件:由所有者的应用来投递 ,经 USB 串口:

PROVISION <64 hex>   → store the ownerSecret in NVS, return the fingerprint
WIPE                  → erase it (basic mode)
STATUS                → show the fingerprint
fingerprint = HMAC-SHA256(ownerSecret, "fp")[0..3]  (hex)

执行 PROVISION 或 WIPE 之后,控制器会带着更新后的 e2ee重新连接。已完成预置的控制器会 拒绝 普通的 device_command ——只适用 §6。

6. 端到端加密:挑战—应答式开启

使用 e2ee=true 而不是 device_command 时,走的是挑战—应答。新鲜性来自 一次性随机数,而不是时间:

1. ← sock_challenge_req  { "type":"sock_challenge_req", "command_id":"<id>" }
2. → sock_challenge      { "type":"sock_challenge", "command_id":"<id>", "nonce":"<b64 16B>" }
3. ← sock_fire           { "type":"sock_fire", "command_id","number_id",
                           "token":"<b64>", "owner_sig":"<b64>", "guest_id":<n>,
                           "nonce":"<b64>", "proof":"<b64>" }
4. → device_status success
token校验
所有者proof == HMAC-SHA256(ownerSecret, nonce)[0..15]
访客3 字节校验 owner_sig,派生 guest_key,校验 proof
token (3B) = [guest_did 2B LE][perms 1B]                      // a capability, no TTL
owner_sig  = HMAC-SHA256(ownerSecret, token)[0..15]
guest_key  = HKDF-SHA256(salt=null, ikm=ownerSecret, info="guest"||guest_did(2B LE), L=32)
proof      = HMAC-SHA256(guest_key, nonce)[0..15]

二进制字段用 base64 表示。随机数为 16 字节,签名截断到 16 字节。HKDF 的 salt=null 为 32 个零字节。

7. 吊销访客

❮  sock_revoke  { "type":"sock_revoke", "number_id","guest_id","version","revoked", "sig":"<b64>" }
→ sock_revoke_ack
sig = HMAC-SHA256(ownerSecret, "rev" || guest_id(2B LE) || version(4B LE) || revoked(1B))[0..15]

只有当 version 大于已保存的值时,控制器才会应用它——这是单调递增的,所以服务器既伪造不了,也没法把权限还回去。

8. 密码学原语

guest_key 的派生方式和签名,与 BLE 的访客分支完全一致——密码学部分是复用的。

9. 测试向量(端到端加密)

已知答案的向量。二进制字段同时给出十六进制和 base64;帧里传的是 base64。

ownerSecret (32B)  = a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
fingerprint        = b358977a   = HMAC(ownerSecret,"fp")[0..3]
nonce (16B)        = 000102030405060708090a0b0c0d0e0f   b64 = AAECAwQFBgcICQoLDA0ODw==

所有者开启(空令牌)

proof (16B) = 3fc0619c684a8261d06c1501ae4e726a   = HMAC(ownerSecret, nonce)[0..15]
proof b64   = P8BhnGhKgmHQbBUBrk5yag==

Guest fire (guest_did=0x0042, perms=0x01)

token (3B)      = 420001                              b64 = QgAB
owner_sig (16B) = 850a9f31fb708b176f4ed62a43acf0ad   = HMAC(ownerSecret, token)[0..15]  b64 = hQqfMftwixdvTtYqQ6zwrQ==
guest_key (32B) = 614da70a34890a807a25f7c5f271e530434c478f144370a2b9efaf0c58cd85c1
  = HKDF(salt=null, ownerSecret, info="guest"||did_LE, L=32)
proof (16B)     = a8a383ae56ac8a0182df2a275e220e83   = HMAC(guest_key, nonce)[0..15]  b64 = qKODrlasigGC3yonXiIOgw==

Revoke (guest_id=0x0042, version=3, revoked=1)

sig input = "rev"||guest_id_LE(2)||version_LE(4)||revoked(1) = 72657642000300000001
sig (16B) = 9433b96ddbb599f6c72bf17c0c9825fe   b64 = lDO5bdu1mfbHK/F8DJgl/g==

10. 获取 device_key(面向 OEM)

要让服务器认出设备并知道它归谁,需要这一对: device_key/device_secret,它为服务器所知,并绑定到所有者的账户。

两端的可用实现见 esp32-ws-example (MIT 许可)。要构建 .bin ——参见 浏览器配置器。这在开放架构中的位置: /open.