ota地址更新,地址 配置在 default_srv.py中
authorsnickersun <snickersun@163.com>
Thu, 21 May 2026 01:35:11 +0000 (09:35 +0800)
committersnickersun <snickersun@163.com>
Thu, 21 May 2026 01:35:11 +0000 (09:35 +0800)
1.doc/K8_diaozhui/design/三代IOT设计文档 (简化版).pdf [new file with mode: 0644]
1.doc/K8_diaozhui/design/三代IOT设计文档(简化版).md [new file with mode: 0644]
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/constants.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/default_srv.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/srv_configs/beike/default_srv.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/products/srv_configs/beike_dev/default_srv.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/vc_ota.py
2.sdk/project/7PIN_1_5/sdk/services/microPython/ports/quectel/core/version_util.py

diff --git a/1.doc/K8_diaozhui/design/三代IOT设计文档 (简化版).pdf b/1.doc/K8_diaozhui/design/三代IOT设计文档 (简化版).pdf
new file mode 100644 (file)
index 0000000..82eeb23
Binary files /dev/null and "b/1.doc/K8_diaozhui/design/\344\270\211\344\273\243IOT\350\256\276\350\256\241\346\226\207\346\241\243 \357\274\210\347\256\200\345\214\226\347\211\210\357\274\211.pdf" differ
diff --git a/1.doc/K8_diaozhui/design/三代IOT设计文档(简化版).md b/1.doc/K8_diaozhui/design/三代IOT设计文档(简化版).md
new file mode 100644 (file)
index 0000000..773b353
--- /dev/null
@@ -0,0 +1,2054 @@
+# 三代 IOT 设计文档(简化版)
+
+> 由 PDF 自动提取:`1.doc/K8_diaozhui/design/三代IOT设计文档 (简化版).pdf`(30 页)  
+> 提取方式:PyMuPDF 文本导出;页眉页脚与版式可能有残留字符。
+
+## 目录(与固件版本相关)
+
+| 章节 | 指令 | 是否含 `version` |
+|------|------|------------------|
+| 设备 WebSocket 上报 | `device_info` | **是**(`data.version`) |
+| 设备 WebSocket 上报 | `post_heart_beat` | 否(仅 `battery`) |
+| 平台下发 | `get_device_state` | 触发设备回 `device_info` |
+| 小程序 WebSocket 推送 | `push_device_info_rest` | **是**(平台转发 `device_info` 结果) |
+| HTTP 查询 | `/ws-bus/query/device-state/{deviceId}` | 文档示例**无** `version` 字段 |
+
+---
+
+## Page 1
+
+三代IOT设计文档 (简化版)​
+概述​
+IOT物联网云平台:实现设备连接、消息上报。​
+业务通讯架构​
+IOT云平台​
+Websocket基础通讯协议​
+Json指令协议​
+字段​
+magic​
+version​
+type​
+playload_le
+n​
+payload​
+## Page 2
+
+说明​
+固定0xFFFE​
+0x01​
+参考类型说明​
+载荷长度​
+载荷​
+大小​
+2byte​
+1byte​
+1byte​
+4byte​
+N​
+type类型说明​
+类型值​
+类型名称​
+描述​
+0x00​
+设备登录​
+废弃,通过url中携带bz-dt来登录​
+0x01​
+用户登录​
+废弃,通过url中携带bz-ut来登录​
+0x02​
+心跳​
+废弃,通过Json来心跳即可​
+0x03​
+json内容​
+具体的内容,请参考协议内容,包括小程序与iot的,设备与iot的。​
+数据流格式​
+字段​
+magic​
+version​
+type​
+msgId​
+flag​
+playload_le
+n​
+payload​
+说明​
+固定0xFFFE​
+​
+0x01​
+​
+参考类型说
+明​
+​
+消息ID​
+​
+标记0x00结
+束,0x01未
+结束​
+载荷长度​
+载荷​
+大小​
+2byte​
+1byte​
+1byte​
+4byte​
+1byte​
+4byte​
+N​
+类型说明​
+类型值​
+类型名称​
+描述​
+0x04​
+下行监听音频​
+PCM/WAV/MP3流,由产品协商​
+0x05​
+上行监听音频​
+PCM/WAV/MP3流,由产品协商​
+0x06​
+图片下行​
+图片内容,格式由产品协商​
+0x07​
+图片上行​
+图片内容,格式由产品协商​
+0x08​
+上行留言语音​
+PCM/WAV/MP3流,由产品协商​
+连接认证​
+## Page 3
+
+在header上或者query上:设备携带token值,header上key是`authorization`。query上是:bz-
+ut/bz-dt,其中bz-ut是用户令牌,bz-dt是设备令牌。​
+访问地址​
+代码块​
+测试环境设备连接地址:ws(s)://dmp-dev.raisound.com/iot-api/ws-hub/dev/v1?bz-dt=​
+测试环境小程序连接地址:wss://dmp-dev.raisound.com/iot-api/ws-hub/user/v1?bz-ut​
+生产环境设备连接地址:ws(s)://wsiot-nocdn.raisound.com/ws-hub/dev/v1?bz-dt=​
+生产环境小程序连接地址:wss://wsiot-nocdn.raisound.com/ws-hub/user/v1?bz-ut=​
+(CDN)生产环境设备连接地址:ws(s)://wsiot.raisound.com/ws-hub/dev/v1?bz-dt=​
+(CDN)生产环境小程序连接地址:wss://wsiot.raisound.com/ws-hub/user/v1?bz-ut=​
+query需要携带设备令牌key为:bz-dt,用户令牌key为:bz-ut​
+Json指令数据结构​
+传输的数据由json格式进行传输。文件/图片/音频等数据传到对象存储中,然后把连接以指令的方式传
+给设备,由设备根据对象地址进行获取使用。​
+上报数据和下发数据的数据结构:​
+代码块​
+{​
+    "cmd":"命令字符串",​
+    "ts":1738283829​
+    "msg":"消息说明",​
+    "data":"JsonObject类型的数据对象"​
+}​
+•
+根据指令使用data里的数据​
+•
+data里的数据是个JsonObject​
+•
+设备ID会根据header上的token进行解析​
+NLP与IOT的http协议内容​
+通用指令接口✅​
+请求路径​
+/ws-bus/cmd/ai​
+应用场景​
+转发命令字符串给设备​
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+## Page 4
+
+请求方法​
+POST​
+请求参数​
+​
+•
+Query​
+◦
+deviceId:设备ID​
+◦
+deviceToken:设备token​
+◦
+cmd:ai_cmd​
+◦
+data:指令数据​
+响应结果​
+•
+下发失败​
+代码块​
+{​
+  "success": false,​
+  "msg": "设备不在线.",​
+  "code": 50000,​
+  "data": null​
+}​
+•
+下发成功​
+代码块​
+{​
+  "success": true,​
+  "msg": "下发成功.",​
+  "code": 20000,​
+  "data": null​
+}​
+​
+设备接收指令​
+代码块​
+{​
+    "cmd": "ai_cmd",​
+    "msg": "AI指令:ai_cmd",​
+    "data": "指令数据",​
+    "ts": 1761153310392,​
+    "targetDeviceId": "E405F8648007"​
+}​
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+7
+设备-IOT的WebSocket内容​
+## Page 5
+
+设备与iot云平台走的是websocket协议。​
+心跳✓​
+协议名称​
+心跳​
+应用场景​
+心跳,不超过5分钟即可,响应的bind表示已绑定用户,unbind表示未绑定,unbind的情况下
+需要打开蓝牙,供app扫描绑定。​
+指令​
+post_heart_beat​
+上报内容​
+代码块​
+{​
+    "cmd":"post_heart_beat",​
+    "ts":1738283829,​
+    "msg":"心跳",​
+    "data":{​
+        "battery": 20​
+    }​
+}​
+响应内容​
+代码块​
+{​
+    "cmd":"post_rest",​
+    "ts":1738283829,​
+    "msg":"消息收到响应",​
+    "data":{​
+        "cmd":"post_heart_beat",​
+        "rest":"bind/unbind",​
+        "msg":"上报成功/格式错误"​
+    }​
+}​
+1
+2
+3
+4
+5
+6
+7
+8
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+接收指令​
+下发用户解绑✓​
+协议名称​
+用户解绑设备推送​
+应用场景​
+设备收到这个指令以后,广播蓝牙,让小程序可以搜索到设备进行绑定​
+## Page 6
+
+指令​
+unbind_dev​
+下发内容​
+​
+代码块​
+{​
+    "cmd":"user_unbind_dev",​
+    "ts":1738283829,​
+    "msg":"解绑设备",​
+    "data":null​
+}​
+响应内容​
+​
+代码块​
+{​
+    "cmd":"dispatch_rest",​
+    "ts":1738283829,​
+    "msg":"消息收到响应",​
+    "data":{​
+        "cmd":"user_unbind_dev",​
+        "rest":"success|error",​
+        "msg":"收到用户解绑设备指令"​
+    }​
+}​
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+下发查找设备✓​
+指令​
+查找设备find_device​
+指令说明​
+收到该指令后,响铃 震动​
+指令内容​
+​
+代码块​
+{​
+    "cmd": "find_device",​
+    "msg": "查找设备",​
+    "data": null,​
+    "ts": 1759328001026,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+## Page 7
+
+下发音量修改✓​
+指令​
+音量修改switch_volume​
+指令说明​
+收到该指令后,对设备音量进行修改​
+指令内容​
+​
+代码块​
+{​
+    "cmd":"switch_volume",​
+    "data":{​
+        "target":"+增加音量;-减少音量;[0,100]修改音量为特定百分比
+值"​
+    }​
+}​
+1
+2
+3
+4
+5
+6
+下发开始声音克隆✓​
+指令​
+开始声音克隆start_voice_clone​
+指令说明​
+收到该指令后,开始声音克隆​
+指令内容​
+​
+代码块​
+{​
+    "cmd": "start_voice_clone",​
+    "msg": "开始声音克隆",​
+    "data": null,​
+    "ts": 1759329865731,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+下发获取获取设备状态✓​
+指令​
+get_device_state​
+应用场景​
+获取设备状态,状态会在推送中得到;调用时机,当UI需要电量、音量、wifi信号强弱时调
+用。​
+请求内容​
+代码块​
+{​
+1
+## Page 8
+
+    "cmd": "get_device_state",​
+    "msg": "获取设备状态",​
+    "data": null,​
+    "ts": 1759330147313,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+2
+3
+4
+5
+6
+7
+下发清除配网✓​
+指令​
+wifi_clean_pair​
+应用场景​
+用户在app上点击清除配网,用于毛绒玩具,没有按键的设备;前提条件是已连接当前网络。​
+请求内容​
+代码块​
+{​
+    "cmd": "start_voice_clone",​
+    "msg": "开始声音克隆",​
+    "data": null,​
+    "ts": 1759329865731,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+解绑​
+指令​
+device_unbind​
+应用场景​
+用户解绑设备时,下发这个指令,然后设备清除配网,清除本地数据,打开蓝牙广播。​
+请求内容​
+代码块​
+{​
+    "cmd":"device_unbind",​
+    "data":null​
+}​
+1
+2
+3
+4
+socket通道上报​
+当前服务状态上传✓​
+(聆听中|播放中|空闲中|思考中)​
+## Page 9
+
+指令​
+chat_state​
+指令说明​
+当前对话状态的上传​
+上报内容​
+​
+代码块​
+{​
+    "msg": "当前服务状态上传",​
+    "data": {​
+        "state": "listening|speaking|idle|thinking",​
+        "deviceId": "E405F8F71009"​
+    },​
+    "cmd": "chat_state",​
+    "ts": 1759979110​
+}​
+响应内容​
+代码块​
+{​
+    "cmd": "post_rest",​
+    "ts": 1759979110661,​
+    "data": {​
+        "cmd": "chat_state",​
+        "rest": "success",​
+        "msg": "收到服务状态上报结果."​
+    }​
+}​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+1
+2
+3
+4
+5
+6
+7
+8
+9
+上报对话文本(用于显示对话)✓​
+指令​
+chat_text​
+指令说明​
+对话内容上传​
+上报内容​
+​
+代码块​
+{​
+    "msg": "上报对话文本",​
+    "data": [​
+        {​
+            "deviceId": "E405F8F71009",​
+            "type": "req", //req:请求说的话​
+            "text": "今天天气如何?",​
+            "createTime": 1759979306​
+1
+2
+3
+4
+5
+6
+7
+8
+## Page 10
+
+        },​
+        {​
+            "deviceId": "E405F8F71009",​
+            "type": "res", //res:响应的内容​
+            "text": "今天暴雨,记得要带伞哦.?",​
+            "createTime": 1759979306​
+        }​
+    ],​
+    "cmd": "chat_text",​
+    "ts": 1759979306​
+}​
+响应内容​
+代码块​
+{​
+    "cmd": "post_rest",​
+    "ts": 1759977562040,​
+    "data": {​
+        "cmd": "chat_text",​
+        "rest": "success",​
+        "msg": "收到对话文本结果."​
+    }​
+}​
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+1
+2
+3
+4
+5
+6
+7
+8
+9
+上报设备信息(电量,Wi-Fi信号强度)✓​
+指令​
+device_info​
+指令说明​
+设备信息上报​
+上报内容​
+​
+代码块​
+{​
+    "msg": "上报设备信息",​
+    "data": {​
+        "deviceId": "E405F8F71009",​
+        "battery": "电池,百分比值,比如说30,25",​
+        "wifiRssi": "wifi强弱:0,1,2,3,4,5。转成这几个梯度",​
+        "volume": "百分比数值",​
+        "bargein": "打断模式:0/1",​
+        "currentRoleName": "当前音色的name",​
+        "charge": "0表示未充电,1表示充电中",​
+        "version": "版本号"​
+    },​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+## Page 11
+
+    "cmd": "device_info",​
+    "ts": 1759979415​
+}​
+响应内容​
+代码块​
+{​
+    "cmd": "post_rest",​
+    "ts": 1759975798004,​
+    "data": {​
+        "cmd": "device_info",​
+        "rest": "success",​
+        "msg": "收到设备信息结果."​
+    }​
+}​
+13
+14
+15
+1
+2
+3
+4
+5
+6
+7
+8
+9
+获取智能体ID(主动上报)✓​
+指令​
+get_bot​
+指令说明​
+获取智能体配置,会在set_bot指令响应​
+上报内容​
+​
+代码块​
+{​
+    "msg": "获取智能体ID",​
+    "data": null,​
+    "cmd": "get_bot",​
+    "ts": 1759979520​
+}​
+响应内容​
+代码块​
+{​
+    "cmd": "set_bot",​
+    "msg": "简单设备信息",​
+    "data": {​
+        "agent": {​
+            "name": "孙悟空",​
+            "speakerId": "zh_male_sunwukong_mars_bigtts",​
+            "id": "sunwukong",​
+            "speakerType": "default"​
+        },​
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+## Page 12
+
+        "speaker": {​
+            "name": "猴哥",​
+            "language": "中文",​
+            "id": "zh_male_sunwukong_mars_bigtts",​
+            "type": "default"​
+        },​
+        "bargein": null,​
+        "memoryId": null,​
+        "longTermMemory": null​
+    },​
+    "ts": 1759974598622,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+小程序与IOT的http协议内容​
+小程序与iot云平台:指令下发,数据回显,走的是Https协议。​
+参考文档:http://118.145.159.222:30700/doc.html​
+下发指令​
+通用接口✓​
+请求路径​
+/ws-bus/cmd/{deviceId}​
+请求方法​
+POST​
+请求参数​
+•
+path​
+◦
+deviceId 设备Id​
+•
+Query​
+◦
+cmd:指令内容​
+◦
+data:下发数据​
+响应结果​
+代码块​
+{​
+  "success": true,​
+  "msg": "下发成功.",​
+  "code": 20000,​
+  "data": null​
+}​
+1
+2
+3
+4
+5
+6
+## Page 13
+
+查找设备✓​
+指令​
+查找设备find_device​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:find_device​
+◦
+data:​
+应用场景​
+app向设备下发此指令,设备收到后响铃震动,用于找到设备所在的位置​
+设备接收指令
+内容​
+代码块​
+{​
+    "cmd": "find_device",​
+    "msg": "查找设备",​
+    "data": null,​
+    "ts": 1759328001026,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+音色切换✓​
+指令​
+音色切换switch_accent​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:switch_accent​
+◦
+data:​
+应用场景​
+修改音色,通过此接口切换设备TTS播报的音色​
+设备接收指令
+内容​
+​
+代码块​
+{​
+    "cmd": "switch_accent",​
+    "msg": "音色切换",​
+    "data": {​
+        "target": "1"​
+1
+2
+3
+4
+5
+## Page 14
+
+    },​
+    "ts": 1759328048259,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+6
+7
+8
+9
+设置智能体✓​
+指令​
+设置智能体set_bot​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:set_bot​
+◦
+data: {"botId": "7462617881076351002", "botName": "小量博士"}​
+应用场景​
+app向设备下发智能体配置,该配置会被后台保存,设备开机时向后台获取上次配置​
+设备接收指令
+内容​
+代码块​
+{​
+    "cmd": "set_bot",​
+    "msg": "设置智能体",​
+    "data": {​
+        "bot_name": "小量博士",​
+        "bot_id": "7462617881076351002"​
+    },​
+    "ts": 1759331069327,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+开始声音克隆✓​
+指令​
+开始声音克隆start_voice_clone​
+## Page 15
+
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:start_voice_clone​
+◦
+data:​
+应用场景​
+声音克隆,音色复刻​
+设备接收指令
+内容​
+代码块​
+{​
+    "cmd": "start_voice_clone",​
+    "msg": "开始声音克隆",​
+    "data": null,​
+    "ts": 1759329865731,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+清除配网✓​
+指令​
+清除配网 wifi_clean_pair​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:wifi_clean_pair​
+◦
+data:​
+应用场景​
+用户在app上点击清除配网,用于毛绒玩具,没有按键的设备;前提条件是已连接当前网络。​
+设备接收指令
+内容​
+代码块​
+{​
+    "cmd": "wifi_clean_pair",​
+    "msg": "清除配网",​
+    "data": null,​
+    "ts": 1759329910033,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+## Page 16
+
+获取设备状态✓​
+指令​
+get_device_state​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:get_device_state​
+◦
+data:​
+应用场景​
+获取设备状态,状态会在推送中得到;调用时机,当UI需要电量、音量、wifi信号强弱时调
+用。​
+设备接收指令
+内容​
+代码块​
+{​
+    "cmd": "get_device_state",​
+    "msg": "获取设备状态",​
+    "data": null,​
+    "ts": 1759330147313,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+更新设备✓​
+指令​
+让设备重新获取配置信息renew_device​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:renew_device​
+◦
+data:​
+应用场景​
+app向设备下发此指令,设备收到后提示“更新配置中,设备将自动重启”,设备从dmp接口
+获取设备的配置信息,并重新发起对话​
+设备接收指令
+内容​
+​
+代码块​
+{​
+    "cmd": "renew_device",​
+    "msg": "更新设备",​
+1
+2
+3
+## Page 17
+
+    "data": null,​
+    "ts": 1759330208898,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+4
+5
+6
+7
+更新配置角色-dmp平台的角色✓​
+指令​
+通知设备更新了角色配置 setup_role​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:setup_role​
+◦
+data:​
+应用场景​
+app向设备下发此指令,设备收到后提示“更新配置中”,并重新发起对话​
+设备接收指令
+内容​
+​
+代码块​
+{​
+    "cmd": "setup_role",​
+    "msg": "更新配置角色",​
+    "data": null,​
+    "ts": 1759330265367,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+设置打断模式✓​
+指令​
+set_break_off​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:set_break_off​
+◦
+data:1​
+应用场景​
+用户在app上选择打断模式。​
+## Page 18
+
+设备接收指令
+内容​
+代码块​
+{​
+    "cmd": "set_break_off",​
+    "msg": "设置打断模式",​
+    "data": {​
+        "target": "1:按键打断;2:插话打断"​
+    },​
+    "ts": 1759330363089,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+设置亮度✓​
+指令​
+亮度修改switch_brightness​
+请求示例​
+•
+path​
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:switch_brightness​
+◦
+data:50​
+指令说明​
+用户在app上滑动亮度调节。​
+设备接收指令
+内容​
+​
+代码块​
+{​
+    "cmd": "switch_brightness",​
+    "msg": "设置亮度",​
+    "data": {​
+        "target": "[0,100]修改亮度为特定百分比值"​
+    },​
+    "ts": 1759330840342,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+设置音量✓​
+指令​
+设置音量修改switch_volume​
+请求示例​
+•
+path​
+## Page 19
+
+◦
+deviceId:E405F8F71009​
+•
+Query​
+◦
+cmd:switch_volume​
+◦
+data:+20​
+指令说明​
+收到该指令后,对设备音量进行修改​
+设备接收指令
+内容​
+​
+代码块​
+{​
+    "cmd": "switch_volume",​
+    "msg": "音量修改",​
+    "data": {​
+        "target":"+增加音量;-减少音量;[0,100]修改音量为特定百分比
+值"​
+    },​
+    "ts": 1759331616977,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+查询接口​
+获取设备的在线状态✓​
+接口名称​
+获取设备的在线状态​
+业务说明​
+小程序APP在打开首页的时候,先检查设备的在线状态。如果不在线,显示配网按钮​
+请求路径​
+/ws-bus/query/device-state/{deviceId}​
+请求方法​
+GET​
+请求参数​
+•
+path​
+◦
+deviceId 设备Id​
+响应结果​
+​
+•
+设备不在线​
+代码块​
+{​
+  "success": true,​
+  "msg": "获取设备状态成功.",​
+  "code": 20000,​
+  "data": {​
+1
+2
+3
+4
+5
+## Page 20
+
+    "id": "68d4e5acbe41c865bbf8055d",​
+    "deviceId": "E405F8F71009",​
+    "battery": 1,​
+    "mediaState": "stop",​
+    "mediaTitle": null,​
+    "deviceState": "offline",​
+    "volume": 50,​
+    "mediaCover": null,​
+    "createTime": 1758782892683,​
+    "updateTime": 1758782892683​
+  }​
+}​
+•
+无权限获取​
+代码块​
+{​
+  "timestamp": "2025-10-01 21:36",​
+  "status": 403,​
+  "error": "Forbidden",​
+  "path": "/query/device-state/E405F8F71001"​
+}​
+•
+ 设备在线​
+代码块​
+{​
+  "success": true,​
+  "msg": "获取设备状态成功.",​
+  "code": 20000,​
+  "data": {​
+    "id": "68d4e5acbe41c865bbf8055d",​
+    "deviceId": "E405F8F71009",​
+    "battery": 1,​
+    "mediaState": "stop",​
+    "mediaTitle": null,​
+    "deviceState": "online",​
+    "volume": 50,​
+    "mediaCover": null,​
+    "createTime": 1758782892683,​
+    "updateTime": 1758782892683​
+  }​
+}​
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+## Page 21
+
+IOT与小程序的websocket协议​
+iot平台推送数据给小程序,走的是websocket协议,传输的内容参考以下格式:​
+心跳✓​
+协议名称​
+心跳​
+应用场景​
+心跳,不超过1分钟即可​
+指令​
+post_heart_beat​
+上传内容​
+代码块​
+{​
+    "cmd":"post_heart_beat",​
+    "ts":1738283829,​
+    "msg":"心跳",​
+    "data":null​
+}​
+响应内容​
+代码块​
+{​
+    "cmd":"post_rest",​
+    "ts":1738283829,​
+    "msg":"消息收到响应",​
+    "data":{​
+        "cmd":"post_heart_beat",​
+        "rest":"success|error",​
+        "msg":"上报成功/格式错误"​
+    }​
+}​
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+接收指令​
+接收设备状态(上线/下线)变化✓​
+协议名称​
+设备状态(上线/下线)变化
+应用场景​
+## Page 22
+
+当设备连上iot的websocket,通知用户设备已上线;当设备断开iot的websocket时,通知用户和
+设备已下线​
+指令​
+push_dev_state​
+设备上线通
+知​
+​
+代码块​
+{​
+    "cmd": "push_dev_state",​
+    "msg": null,​
+    "data": {​
+        "id": "68d4e5acbe41c865bbf8055d",​
+        "deviceId": "E405F8F71009",​
+        "battery": 1,​
+        "mediaState": "stop",​
+        "mediaTitle": null,​
+        "deviceState": "online",​
+        "volume": 50,​
+        "mediaCover": null,​
+        "createTime": 1758782892683,​
+        "updateTime": 1758782892683​
+    },​
+    "ts": 1759324958781,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+设备下线通
+知​
+​
+代码块​
+{​
+    "cmd": "push_dev_state",​
+    "msg": null,​
+    "data": {​
+        "id": "68d4e5acbe41c865bbf8055d",​
+        "deviceId": "E405F8F71009",​
+        "battery": 1,​
+        "mediaState": "stop",​
+        "mediaTitle": null,​
+        "deviceState": "offline",​
+        "volume": 50,​
+        "mediaCover": null,​
+        "createTime": 1758782892683,​
+        "updateTime": 1758782892683​
+    },​
+    "ts": 1759325118496,​
+    "targetDeviceId": "E405F8F71009"​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+## Page 23
+
+}​
+18
+接收当前服务状态✓​
+(聆听中|播放中|空闲中|思考中)​
+指令​
+push_chat_state_rest​
+应用场景​
+设备对话中,把对话的状态上报给后台,后台把状态推送给app​
+请求内容​
+代码块​
+{​
+    "cmd": "push_chat_state_rest",​
+    "msg": "收到设备服务状态上报结果.",​
+    "data": {​
+        "deviceId": "E405F8F71009",​
+        "state": "listening"​
+    },​
+    "ts": 1759979110670,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+接收对话文本(用于显示对话)✓​
+指令​
+push_chat_text_rest​
+指令说明​
+当前对话状态的上传​
+上报内容​
+​
+代码块​
+{​
+    "cmd": "push_chat_text_rest",​
+    "msg": "收到对话文本结果.",​
+    "data": [​
+        {​
+            "deviceId": "E405F8F71009",​
+1
+2
+3
+4
+5
+6
+## Page 24
+
+            "type": "req",​
+            "text": "今天天气如何?",​
+            "createTime": 1759979306​
+        },​
+        {​
+            "deviceId": "E405F8F71009",​
+            "type": "res",​
+            "text": "今天暴雨,记得要带伞哦.?",​
+            "createTime": 1759979306​
+        }​
+    ],​
+    "ts": 1759979306740,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+接收设备信息(电量,Wi-Fi信号强度)✓​
+指令​
+push_device_info_rest​
+指令说明​
+设备信息上报​
+上报内容​
+​
+代码块​
+{​
+    "cmd": "push_device_info_rest",​
+    "msg": "收到设备信息结果.",​
+    "data": {​
+        "deviceId": "E405F8F71009",​
+        "battery": "电池,百分比值,比如说30,25",​
+        "wifiRssi": "wifi强弱:0,1,2,3,4,5。转成这几个梯度",​
+        "volume": "百分比数值",​
+        "bargein": "打断模式:0/1",​
+        "currentRoleName": "当前音色的name",​
+        "charge": "0表示未充电,1表示充电中",​
+        "version": "版本号"​
+    },​
+    "ts": 1759979416209,​
+    "targetDeviceId": "E405F8F71009"​
+}​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+SDK​
+## Page 25
+
+go版本​
+代码块​
+package test​
+​
+import (​
+    "bytes"​
+    "context"​
+    "encoding/binary"​
+    "fmt"​
+    "net/http"​
+    "os"​
+    "sync"​
+    "time"​
+​
+    ".go_vvvdevice/common"​
+    ".go_vvvdevice/log"​
+    ".go_vvvdevice/modules"​
+    "github.com/gorilla/websocket"​
+)​
+​
+var wg sync.WaitGroup​
+​
+func wsClient() *websocket.Conn {​
+    ctx, cancel := context.WithTimeout(context.Background(), 7*time.Second)​
+    defer cancel()​
+​
+    urlStr := fmt.Sprintf("wss://dmp-dev.raisound.com/iot-api/ws-hub/dev/v1?bz-
+dt=%s", "954668a315d61dd4cacacb01b255387bca")​
+    header := http.Header{​
+       "User-Agent": []string{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
+AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"},​
+    }​
+​
+    fmt.Println(urlStr)​
+    ws, _, wsErr := websocket.DefaultDialer.DialContext(ctx, urlStr, header)​
+    if wsErr != nil {​
+       log.Warn(fmt.Sprintf("Dial failed: %s", wsErr.Error()))​
+    }​
+    return ws​
+}​
+​
+// Json数据​
+func fmtJsonMsg(jsonStr string) []byte {​
+    log.Debug(fmt.Sprintf("拼接数据: %s", jsonStr))​
+    // 创建一个字节缓冲区​
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+## Page 26
+
+    buf := new(bytes.Buffer)​
+    // 写入头部​
+    buf.Write([]byte{0xFF, 0xFE})​
+    // 写入版本​
+    buf.WriteByte(0x01)​
+    // 写入类型​
+    buf.WriteByte(0x03)​
+    // 将 JSON 字符串转换为字节数组​
+    jsonBytes := []byte(jsonStr)​
+    // 计算载荷长度​
+    jsonLen := len(jsonBytes)​
+    jsonLenBytes := make([]byte, 4)​
+    // 载荷长度转大端序(高位在前)​
+    binary.BigEndian.PutUint32(jsonLenBytes, uint32(jsonLen))​
+    // 写入长度​
+    buf.Write(jsonLenBytes)​
+    // 写入 JSON 数据​
+    buf.Write(jsonBytes)​
+    return buf.Bytes()​
+}​
+​
+func Run() {​
+    wg.Add(1)​
+​
+    // 创建WebSocket连接​
+    client := wsClient()​
+    defer func() {​
+       _ = client.Close()​
+       os.Exit(0)​
+    }()​
+​
+    heartTicker := time.NewTicker(3 * time.Second)​
+    // 心跳协程​
+    go func() {​
+       for {​
+          select {​
+          case <-heartTicker.C:​
+             heartData := 
+fmtJsonMsg(fmt.Sprintf(`{"cmd":"post_heart_beat","ts":%d,"msg":"心跳","data":
+{"battery":%d}}`, time.Now().Unix(), common.RandInt(1, 101)))​
+             log.Debug(fmt.Sprintf("Write Message: %v", heartData))​
+             _, wErr := modules.SendMsg(client, heartData, 
+websocket.BinaryMessage)​
+             if wErr != nil {​
+                log.Error(fmt.Sprintf("SendMsg Error: %s", wErr))​
+                reStart()​
+             }​
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+## Page 27
+
+          }​
+       }​
+    }()​
+​
+    // 接收协程​
+    go func() {​
+       for {​
+          // 接收服务器返回的信息​
+          _, result, readErr := client.ReadMessage()​
+          log.Debug(fmt.Sprintf("服务器返回: %v\n字符串内容: %s", result, 
+string(result)))​
+          if readErr != nil {​
+             log.Error(fmt.Sprintf("ReadMessage Error: %s", readErr.Error()))​
+             reStart()​
+          }​
+       }​
+    }()​
+    wg.Wait()​
+}​
+​
+func reStart() {​
+    wg.Done()​
+    time.Sleep(300 * time.Millisecond)​
+    Run()​
+}​
+java版本​
+代码块​
+package com.dobell.iot;​
+​
+import java.net.URI;​
+import java.net.http.HttpClient;​
+import java.net.http.WebSocket;​
+import java.nio.ByteBuffer;​
+import java.nio.ByteOrder;​
+import java.time.Instant;​
+import java.util.Random;​
+import java.util.concurrent.*;​
+​
+public class WsClient {​
+​
+    private static final String URL =​
+            "wss://dmp-dev.raisound.com/iot-api/ws-hub/dev/v1?bz-
+dt=954668a315d61dd4cacacb01b255387bca";​
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+## Page 28
+
+​
+    private static WebSocket webSocket;​
+    private static ScheduledExecutorService scheduler;​
+    private static final Random RANDOM = new Random();​
+​
+    /** ===== main 启动入口 ===== */​
+    public static void main(String[] args) {​
+        start();​
+    }​
+​
+    /** 启动逻辑(等价 Go 的 Run) */​
+    private static void start() {​
+        try {​
+            scheduler = Executors.newScheduledThreadPool(2);​
+            connect();​
+            startHeartbeat();​
+​
+            // 阻塞主线程,防止 main 退出​
+            Thread.currentThread().join();​
+        } catch (Exception e) {​
+            e.printStackTrace();​
+            restart();​
+        }​
+    }​
+​
+    /** 建立 WebSocket 连接 */​
+    private static void connect() {​
+        HttpClient client = HttpClient.newHttpClient();​
+​
+        webSocket = client.newWebSocketBuilder()​
+                .connectTimeout(java.time.Duration.ofSeconds(7))​
+                .header("User-Agent",​
+                        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
+AppleWebKit/537.36 " +​
+                                "(KHTML, like Gecko) Chrome/138.0.0.0 
+Safari/537.36")​
+                .buildAsync(URI.create(URL), new WsListener())​
+                .join();​
+​
+        System.out.println("WebSocket connected");​
+    }​
+​
+    /** 心跳任务(3 秒一次) */​
+    private static void startHeartbeat() {​
+        scheduler.scheduleAtFixedRate(() -> {​
+            try {​
+                long ts = Instant.now().getEpochSecond();​
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+## Page 29
+
+                int battery = RANDOM.nextInt(100) + 1;​
+​
+                String json = String.format(​
+                        "{\"cmd\":\"post_heart_beat\",\"ts\":%d,\"msg\":\"心跳
+\",\"data\":{\"battery\":%d}}",​
+                        ts, battery​
+                );​
+​
+                ByteBuffer data = buildBinaryMsg(json);​
+                webSocket.sendBinary(data, true).join();​
+​
+                System.out.println("Send heartbeat: " + json);​
+            } catch (Exception e) {​
+                e.printStackTrace();​
+                restart();​
+            }​
+        }, 0, 3, TimeUnit.SECONDS);​
+    }​
+​
+    /** 二进制协议封包(完全等价 Go fmtJsonMsg) */​
+    private static ByteBuffer buildBinaryMsg(String json) {​
+        byte[] jsonBytes = json.getBytes();​
+        ByteBuffer buffer = ByteBuffer.allocate(2 + 1 + 1 + 4 + 
+jsonBytes.length);​
+        buffer.order(ByteOrder.BIG_ENDIAN);​
+​
+        buffer.put((byte) 0xFF);​
+        buffer.put((byte) 0xFE);​
+        buffer.put((byte) 0x01); // version​
+        buffer.put((byte) 0x03); // type​
+        buffer.putInt(jsonBytes.length);​
+        buffer.put(jsonBytes);​
+        buffer.flip();​
+​
+        return buffer;​
+    }​
+​
+    /** 重连(等价 Go reStart) */​
+    private static synchronized void restart() {​
+        try {​
+            System.err.println("Restarting...");​
+            if (scheduler != null) {​
+                scheduler.shutdownNow();​
+            }​
+            if (webSocket != null) {​
+                webSocket.abort();​
+            }​
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+## Page 30
+
+            Thread.sleep(300);​
+            start();​
+        } catch (Exception ignored) {​
+        }​
+    }​
+​
+    /** WebSocket 回调 */​
+    static class WsListener implements WebSocket.Listener {​
+​
+        @Override​
+        public CompletionStage<?> onBinary(WebSocket ws,​
+                                           ByteBuffer data,​
+                                           boolean last) {​
+            byte[] bytes = new byte[data.remaining()];​
+            data.get(bytes);​
+            System.out.println("Server(binary): " + new String(bytes));​
+            return WebSocket.Listener.super.onBinary(ws, data, last);​
+        }​
+​
+        @Override​
+        public CompletionStage<?> onText(WebSocket ws,​
+                                         CharSequence data,​
+                                         boolean last) {​
+            System.out.println("Server(text): " + data);​
+            return WebSocket.Listener.super.onText(ws, data, last);​
+        }​
+​
+        @Override​
+        public void onError(WebSocket ws, Throwable error) {​
+            System.err.println("WebSocket error: " + error.getMessage());​
+            restart();​
+        }​
+​
+        @Override​
+        public CompletionStage<?> onClose(WebSocket ws,​
+                                          int statusCode,​
+                                          String reason) {​
+            System.err.println("WebSocket closed: " + statusCode + " " + 
+reason);​
+            restart();​
+            return WebSocket.Listener.super.onClose(ws, statusCode, reason);​
+        }​
+    }​
+}​
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
index b905e29..bd92bd2 100644 (file)
@@ -208,10 +208,17 @@ try:
         DMP_BASE = _srv.get_addr("DMP_BASE", "")
         AMP_SERVER = _srv.get_addr("AMP_SERVER", "")
         IOT_WS_URL = _srv.get_addr("IOT_WS_URL", "")
+        OTA_TASK_URL_FMT = _srv.get_addr("OTA_TASK_URL_FMT", "")
+        OTA_RESULT_PATH_FMT = _srv.get_addr(
+            "OTA_RESULT_PATH_FMT",
+            "/ota/result?deviceId={}&currentVersion={}",
+        )
     else:
         DMP_BASE = ""
         AMP_SERVER = ""
         IOT_WS_URL = ""
+        OTA_TASK_URL_FMT = ""
+        OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"
 
     # 兼容历史:提供与旧 _SERVER_CFG 相似的结构
     _SERVER_CFG = {
@@ -226,6 +233,8 @@ except Exception:
     DMP_BASE = ""
     AMP_SERVER = ""
     IOT_WS_URL = ""
+    OTA_TASK_URL_FMT = ""
+    OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"
     _SERVER_CFG = {SERVER_ENV: {"dmp_base": "", "amp_server": "", "iot_ws_url": ""}}
 
 
index c333e1a..d282d90 100644 (file)
 #   路径:ai_VC 内目前为字面量 "/dev/..."(与下方 DMP_*_PATH 取值应对齐)\r
 #   示例:{DMP_BASE}/dev/time 、{DMP_BASE}/dev/auth/active\r
 #\r
-# 【OTA 检查任务 — ai_VC OTA 查询】\r
-#   当前 ai_VC 使用完整 URL 字面量(应与 OTA_TASK_URL_FMT 一致):\r
-#     OTA_TASK_URL_FMT.format(imei, sourceVersion)\r
-#   OTA 结果上报:{DMP_BASE}/ota/result?deviceId=...(路径与 OTA_RESULT_PATH_FMT 应对齐)\r
+# 【OTA 检查任务 — vc_ota.min_sys_ota / bt_ota】\r
+#   constants.OTA_TASK_URL_FMT.format(imei, sourceVersion)\r
+#   OTA 结果上报:{DMP_BASE} + OTA_RESULT_PATH_FMT(vc_ota.ota_result)\r
 #\r
 # 【AMP 健康检查 — ai_VC 错误上报】\r
 #   使用 self.server_ip(同 AMP_SERVER)+ 硬编码路径:\r
@@ -89,7 +88,7 @@ DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"     # 设备配置,format(deviceId)
 # ---- OTA ----\r
 \r
 # 完整 URL 模板;占位符 {} 依次为 deviceId(IMEI)、sourceVersion\r
-OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
+OTA_TASK_URL_FMT = "http://dmp-dev.raisound.com/dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
 \r
 # 相对 DMP_BASE 的路径模板(上报 OTA 结果)\r
 OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"\r
index 7915225..dda1084 100644 (file)
@@ -17,7 +17,7 @@ DMP_TOKEN_PATH = "/dev/auth/token"
 DMP_ICCID_PATH = "/dev/iccid"
 DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"
 
-OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"
+OTA_TASK_URL_FMT = "http://dmp.raisound.com/bz-api/ota/task?deviceId={}&sourceVersion={}"
 OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"
 
 AMP_HEALTH_URL_FMT = "http://{}/health?task_id={}"
index c333e1a..d282d90 100644 (file)
 #   路径:ai_VC 内目前为字面量 "/dev/..."(与下方 DMP_*_PATH 取值应对齐)\r
 #   示例:{DMP_BASE}/dev/time 、{DMP_BASE}/dev/auth/active\r
 #\r
-# 【OTA 检查任务 — ai_VC OTA 查询】\r
-#   当前 ai_VC 使用完整 URL 字面量(应与 OTA_TASK_URL_FMT 一致):\r
-#     OTA_TASK_URL_FMT.format(imei, sourceVersion)\r
-#   OTA 结果上报:{DMP_BASE}/ota/result?deviceId=...(路径与 OTA_RESULT_PATH_FMT 应对齐)\r
+# 【OTA 检查任务 — vc_ota.min_sys_ota / bt_ota】\r
+#   constants.OTA_TASK_URL_FMT.format(imei, sourceVersion)\r
+#   OTA 结果上报:{DMP_BASE} + OTA_RESULT_PATH_FMT(vc_ota.ota_result)\r
 #\r
 # 【AMP 健康检查 — ai_VC 错误上报】\r
 #   使用 self.server_ip(同 AMP_SERVER)+ 硬编码路径:\r
@@ -89,7 +88,7 @@ DMP_DEV_CONFIG_PATH_FMT = "/dev/config/{}"     # 设备配置,format(deviceId)
 # ---- OTA ----\r
 \r
 # 完整 URL 模板;占位符 {} 依次为 deviceId(IMEI)、sourceVersion\r
-OTA_TASK_URL_FMT = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
+OTA_TASK_URL_FMT = "http://dmp-dev.raisound.com/dmp-api/ota/task?deviceId={}&sourceVersion={}"\r
 \r
 # 相对 DMP_BASE 的路径模板(上报 OTA 结果)\r
 OTA_RESULT_PATH_FMT = "/ota/result?deviceId={}&currentVersion={}"\r
index 168903d..8df37fe 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-\r
-"""FOTA / DMP 上报。"""\r
+"""FOTA / DMP 上报。小程序上的版本,是这个ota接口上报的。"""\r
 try:\r
     from qp_log import qp_print as print\r
 except Exception:\r
@@ -16,6 +16,29 @@ import constants
 import version_util\r
 from misc import Power\r
 \r
+def _build_ota_task_url(source_version):\r
+    """OTA 检查任务 URL:default_srv.OTA_TASK_URL_FMT,否则 DMP_BASE + /ota/task。"""\r
+    fmt = getattr(constants, "OTA_TASK_URL_FMT", None) or ""\r
+    if not fmt:\r
+        base = getattr(constants, "DMP_BASE", None) or ""\r
+        if base:\r
+            fmt = base + "/ota/task?deviceId={}&sourceVersion={}"\r
+    if not fmt:\r
+        return None\r
+    return fmt.format(modem.getDevImei(), source_version)\r
+\r
+\r
+def _build_ota_result_url(current_version):\r
+    """OTA 结果上报:DMP_BASE + default_srv.OTA_RESULT_PATH_FMT。"""\r
+    path_fmt = getattr(\r
+        constants,\r
+        "OTA_RESULT_PATH_FMT",\r
+        "/ota/result?deviceId={}&currentVersion={}",\r
+    )\r
+    base = getattr(constants, "DMP_BASE", None) or ""\r
+    return base + path_fmt.format(modem.getDevImei(), current_version)\r
+\r
+\r
 class OTA():\r
     def __init__(self):\r
         #EventMesh.subscribe("min_sys_ota",self.min_sys_ota)\r
@@ -48,9 +71,10 @@ class OTA():
             if bt_versions == None:\r
                 bt_versions=""\r
             print("bt_versions:{}".format(bt_versions))\r
-            #url = "http://iot.api.thisiot.com/ota/task?deviceId=860132060348836&sourceVersion=2024-12-20-15:35-1"\r
-            url =None\r
-            url = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}".format(modem.getDevImei(),sourceVersion)\r
+            url = _build_ota_task_url(sourceVersion)\r
+            if not url:\r
+                print("bt_ota: OTA_TASK_URL_FMT 未配置")\r
+                return 0\r
             print(url)\r
             response = request.get(url,timeout=5)\r
             if response.status_code == 200:\r
@@ -109,8 +133,10 @@ class OTA():
         try:\r
             sourceVersion = version_util.get_unified_version()\r
             print("sourceVersion:{}".format(sourceVersion))\r
-            url =None\r
-            url = "https://dmp-api.qipai.com/qp-dmp-api/ota/task?deviceId={}&sourceVersion={}".format(modem.getDevImei(),sourceVersion)\r
+            url = _build_ota_task_url(sourceVersion)\r
+            if not url:\r
+                print("min_sys_ota: OTA_TASK_URL_FMT 未配置")\r
+                return 0\r
             print(url)\r
             response = request.get(url,timeout=5)\r
             if response.status_code == 200:\r
@@ -198,7 +224,7 @@ class OTA():
         try:\r
             currentVersion = version_util.get_unified_version()\r
             print("currentVersion:{}".format(currentVersion))\r
-            url = (constants.DMP_BASE + "/ota/result?deviceId={}&currentVersion={}").format(modem.getDevImei(),currentVersion)\r
+            url = _build_ota_result_url(currentVersion)\r
             print(url)\r
             response = request.post(url,timeout=5)\r
             if response.status_code == 200:\r
index abeff68..2c1ecf7 100644 (file)
@@ -9,7 +9,7 @@ def _char_is_digit(ch):
 
 
 def _build_time_token_sanitize(s):
-    """ASCII 兜底:不依赖 isdigit/逐字符迭代,仅 replace + split。"""
+    #ASCII 兜底:不依赖 isdigit/逐字符迭代,仅 replace + split。
     if not s:
         return ""
     out = str(s).strip()
@@ -31,7 +31,8 @@ def _build_time_token_sanitize(s):
 
 
 def _build_time_token():
-    """从 versions.Build_Time 解析,输出 yyyy_MM_dd_HH_mm(不含秒)。"""
+    #从 versions.Build_Time 解析,输出 yyyy_MM_dd_HH_mm(不含秒)。
+    #Build_Time="2026-05-20 17:39:09" --> yyyy_MM_dd_HH_mm
     try:
         s = str(getattr(versions, "Build_Time", "") or "").strip().strip('"').strip("'")
     except Exception:
@@ -77,6 +78,9 @@ def get_unified_version():
         ver = str(getattr(versions, "Version", "") or "").strip()
     except Exception:
         ver = ""
+    # 对外展示统一为小写 v(k8_v1.2_xxx),与 product_*.ini 中 VERSION=V1.2 兼容
+    if len(ver) >= 2 and ver[0] == "V" and ver[1].isdigit():
+        ver = "v" + ver[1:]
     bt = _build_time_token()
     if bt:
         return "{}_{}_{}".format(pn, ver, bt)