MQTT通信参考
连接使用mqtts、可信CA及主机名验证;当前测试服务地址为mqtts://yunstack-s.duruofu.cn:8883。客户端身份为ClientId=physicalId、Username=productId、Password=DeviceSecret。密码不写在URL中,不把客户端鉴权材料放入日志或通用固件。
Topic与ACL
以下以P=sys/{productId}/{physicalId}为前缀,设备发布时替换为自己的产品和物理标识,不能使用HTTP逻辑设备UUID。下行由后端发布;上行由设备发布。
| 相对P的Topic | 方向 | 内容 |
|---|---|---|
| thing/event/property/post | 上行 | 实际属性、新测量、连接元数据或保活 |
| thing/service/property/set | 下行 | 即时属性控制 |
| thing/service/property/set_reply | 上行 | 原msgId、明确code |
| thing/event/capability/post | 上行 | 当前epoch/boot的设备能力 |
| thing/config/set | 下行 | 持久完整scope快照 |
| thing/config/set_reply | 上行 | 当前配置接受/逐项应用结果 |
| thing/event/config_report/post | 上行 | 配置报告 |
| thing/service/{serviceId}/call | 下行 | 五个标准服务或内部binding_sync |
| thing/service/{serviceId}/call_reply | 上行 | 五个标准服务回复;归属使用binding事件 |
| thing/event/binding/post | 上行 | claim、ready、active、resume、boot_confirm、aborted |
| thing/ota/upgrade、thing/ota/notify | 下行 | OTA指令 |
| thing/ota/progress | 上行 | OTA阶段和结果 |
设备授权只在自己的精确前缀下允许已列下行订阅;禁止订阅#或其他设备。服务端订阅使用$share/group1/加mqtt.constants.ts白名单,包含property、set_reply、config回复、五个服务回复、OTA和事件。事件通配监听并不等于业务消费任意事件;新产品增加服务还须同步ACL、订阅与分发,不能仅修改物模型。
后端订阅与发布均QoS1,固件控制/配置使用非retain报文。QoS1可能重复,必须用msgId、版本和执行水位幂等;不得把PubAck当执行确认。固件拒绝retain控制/配置,后端对归属、能力、配置及服务回复拒绝retain,不使用保留消息恢复归属。后端单PM2实例,部分即时控制等待在内存,当前不宣称多实例完整支持。
示例约定
下例产品使用p_relay_4ch,physicalId为6055F97DE54C;UUID和epoch仅用于说明。运行时必须从当前有效归属和启动取得真实值,expiresAtUtc必须由当前UTC时间计算,不能复制旧时间常量。只读服务与持久配置故意没有targetBootId/有效期,不能为整齐补入未知字段。
即时控制
发布到sys/p_relay_4ch/6055F97DE54C/thing/service/property/set:
{"msgId":"11111111-1111-4111-8111-111111111111","bindingEpoch":"0123456789abcdef0123456789abcdef","targetBootId":"22222222-2222-4222-8222-222222222222","expiresAtUtc":1793203230,"params":{"relay_1":true}}expiresAtUtc表示UTC秒,当前后端生成约30秒有效窗口;发送前用Math.floor(Date.now()/1000)+30替换。该示例时间只在对应有效窗口可用。设备回复同主题的set_reply,最小成功体:
{"msgId":"11111111-1111-4111-8111-111111111111","code":200}Topic身份必须与等待请求一致。非200立即失败,缺code不成功;硬件写失败不改变内存/NVS为虚假成功,回复500并上报实际状态。
property/post实际状态示例:
{"msgId":"33333333-3333-4333-8333-333333333333","params":{"relay_1":true,"relay_2":false,"relay_3":false,"relay_4":false}}该普通属性报告没有epoch字段;归属/配置报告使用各自严格结构,不把不同消息类型的字段混用。
持久定时配置与接受回复
发布到同设备thing/config/set:
{"schemaVersion":1,"msgId":"44444444-4444-4444-8444-444444444444","bindingEpoch":"0123456789abcdef0123456789abcdef","scope":"schedules","revision":1,"params":{"timezoneMinutes":480,"rules":[{"id":1,"enabled":true,"trigger":{"kind":"weekly","weekdays":31,"minute":480},"action":{"type":"property.set","params":{"relay_1":true}}}]}}这表示UTC+8、周一至周五08:00开启通道一。全量替换scope;执行含义变化要换新规则ID。对应set_reply:
{"schemaVersion":1,"msgId":"44444444-4444-4444-8444-444444444444","bindingEpoch":"0123456789abcdef0123456789abcdef","bootId":"22222222-2222-4222-8222-222222222222","scope":"schedules","revision":1,"code":200,"acceptedRevision":1,"appliedRevision":1,"params":{"timezoneMinutes":480,"rules":[{"id":1,"enabled":true,"trigger":{"kind":"weekly","weekdays":31,"minute":480},"action":{"type":"property.set","params":{"relay_1":true}}}]}}settings使用同一封装,params必须是该产品完整可写设置快照。以四路继电器默认设置为例:
{"schemaVersion":1,"msgId":"55555555-5555-4555-8555-555555555555","bindingEpoch":"0123456789abcdef0123456789abcdef","scope":"settings","revision":1,"params":{"relay_1_boot_delay_s":0,"relay_2_boot_delay_s":0,"relay_3_boot_delay_s":0,"relay_4_boot_delay_s":0}}settings成功报告必须带items;每项包含id、desiredValue、可选reportedValue、acceptedRevision、appliedRevision及state。state=applied要求reportedValue严格等于desiredValue,state=pending_restart不带error,failed可带有界错误标识。非200报告不得附成功字段:
{"schemaVersion":1,"msgId":"55555555-5555-4555-8555-555555555555","bindingEpoch":"0123456789abcdef0123456789abcdef","bootId":"22222222-2222-4222-8222-222222222222","scope":"settings","revision":1,"code":409}完整settings应用成功回复示例(实际重启前若待生效,应使用pending_restart及旧reported,而不是套用本成功值):
{
"schemaVersion": 1,
"msgId": "55555555-5555-4555-8555-555555555555",
"bindingEpoch": "0123456789abcdef0123456789abcdef",
"bootId": "22222222-2222-4222-8222-222222222222",
"scope": "settings",
"revision": 1,
"code": 200,
"acceptedRevision": 1,
"appliedRevision": 1,
"params": {
"relay_1_boot_delay_s": 0,
"relay_2_boot_delay_s": 0,
"relay_3_boot_delay_s": 0,
"relay_4_boot_delay_s": 0
},
"items": [
{
"id": "relay_1_boot_delay_s",
"desiredValue": 0,
"reportedValue": 0,
"acceptedRevision": 1,
"appliedRevision": 1,
"state": "applied"
},
{
"id": "relay_2_boot_delay_s",
"desiredValue": 0,
"reportedValue": 0,
"acceptedRevision": 1,
"appliedRevision": 1,
"state": "applied"
},
{
"id": "relay_3_boot_delay_s",
"desiredValue": 0,
"reportedValue": 0,
"acceptedRevision": 1,
"appliedRevision": 1,
"state": "applied"
},
{
"id": "relay_4_boot_delay_s",
"desiredValue": 0,
"reportedValue": 0,
"acceptedRevision": 1,
"appliedRevision": 1,
"state": "applied"
}
]
}能力报告发布在thing/event/capability/post,完整四路示例:
{
"schemaVersion": 1,
"modelRevision": "1.0",
"bootId": "22222222-2222-4222-8222-222222222222",
"bindingEpoch": "0123456789abcdef0123456789abcdef",
"settings": [
"relay_1_boot_delay_s",
"relay_2_boot_delay_s",
"relay_3_boot_delay_s",
"relay_4_boot_delay_s"
],
"services": [
"config_get",
"config_apply_retry",
"schedule_get",
"countdown_set",
"countdown_cancel"
],
"scheduling": {
"propertyIds": [
"relay_1",
"relay_2",
"relay_3",
"relay_4"
],
"propertyTypes": [
"bool"
],
"maxRules": 16,
"maxCountdowns": 4,
"maxCountdownSeconds": 86400,
"timezoneMinMinutes": -720,
"timezoneMaxMinutes": 840
}
}标准服务
| serviceId | params | 权限/有效期 |
|---|---|---|
| config_get | {"scope":"settings"}或schedules | 有效分享者可读;无boot目标/有效期 |
| schedule_get | {} | 有效分享者可读;设备需支持定时 |
| config_apply_retry | 拥有者;仅当前failed已接受版本 | |
| countdown_set | {"action":{"type":"property.set","params":{"relay_1":false}},"seconds":60} | 拥有者;1至设备最大秒数 |
| countdown_cancel | 拥有者 |
写服务封装例:
{"msgId":"66666666-6666-4666-8666-666666666666","bindingEpoch":"0123456789abcdef0123456789abcdef","targetBootId":"22222222-2222-4222-8222-222222222222","expiresAtUtc":1793203230,"params":{"action":{"type":"property.set","params":{"relay_1":false}},"seconds":60}}同样必须重算expiresAtUtc。只读config_get请求的完整封装为:
{"msgId":"77777777-7777-4777-8777-777777777777","bindingEpoch":"0123456789abcdef0123456789abcdef","params":{"scope":"schedules"}}返回到对应call_reply。以下查询空规则的成功完整回复适用于该scope已经配置为revision=1的设备:
{"schemaVersion":1,"msgId":"77777777-7777-4777-8777-777777777777","serviceId":"config_get","bindingEpoch":"0123456789abcdef0123456789abcdef","bootId":"22222222-2222-4222-8222-222222222222","code":200,"result":{"configuration":{"scope":"schedules","revision":1,"acceptedRevision":1,"appliedRevision":1,"params":{"timezoneMinutes":0,"rules":[]}}}}未配置scope返回revision/accepted/applied均0、settings空params/items或schedules空rules和时区0,不能冒充已应用默认配置。非200服务回复不带result。字段、尺寸及取值校验由operation-contract.ts、configuration-report.ts、service-report.ts和固件wire测试共同约束。
故障与安全
默认控制/服务等待约30秒;服务请求记录120秒,设备占用35秒,自动查询记录120秒。retain、旧epoch、旧boot、错误Topic、未知字段、容量超限或冲突均应拒绝。持久配置使用版本幂等;短期写操作使用当前启动和有效期,不能通过重发过期消息恢复任务。归属与OTA有独立报文契约,见配网认领、OTA。
