Author SHA1 Message Date
chenjintao 815918c99d fix: ensure OTA prompt plays before upgrade 2026-09-21 16:33:19 +08:00
chenjintao 90a6a0fff0 fix: correct OTA state file byte count checks 2026-09-21 16:16:53 +08:00
chenjintao 56a8cd15e5 fix: improve OTA response flow and state tracking 2026-09-21 15:53:51 +08:00
chenjintao 4e5705e847 fix: battery level update without charging status 2026-09-21 14:54:42 +08:00
chenjintao 2a8b6b419b fix: charging staus fix 2026-09-21 14:37:33 +08:00
chenjintao 823e846262 fix: battery check fix 2026-09-21 14:07:30 +08:00
chenjintao 91e9b2e66b chore: messages increase from 5 to 10 2026-09-21 11:09:18 +08:00
chenjintao 53d480c1fd Use TalkingQ domain for audio downloads 2026-09-20 16:32:44 +08:00
chenjintao 0422d754a1 feat: report ICCID during NFC binding 2026-09-20 14:07:13 +08:00
Your Name dfc738a699 vad detect optimize 2026-09-18 11:25:20 +08:00
chenjintao 3deb51d838 Adapt WSS to new boards and use MQTT domain for eSIM connectivity
Preserve main GNSS and volume behavior while porting verified TLS, receive fixes and full-handshake optimizations. Add MQTT connection diagnostics and route MQTT through banban.api.talkingq.com:1883.

Include migration documentation and regression tools. Firmware builds and package checks passed; user confirmed new-board connectivity after switching the MQTT host to the domain.
2026-09-16 17:27:21 +08:00
27 changed files with 1062 additions and 439 deletions
+3
View File
@@ -2,6 +2,9 @@
/target/
/version/
# Local legacy-board packages retained when switching to the new-board branch.
/artifacts/legacy-no-gnss/
# Extracted toolchain and local compiler license; keep compile_tools.zip in LFS.
/ql-cross-tool/win32/owtoolchain/
-9
View File
@@ -1,9 +0,0 @@
# 旧板专用分支:legacy-board
此分支用于没有 GNSS 芯片的旧板,后续旧板修改在此分支维护。
`ql-config/config/board_features.h` 中 `NFC_GNSS_BOARD_HAS_GNSS` 默认为 `0`,
跳过内核 GNSS 型号识别,关闭应用定位任务和定位模式切换。
切换到此版本时必须重新编译并更新内核和 APP。
修改说明、构建步骤和板上验收要求见 [旧板说明](artifacts/legacy-no-gnss/README.md)。
-6
View File
@@ -1,6 +0,0 @@
# Local firmware exports, build logs, verification output and build-file backups.
*.zip
*.log
*.disasm.txt
/verification.json
/build-backup/
-53
View File
@@ -1,53 +0,0 @@
# 无 GNSS 旧板调试版本
本分支名称为 `legacy-board`,用于后续无 GNSS 旧板开发。
本目录的说明和验证脚本纳入版本控制;烧录包、日志、验证结果及备份仅保留在本地。
当前开关位于 `ql-config/config/board_features.h`:
```c
#define NFC_GNSS_BOARD_HAS_GNSS 0
```
- 内核不创建 `q_gocg` 型号识别任务,避免无定位芯片时触发 `gps_app.c` 的断言复位。
- `app_gnss_init()` 不创建定位任务,包括联网成功后的调用。
- `set_gnss_mode()` 忽略定位模式请求,避免无定位任务时无法退出 FAST 模式、阻止休眠。
- 定位数据保持无效,不伪造坐标或定位成功状态。
- 未保存音量时默认使用 100% 档位;已有保存音量时仍使用保存值。
在 SDK 根目录依次运行:
```bat
build.bat kernel
build.bat app
build.bat firmware
```
必须更新内核和 APP;只烧 APP 无法移除旧内核中的断言。
烧录输入使用生成目录中的内层 ZIP,不使用包含 DBG 的外层归档。
`original-EG800AKCN_11LG.zip` 是修改前外层归档的备份。
本次已导出可直接交给烧录工具的 `EG800AKCN_11LG-legacy-no-gnss.zip`
(6,519,833 字节)。`EG800AKCN_11LG-legacy-no-gnss-debug.zip` 保存对应调试归档。
仍沿用 EG800AKCN_11LG 的分区布局和 GNSS 资源文件;本修改关闭定位启动行为,
不是更换模组型号或重做其他硬件的板级适配。
验证结果:内核、APP 和打包完成;内层 ZIP 的 CRC 检查通过;包内 `cp.bin`
及 `customer_app.bin` 与本次构建输出逐字节一致;反汇编确认型号识别入口
和应用定位初始化只输出日志,模式设置函数直接返回。
详情和 SHA256 见 `verification.json`,构建日志及反汇编保存在同目录。
本次未烧录设备,尚未完成板上功能验证。
重新构建后,将 `target/EG800AKCN_11LG/EG800AKCN_11LG.zip` 复制到本目录,
命名为 `EG800AKCN_11LG-legacy-no-gnss.zip`,在 SDK 根目录执行:
```bat
ql-cross-tool\win32\owtoolchain\Python\python.exe artifacts\legacy-no-gnss\verify_package.py
```
恢复带 GNSS 板时将开关改为 `1`,重新编译内核和 APP 并打包。
两份构建都必须重新编译对应 GNSS 源文件;检查日志,不要只依赖批处理退出码。
板上验收:启动后不再出现 `No model was found for val` / `q_gocg` 断言;
联网后继续运行至少一分钟,并实际检查网络、音频和 NFC 等所需功能。
构建成功不能代替上述板上验证。
@@ -1,45 +0,0 @@
"""Verify the exported legacy-board package against this SDK build."""
import hashlib
import json
import re
from pathlib import Path
import subprocess
import zipfile
output = Path(__file__).resolve().parent
root = output.parent.parent
package = output / "EG800AKCN_11LG-legacy-no-gnss.zip"
objdump = root / "ql-cross-tool/win32/owtoolchain/gcc-arm-none-eabi/bin/arm-none-eabi-objdump.exe"
report = {"package": package.name, "sha256": hashlib.sha256(package.read_bytes()).hexdigest()}
app_symbols = subprocess.check_output(
[str(objdump.with_name("arm-none-eabi-nm.exe")), str(root / "ql-application/threadx/build/app.elf")], text=True
)
printf_address = int(re.search(r"^([0-9a-fA-F]+) T printf$", app_symbols, re.MULTILINE).group(1), 16)
with zipfile.ZipFile(package) as archive:
assert archive.testzip() is None, "ZIP CRC failure"
for entry, source in {
"cp.bin": "ql-kernel/threadx/build/release_kernel.bin",
"customer_app.bin": "ql-application/threadx/build/app.bin",
}.items():
data = archive.read(entry)
assert data == (root / source).read_bytes(), f"Stale image: {entry}"
report[entry] = {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
report["download_manifest"] = json.loads(archive.read("download.json"))
for symbol, binary, required in [
("quec_get_gnssid_task", "ql-kernel/threadx/build/kernel.axf", "<RTI_LOG>"),
("app_gnss_init", "ql-application/threadx/build/app.elf", f"0x{printf_address | 1:08x}"),
("set_gnss_mode", "ql-application/threadx/build/app.elf", "bx\tlr"),
]:
disassembly = subprocess.check_output(
[str(objdump), "-d", f"--disassemble={symbol}", str(root / binary)], text=True
)
assert f"<{symbol}>:" in disassembly, f"Missing function: {symbol}"
assert required in disassembly, f"Unexpected function body: {symbol}"
assert "task_create" not in disassembly, f"Task still created: {symbol}"
(output / f"{symbol}.disasm.txt").write_text(disassembly, encoding="utf-8")
report["verification"] = "PASS: ZIP CRC, kernel and APP byte identity, GNSS entry disassembly"
report["hardware_test"] = "Not performed"
(output / "verification.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
print(report["verification"])
print("SHA256:", report["sha256"])
+11 -7
View File
@@ -1,9 +1,13 @@
# 旧板 WSS 联调包
# WSS 联调包
最新优化包为 `full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip`,供无 GNSS 旧板联调,默认连接 `wss://banban.api.talkingq.com:443/ws`。默认音量仍为 100(已有保存值按原逻辑读取)。这是完整多分区包,沿用上一版 NIST 加速内核,APP 调整 WSS 的 ECDHE 曲线优先级为 X25519 优先,保留其他已启用曲线作为回退。每次仍执行完整握手,按键后建连。
最新域名试验包:new-board/mqtt-domain/EG800AKCN_11LG-new-board-wss-mqtt-domain.zip。MQTT 改为 banban.api.talkingq.com:1883,保留诊断日志;用户已确认改域名后新板连接成功,详细成功日志和其他业务验收尚未提供。
2026-09-16 14:38 板端确认 NIST 加速后完整 TLS 握手为 1820 ms;15:52 补充日志进一步确认 X25519 实际协商为 group=9,客户端密钥交换从 445 ms 降至 135 ms,完整 TLS 握手降至 1455 ms,HTTP 101 正常。两份日志样本中 TLS 再缩短约 20%。包校验与 SHA-256 见 `full-handshake/verification.json`;其中待上板字段为生成时历史状态,新增运行证据见 [完整总结](D:/banban/project/nfc_gnss/docs/WSS适配与性能优化完整总结.md)。
2026-09-16 11:32 复测:443 `/ws` 已返回 HTTP 101,TLS 和证书校验通过;8080 仍是明文 WS。`tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip`(1820 ms)和 `recv-fix/EG800AKCN_11LG-legacy-wss-recv.zip` 均保留作已上板通过的回退版本。本目录原 `EG800AKCN_11LG-legacy-wss.zip` 缺少 ECDSA,`ecc-fix` 包存在接收误判,均仅用于历史对照。
原旧板包保留在 `../legacy-no-gnss/`。构建日志、探测 JSON、导出的公共证书和包体在此目录本地保存并被 Git 忽略。源码和详细步骤见 `../../docs/talkingq_wss.md`。
最新网络诊断包:new-board/mqtt-diag/EG800AKCN_11LG-new-board-wss-mqtt-diag.zip。沿用新板 WSS 内核,APP 补充 MQTT 收发、CONNACK 和语音键网络准入日志,具体根因待新板日志确认。
当前 main 对应新板,下载包为 [EG800AKCN_11LG-new-board-wss-x25519.zip](D:/banban/project/nfc_gnss/artifacts/wss/new-board/EG800AKCN_11LG-new-board-wss-x25519.zip)。这是包含本次内核和 APP 的完整多分区包,默认连接 `wss://banban.api.talkingq.com:443/ws`,保留新板 GNSS。音量沿用原 main 逻辑,读取不到保存值时回退至 70。
新板已经完成内核、APP、固件打包和主机/静态验证,尚待新板上板测试。包哈希、镜像一致性、GNSS 任务入口、TLS 算法与时间检查结果见 `new-board/verification.json`;迁移说明见 [新板 WSS 文档](D:/banban/project/nfc_gnss/docs/new_board_wss.md)。
旧板包 `full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip` 保持原样,属于无 GNSS、默认音量 100 的旧板配置。旧板 2026-09-16 日志确认 X25519 完整 TLS 握手 1455 ms;该数值不是新板实测结果。旧板 NIST 版本、接收修复版本及更早实验包保留用于历史追溯。完整旧板过程见 [旧板总结](D:/banban/project/nfc_gnss/docs/WSS适配与性能优化完整总结.md)。
构建日志、测试报告、公共证书和包体在本目录本地保存并被 Git 忽略;提交源码与文档不会把这些本地包一并上传。
@@ -1,5 +1,7 @@
# 旧板 WSS 适配、故障修复与性能优化完整总结
> 新板 main 说明:本文保留旧板历史过程、配置与实测数据。新板已迁入 WSS,保留 GNSS 与原 main 音量行为;新板构建、下载包及验收请见 [新板 WSS 迁移说明](D:/banban/project/nfc_gnss/docs/new_board_wss.md)。旧板实测耗时不代表新板实测。
> 整理日期:2026-09-16;设备日志时间按用户提供的串口时间记录。
>
> 工作区:`D:\banban\project\nfc_gnss`。分支:`legacy-board-wss`。本文覆盖的最新代码提交:`551a7bb1e657c12f45ac0a1f2ca851625723a63d`。
+125
View File
@@ -0,0 +1,125 @@
# 新板 main 分支 WSS 迁移说明
> 最新上板反馈(2026-09-16):用户确认将 MQTT 改为 banban.api.talkingq.com:1883 后已连接成功。该反馈支持域名访问方式适配新板受限 eSIM 的判断;未提供本次成功日志,本文不据此宣称 GNSS、全部业务及 WSS 性能均已验收。
更新日期:2026-09-16。适用工作区:`D:\banban\project\nfc_gnss`,分支:`main`。
## 1. 迁移范围
以原主分支 `2746cc3d4ebbd90f8fa0c1e68db93b199b297714` 的新板代码为基础,迁入旧板已经完成联调的 WSS 实现、故障修复、完整握手优化与测试工具。新板仍使用自身的 GNSS 检测和定位任务。
| 旧板来源提交 | 迁入内容 |
| --- | --- |
| `3e463f5` | WSS 地址解析、可信根 CA、服务器证书及域名验证、WebSocket Upgrade 校验 |
| `5c3e7ee` | 内核 ECDSA/GCM 能力、APP 与内核共享配置、RTC 证书日期校验、TLS 接收重试修复 |
| `3a0714c` | NIST 曲线模约减加速、握手阶段计时、连接循环日志修正 |
| `551a7bb` | 完整握手优先协商 X25519,保留原有其他曲线作为回退 |
| `54bf67c` | 旧板联调过程、算法说明与实测总结,作为历史资料保留 |
这些提交的修改迁入工作区,不直接合并旧板分支。旧板关闭 GNSS 的提交 `f283223` 和音量改为 100 的提交 `4b56595` 未迁入。
## 2. 新板硬件与业务行为
- 内核 `quec_get_gnssid_task()` 仍创建 GNSS 型号识别任务 `quec_get_ocname_by_gnss_task`。
- APP `app_gnss_init()` 仍创建定位任务 `quec_gnss_test`,`set_gnss_mode()` 仍更新定位模式。
- `audio.c` 与原主分支一致:音量优先读取设备保存值,读取失败时回退至 70。
- 设备身份仍沿用 `device_id` / `serial_number`,不改后台鉴权格式。
- 按键触发连接;每次新连接仍执行完整 TLS 握手,没有提前建连或新增会话恢复逻辑。
## 3. WSS 与密码算法
默认地址:`wss://banban.api.talkingq.com:443/ws`。
使用 TLS 1.2,配置 ISRG Root X2 公共根 CA,验证服务器证书链、有效期和主机名。单向 TLS 不需要设备客户端证书,但服务器证书校验仍必须通过。WebSocket 层继续验证 HTTP 101 与 `Sec-WebSocket-Accept`。
内核提供 ECDSA 签名验证、ECDHE 密钥交换及 AES-GCM 等能力。APP 和内核共用 `talkingq_mbedtls_config.h`,避免跨边界使用 mbedTLS 结构体时因宏配置不同导致布局不一致。证书时间检查使用真实 RTC 和 UTC 转换,不再依赖原空时间实现。
ECDHE 优先使用 X25519;证书签名算法不因此改变。NIST 优化针对证书链和回退曲线的有限域计算,X25519 优化针对临时密钥交换,二者可同时受益。服务器最终选择的曲线以设备协商日志为准。
TLS 非阻塞读写返回 `WANT_READ` / `WANT_WRITE` 时继续等待或重试,不把暂时没有数据误判为连接断开。工作线程使用 16 KiB 栈,并保留完整握手总耗时、各阶段耗时及协商信息日志。
更多算法和问题根因见 [旧板完整总结](D:/banban/project/nfc_gnss/docs/WSS适配与性能优化完整总结.md)。其中的默认音量、关闭 GNSS 和实测性能均属于旧板历史。
## 4. 构建与切分支缓存
从原新板 main 升级至此版本,需要同时更新内核和 APP,因为原内核没有本次补齐的完整 TLS 算法与配置。不能只编译 APP 并沿用原 main 内核。
在仓库根目录执行:
```bat
build.bat kernel
build.bat app
build.bat firmware
```
首次切换发现旧 GNSS `.d` 文件仍引用旧板专用 `board_features.h`,导致内核构建提示依赖不存在。这是跨分支残留编译缓存,不是新板缺失硬件配置。处理方法为备份并移走内核 `gps_app` 和 APP `gnss` 的旧 `.o`、`.d` 文件,再重新构建;没有添加旧板配置头或跳过 GNSS 编译。
本次缓存备份和构建日志位于 `artifacts/wss/new-board/`。SDK 生成的诊断对象和链接反馈文件另行备份,包生成后恢复其原始受版本控制的内容,避免把构建副产物混入源码修改。
`build.bat firmware` 生成完整多分区固件包。提供的下载包来自 `target/EG800AKCN_11LG/EG800AKCN_11LG.zip` 内层 ZIP;它不是仅含 APP 的更新包。
## 5. 产物与验证
新板包:[EG800AKCN_11LG-new-board-wss-x25519.zip](D:/banban/project/nfc_gnss/artifacts/wss/new-board/EG800AKCN_11LG-new-board-wss-x25519.zip)。
本地验证报告:[verification.json](D:/banban/project/nfc_gnss/artifacts/wss/new-board/verification.json)。该报告记录包 SHA-256、镜像大小和哈希、下载清单及检查结果。
验证覆盖:
- WSS URL、HTTP Upgrade、TLS 读写及接收回归测试。
- APP/内核 mbedTLS ABI、实际 SDK 证书链验证及 ECDH 运算测试。
- ZIP CRC,以及包内 `cp.bin`、`customer_app.bin` 与本次构建输出逐字节一致性。
- 最终 ELF 中 ECDSA、GCM、NIST 优化、RTC 时间转换和 WSS 关键符号。
- 反汇编确认 GNSS 识别及定位入口仍创建任务,且对应 worker 存在;新板镜像不包含旧板跳过 GNSS 的日志标记。
主机算法测试仅证明算法与配置可用,不能代替设备端速度测量。构建和静态校验也不代表已经完成新板上电、定位或语音业务测试。
## 6. 上板验收与影响
新板仍需验证正常启动、GNSS 识别、定位任务和语音业务。WSS 日志应出现证书/域名验证成功、HTTP 101,以及原有设备身份发送流程。关注 RTC 是否有效、网络信号、完整握手协商曲线与各阶段耗时。
旧板实测从最初约 9.6 秒 TLS 握手,经 NIST 优化降至 1820 ms,再经 X25519 优化降至 1455 ms。上述数据不能直接标注为新板测试结果;新板 GNSS 等任务可能影响调度和 CPU 负载,需要采集新日志比较。
证书校验继续要求正确时间、匹配域名和受信任证书链;未来后台更换证书链或根 CA 时需要复核。共享 TLS 配置改变了内核密码能力,其他使用相同内核接口的模块也需在新板回归时观察。
旧板分支和历史包保留,供旧板使用与回溯。新板使用本说明中的独立包目录,避免与 `artifacts/wss/full-handshake/` 的旧板包混淆。
## 7. MQTT 连接诊断版本(2026-09-16)
新板 16:43 日志确认拨号获得 IP,但 MQTTConnect 返回 -1。语音键被识别后,因现有入口要求“拨号在线且 MQTT 在线”,播放网络检查提示,尚未发起 WSS。本轮增加连接诊断,GNSS 及网络准入、鉴权、重连策略保持原逻辑。
诊断包:[EG800AKCN_11LG-new-board-wss-mqtt-diag.zip](D:/banban/project/nfc_gnss/artifacts/wss/new-board/mqtt-diag/EG800AKCN_11LG-new-board-wss-mqtt-diag.zip)。这是完整多分区包;内核与上一新板 WSS 包逐字节相同,APP 增加日志。已刷入上一新板 WSS 内核时,本轮只需 build.bat app 和 build.bat firmware,无需重编内核。报告和构建日志位于包的同一目录。
| 日志 | 含义 |
| --- | --- |
| [MQTT-DIAG] TCP begin/end | MQTT 服务地址、端口、拨号状态、连接返回值与耗时 |
| CONNECT begin | 协议版本、会话选项、超时及 SDK 客户端状态 |
| write request / rc | SDK 写回调请求及返回字节数;负值表示失败 |
| read request / rc | SDK 读回调返回值及耗时;0 或负值不等于鉴权拒绝 |
| CONNECT end | MQTT 最终返回值、总耗时、读写次数及字节数 |
| CONNACK code=0 | 服务端接受连接 |
| CONNACK code=1/2/3/4/5 | 协议版本不支持 / Client ID 被拒绝 / 服务不可用 / 用户名或密码错误 / 未授权 |
| no response bytes returned by SDK read | SDK 读回调未返回有效字节,不能单凭此项断定服务端原因 |
| no valid initial CONNACK | 已收到数据,但开头不构成完整合法 CONNACK |
| failed before transport write | 尚未调用发送回调,优先检查客户端状态或 CONNECT 序列化 |
| [NET-GATE] blocked_mqtt_offline | 按键已识别,但 MQTT 未在线,因此未尝试 WSS |
| [NET-GATE] start_voice | 通过网络准入,进入原语音流程 |
SDK 读回调把 EOF 和部分 socket 错误统一映射为 -1。errno_snapshot 是即时读取的错误快照,可能残留旧值,不能单凭它断言服务端拒绝或关闭。只有完整合法 CONNACK 中的拒绝码才作为服务端拒绝证据。诊断只观察原回调,不替换底层 socket 实现。
诊断仅在同步 MQTT CONNECT 期间挂接原有读写回调,结束即恢复,随后才启动 MQTT 后台线程。代码不打印 CONNECT 原文、账号、密码或业务报文,仅暂存接收开头四字节用于识别 CONNACK。诊断状态按现有单一 MQTT 连接工作线程的串行使用约束实现。
在 Visual Studio 开发命令行运行 tools/test_mqtt_diag.py,可测试参数和返回值透传、成功与失败后回调恢复、分片 CONNACK、服务端拒绝、无效响应、读写错误、无发送提前返回、时钟回绕,以及凭据测试标记不出现在日志中。
APP 构建、诊断测试和固件镜像校验已通过。SDK 原有隐式声明等编译警告仍存在,本轮没有修改这些接口。具体 MQTT 根因仍需新板运行诊断包后取得日志。
## 8. MQTT 域名试验版本(2026-09-16)
根据新板使用受限 eSIM 的情况,将 mqtt.c 中 DEFAULT_SERVER_DOMAIN 从 101.35.224.118 改为 banban.api.talkingq.com,端口仍为 1883,沿用明文 MQTT 和原有鉴权、注册流程。保留 MQTT-DIAG 与 NET-GATE 日志。
电脑侧已验证该域名解析到 101.35.224.118,使用固件现有账号、临时 Client ID 可收到 CONNACK=0。该结果不能代替新板 eSIM 白名单验证:若卡商还限制端口或协议,仅改域名可能仍无法通过。
本次包:[EG800AKCN_11LG-new-board-wss-mqtt-domain.zip](D:/banban/project/nfc_gnss/artifacts/wss/new-board/mqtt-domain/EG800AKCN_11LG-new-board-wss-mqtt-domain.zip)。内核与上一诊断包相同,APP 编译、固件打包和镜像校验通过;用户随后确认改域名后已连接成功。已使用本轮新板 WSS 内核时,只需 build.bat app 和 build.bat firmware。本包仍是完整多分区包。
开机应看到 [MQTT-DIAG] TCP begin host=banban.api.talkingq.com port=1883。随后观察 CONNACK、设备注册和按语音键后的 WSS 连接结果。本轮不更改提示音下载地址。
+2
View File
@@ -1,5 +1,7 @@
# 旧板 WSS 适配与联调
> 新板 main 说明:本文保留旧板历史过程、配置与实测数据。新板已迁入 WSS,保留 GNSS 与原 main 音量行为;新板构建、下载包及验收请见 [新板 WSS 迁移说明](D:/banban/project/nfc_gnss/docs/new_board_wss.md)。旧板实测耗时不代表新板实测。
本次修改位于 `legacy-board-wss`(基于 `legacy-board`),默认地址为 `wss://banban.api.talkingq.com:443/ws`。沿用无 GNSS、默认音量 100 的旧板配置。上板暴露内核缺少 ECDSA 后,已补齐内核 TLS 能力并统一 APP/内核配置;从原明文旧板版本升级时必须更新内核和 APP。已有匹配 NIST 加速内核时,最新 X25519 调整只需更新 APP。
完整的问题根因、算法、提交、性能数据和影响说明见 [WSS 适配与性能优化完整总结](D:/banban/project/nfc_gnss/docs/WSS适配与性能优化完整总结.md)。2026-09-16 15:52 最新板端已确认 X25519 协商、完整 TLS 握手 1455 ms 和 HTTP 101。
@@ -81,6 +81,7 @@ SRC_FILES+= \
nfc.c \
DP1312EA.c \
mqtt.c \
mqtt_connect_diag.c \
vad_det.c \
sleep_wake.c \
battery.c \
@@ -414,9 +414,9 @@ uint8_t app_get_volume(void)
printf("get volume:%d, ret:%d\r\n", value, ret);
//ret = read_volume_file(&value, 1);
if(ret<0){
g_cur_volume = 100;
g_cur_volume = 70;
hal_kv_set("volume", &g_cur_volume, 1);
return VOLUME_100;
return VOLUME_70;
}
g_cur_volume = value;
@@ -19,9 +19,11 @@
#include "ql_atcmd.h"
#include "app_inc.h"
#include "ql_uart.h"
#include "ql_application.h"
#include "ql_log.h"
#include "hal_api.h"
#include "ql_application.h"
#include "ql_log.h"
#include "ql_adc.h"
#include "ql_power.h"
#include "hal_api.h"
// 电量阈值定义
#define BAT_VOLTAGE_100 4200
@@ -36,25 +38,24 @@
#define BAT_VOLTAGE_10 3550
#define BAT_VOLTAGE_MIN 3500
// ========== 新增:10分钟电压历史记录用于判断充放电状态 ==========
#define VOLTAGE_HISTORY_SIZE 60 // 10分钟 = 60个10秒采样点
#define VOLTAGE_RISE_THRESHOLD 15 // 10分钟内上升超过15mV判定为充电
#define VOLTAGE_DROP_THRESHOLD 15 // 10分钟内下降超过15mV判定为放电
#define VOLTAGE_STABLE_RANGE 10 // 变化在±10mV内判定为稳定
#define BAT_LOW_WARN_MV 3550
#define BAT_LOW_PROBE_EXIT_MV 3600
#define BAT_SHUTDOWN_MV 3500
#define BAT_VALID_MIN_MV 2500
#define BAT_VALID_MAX_MV 5000
#define BAT_SHUTDOWN_CONFIRM_COUNT 3
#define BAT_NORMAL_SAMPLE_CALLS 6
unsigned short g_vbat_vol = 0;
unsigned short vbat_sample[6] = {0}; // 修改:样本数改为6
unsigned char battery_first_update_flag = 0;
static unsigned short g_vbat_vol_last = 0;
static bat_level_t g_bat_level_last = BAT_LEVEL_100;
// ========== 新增:电压历史记录环形缓冲区 ==========
static unsigned short g_voltage_history[VOLTAGE_HISTORY_SIZE] = {0};
static unsigned char g_history_index = 0;
static unsigned char g_history_count = 0; // 已记录的有效点数
static int g_charging_state = 0; // 当前判定的充电状态:1-充电, 0-未充电
static unsigned char g_charging_stable_cnt = 0; // 状态稳定计数
static bat_level_t g_bat_level_last = BAT_LEVEL_100;
static unsigned char g_bat_level_valid = 0;
static unsigned char g_low_voltage_count = 0;
static unsigned char g_low_probe_active = 0;
static unsigned char g_low_power_warned = 0;
bat_level_t calc_battery_level(uint16_t voltage_mv)
{
@@ -71,101 +72,64 @@ bat_level_t calc_battery_level(uint16_t voltage_mv)
else return BAT_LEVEL_0;
}
/**
* @brief 基于10分钟电压历史判断充放电状态
* @return 1-充电中, 0-未充电/放电
*/
static int detect_charging_by_trend(void)
{
if (g_history_count < VOLTAGE_HISTORY_SIZE) {
printf("[BAT] history not full (%d/%d), keep state:%d\r\n",
g_history_count, VOLTAGE_HISTORY_SIZE, g_charging_state);
return g_charging_state;
}
unsigned char old_index = (g_history_index + 1) % VOLTAGE_HISTORY_SIZE;
unsigned short vbat_now = g_voltage_history[(g_history_index + VOLTAGE_HISTORY_SIZE - 1) % VOLTAGE_HISTORY_SIZE];
unsigned short vbat_10min_ago = g_voltage_history[old_index];
short delta = (short)vbat_now - (short)vbat_10min_ago;
printf("[BAT] trend: now=%d, 10min_ago=%d, delta=%d\r\n",
vbat_now, vbat_10min_ago, delta);
int new_state = g_charging_state;
if (delta > VOLTAGE_RISE_THRESHOLD) {
new_state = 1;
printf("[BAT] trend: CHARGING detected (rise %dmV)\r\n", delta);
}
else if (delta < -VOLTAGE_DROP_THRESHOLD) {
new_state = 0;
printf("[BAT] trend: DISCHARGING detected (drop %dmV)\r\n", -delta);
}
else {
printf("[BAT] trend: STABLE (delta %dmV), keep state:%d\r\n", delta, g_charging_state);
}
if (new_state == g_charging_state) {
g_charging_stable_cnt = 0;
} else {
g_charging_stable_cnt++;
if (g_charging_stable_cnt < 3) {
printf("[BAT] state change pending: %d -> %d (%d/3)\r\n",
g_charging_state, new_state, g_charging_stable_cnt);
return g_charging_state;
}
g_charging_stable_cnt = 0;
printf("[BAT] state changed: %d -> %d (confirmed)\r\n", g_charging_state, new_state);
}
return new_state;
}
static void update_voltage_history(unsigned short voltage)
{
g_voltage_history[g_history_index] = voltage;
g_history_index = (g_history_index + 1) % VOLTAGE_HISTORY_SIZE;
if (g_history_count < VOLTAGE_HISTORY_SIZE) {
g_history_count++;
}
}
static int is_charging(void)
{
// 如有硬件检测引脚,可优先使用
return detect_charging_by_trend();
}
static bat_level_t limit_level_jump(bat_level_t raw_level, bat_level_t last_level, int charging)
{
bat_level_t result = last_level;
if (charging) {
if (raw_level > last_level) {
if (raw_level - last_level == 1) {
result = raw_level;
printf("[BAT] level up (charging): %d -> %d\r\n", last_level, raw_level);
} else if (raw_level - last_level > 1) {
result = (bat_level_t)(last_level + 1);
printf("[BAT] level up limited (charging): %d -> %d (raw:%d)\r\n",
last_level, result, raw_level);
}
}
} else {
if (raw_level < last_level) {
if (last_level - raw_level == 1) {
result = raw_level;
printf("[BAT] level down (discharging): %d -> %d\r\n", last_level, raw_level);
} else if (last_level - raw_level > 1) {
result = (bat_level_t)(last_level - 1);
printf("[BAT] level down limited (discharging): %d -> %d (raw:%d)\r\n",
last_level, result, raw_level);
}
}
}
return result;
}
bat_level_t battery_get_level(void)
{
return g_bat_level_valid ? g_bat_level_last : calc_battery_level(g_vbat_vol);
}
static bat_level_t limit_level_jump(bat_level_t raw_level)
{
bat_level_t result = g_bat_level_last;
if (!g_bat_level_valid) {
g_bat_level_valid = 1;
g_bat_level_last = raw_level;
printf("[BAT] first level initialized: %d\r\n", raw_level);
return raw_level;
}
if (raw_level > g_bat_level_last) {
result = (raw_level >= g_bat_level_last + BAT_LEVEL_10)
? (bat_level_t)(g_bat_level_last + BAT_LEVEL_10)
: raw_level;
} else if (raw_level < g_bat_level_last) {
result = (raw_level <= g_bat_level_last - BAT_LEVEL_10)
? (bat_level_t)(g_bat_level_last - BAT_LEVEL_10)
: raw_level;
}
printf("[BAT] level update: last=%d raw=%d result=%d\r\n",
g_bat_level_last, raw_level, result);
g_bat_level_last = result;
return result;
}
static void battery_check_critical_voltage(unsigned short voltage_mv)
{
if (voltage_mv < BAT_LOW_WARN_MV) {
g_low_probe_active = 1;
} else if (voltage_mv >= BAT_LOW_PROBE_EXIT_MV) {
g_low_probe_active = 0;
}
if (voltage_mv < BAT_SHUTDOWN_MV) {
if (g_low_voltage_count < BAT_SHUTDOWN_CONFIRM_COUNT) {
g_low_voltage_count++;
}
printf("[BAT] critical voltage:%d, confirm:%d/%d\r\n",
voltage_mv, g_low_voltage_count, BAT_SHUTDOWN_CONFIRM_COUNT);
if (g_low_voltage_count >= BAT_SHUTDOWN_CONFIRM_COUNT) {
printf("[BAT] critical voltage confirmed, poweroff now\r\n");
app_play_tips_file(15);
ql_rtos_task_sleep_s(5);
ql_power_down(1);
}
} else {
g_low_voltage_count = 0;
}
}
/* 修改后的 battery_check:分阶段采样,采样窗口为6次 */
void battery_check(void)
@@ -174,15 +138,17 @@ void battery_check(void)
static unsigned char sample_idx = 0; // 当前已采集样本数 (0~5)
static unsigned short sample_buf[6] = {0}; // 本地缓冲(也可用全局 vbat_sample,此处直接用局部)
call_count++;
call_count++;
/* 判断本次是否执行ADC采样 */
unsigned char do_sample = 0;
if (call_count <= 24) { // 开机2分钟内 (24次*5s=120s)
do_sample = 1;
} else {
// 2分钟后,每36次调用采样一次 (36*5s=180s=3分钟)
if ((call_count - 25) % 36 == 0) {
if (call_count <= 24) { // 开机2分钟内 (24次*5s=120s)
do_sample = 1;
} else if (g_low_probe_active) {
do_sample = 1; // 低电风险时每5秒快速确认
} else {
// 2分钟后,每6次调用采样一次 (6*5s=30秒)
if (call_count % BAT_NORMAL_SAMPLE_CALLS == 0) {
do_sample = 1;
}
}
@@ -195,9 +161,21 @@ void battery_check(void)
u16 vbat_vol = 0;
ql_adc_onff(1);
ql_rtos_task_sleep_ms(5);
ql_adc_onff(0);
vbat_vol = ql_get_battery_vol();
sample_buf[sample_idx] = vbat_vol;
ql_adc_onff(0);
vbat_vol = ql_get_battery_vol();
printf("[BAT][SAMPLE] voltage=%d, low_probe=%d\r\n",
vbat_vol, g_low_probe_active);
if (vbat_vol < BAT_VALID_MIN_MV || vbat_vol > BAT_VALID_MAX_MV) {
printf("[BAT] invalid voltage sample ignored:%d\r\n", vbat_vol);
g_low_voltage_count = 0;
return;
}
battery_check_critical_voltage(vbat_vol);
sample_buf[sample_idx] = vbat_vol;
sample_idx++;
/* 当收集满6个样本时进行平均处理 */
@@ -215,47 +193,29 @@ void battery_check(void)
vol_all = vol_all - min - max;
vbat_vol = vol_all / 4; // 6个样本去掉最大最小,剩余4个平均
/* 更新电压历史(用于充电趋势判断) */
update_voltage_history(vbat_vol);
g_vbat_vol = vbat_vol;
printf("vbat_vol:%d\r\n", g_vbat_vol);
/* 判断充电状态 */
int charging = is_charging();
g_charging_state = charging;
/* 非充电状态下,电压不允许上升 */
if (!charging) {
if (g_vbat_vol_last > 0 && vbat_vol > g_vbat_vol_last) {
printf("[BAT] voltage rise blocked: %d -> %d (not charging)\r\n",
g_vbat_vol_last, vbat_vol);
vbat_vol = g_vbat_vol_last;
}
}
g_vbat_vol = vbat_vol;
g_vbat_vol_last = vbat_vol;
printf("vbat_vol:%d, charging:%d\r\n", g_vbat_vol, charging);
/* 计算电量等级并限制跳变 */
bat_level_t raw_level = calc_battery_level(g_vbat_vol);
bat_level_t level = limit_level_jump(raw_level, g_bat_level_last, charging);
g_bat_level_last = level;
printf("[BAT] raw_level:%d, limited_level:%d, charging:%d\r\n",
raw_level, level, charging);
/* 计算电量等级并限制跳变 */
bat_level_t raw_level = calc_battery_level(g_vbat_vol);
bat_level_t level = limit_level_jump(raw_level);
printf("[BAT] raw_level:%d, limited_level:%d\r\n",
raw_level, level);
if (battery_first_update_flag == 0) {
battery_first_update_flag = 1;
}
/* 低电量处理 */
if (level == BAT_LEVEL_0) {
printf("poweroff now\r\n");
app_play_tips_file(15);
ql_rtos_task_sleep_s(5);
ql_power_down(1);
} else if (level <= BAT_LEVEL_10) {
app_play_tips_file(14);
}
/* 极低电关机由原始电压独立处理,普通低电提示只播放一次 */
if (level <= BAT_LEVEL_10) {
if (!g_low_power_warned) {
g_low_power_warned = 1;
app_play_tips_file(14);
}
} else if (level >= BAT_LEVEL_20) {
g_low_power_warned = 0;
}
sample_idx = 0; // 重置样本索引,准备下一轮采集
}
@@ -15,7 +15,6 @@
#include "sockets.h"
#include "netdb.h"
#include "app_inc.h"
#include "../../../../ql-config/config/board_features.h"
// 新增宏定义(原代码中未定义)
#define GNSS_FAST_MODE_HOLD_TIME (5 * 60 * 1000) // FAST模式持续5分钟后切SLOW
@@ -321,14 +320,9 @@ void quec_gnss_callback(char *data, int len)
*===========================================================================*/
void set_gnss_mode(gnss_mode_t mode)
{
#if !NFC_GNSS_BOARD_HAS_GNSS
(void)mode;
return; /* No GNSS task exists to leave FAST mode and allow sleep. */
#else
g_gnss_mode = mode;
printf("[GNSSMode] Set to %s Mode\n",
(mode == GNSS_MODE_FAST) ? "Fast (30s)" : "Slow (5min)");
#endif
}
gnss_mode_t get_gnss_mode(void)
@@ -537,11 +531,6 @@ static void quec_gnss_test(void * argv)
void app_gnss_init(void)
{
#if !NFC_GNSS_BOARD_HAS_GNSS
printf("[GNSS] Legacy board: positioning disabled\n");
return;
#else
ql_task_t gnssTaskRef = NULL;
ql_rtos_task_create(&gnssTaskRef, 1024*4, 100, "gnss_Task_Ref", quec_gnss_test, NULL);
#endif
}
@@ -126,7 +126,12 @@ static void key_thread_entry(void *parameter)
app_set_app_operation(1);
}
if(app_get_data_call_net_status()&&get_mqtt_connect_status()==1){
int mic_pdp = app_get_data_call_net_status();
int mic_mqtt = get_mqtt_connect_status();
printf("[NET-GATE] mic pdp=%d mqtt=%d action=%s\r\n",
mic_pdp, mic_mqtt,
!mic_pdp ? "blocked_no_pdp" : (mic_mqtt != 1 ? "blocked_mqtt_offline" : "start_voice"));
if(mic_pdp && mic_mqtt == 1){
if(tts_play_interrupt_func()==-2){
app_play_tips_file(2);
}
@@ -36,7 +36,7 @@ const char *mp3_file[] ={
"ai_active_zh.mp3",//18
};
//http://175.24.73.253/assets/roles/zhuli/zh/tts_error.mp3
//http://banban.api.talkingq.com:8080/assets/roles/banban/tts_error.mp3
//#define PCM_FILE "http://47.113.99.138:7050/output_16k.pcm"
@@ -194,7 +194,7 @@ void app_file_check_download(void)
if(ql_access(filename_buff, 0)!=0){
printf("downing mp3 file %d,%s\r\n", index,filename_buff);
down_loop:
sprintf(url_buff, "http://101.35.224.118:8080/assets/roles/banban/%s", mp3_file[index]);
sprintf(url_buff, "http://banban.api.talkingq.com:8080/assets/roles/banban/%s", mp3_file[index]);
ret = http_file_download(url_buff);
if(ret!=0&&cnt++<3) goto down_loop;
@@ -0,0 +1,9 @@
#ifndef TALKINGQ_MQTT_CONNECT_DIAG_H
#define TALKINGQ_MQTT_CONNECT_DIAG_H
#include "MQTTClient.h"
/* Called by the single MQTT connection worker, before MQTTStartTask(). */
int talkingq_mqtt_connect_diagnosed(MQTTClient *client, MQTTPacket_connectData *options);
#endif
@@ -280,10 +280,11 @@ u32 local_mp3_data_fetch(const char *uri, uint8_t *buffer, u32 length)
size_t url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
{
int resp_status = -1;
uint32_t nw = length;
uint32_t page_pos = 0;
int error_cnt = 0;
int resp_status = -1;
uint32_t nw = length;
uint32_t page_pos = 0;
uint32_t content_length = 0;
int error_cnt = 0;
//printf("fd_fetch start\n");
session = webclient_session_create(1024);
@@ -306,11 +307,12 @@ size_t url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
goto __exit;
}
if (session->content_length == 0)
{
printf("Request file size is 0!");
goto __exit;
}
if (session->content_length == 0)
{
printf("Request file size is 0!");
goto __exit;
}
content_length = session->content_length;
printf("start read\n");
if(total_length < session->content_length)
@@ -325,7 +327,7 @@ size_t url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
page_pos = 0;
do
{
if(vad_audio_is_recording()) {
if(vad_audio_is_recording() || tts_playing_ret()==2) {
break;
}
@@ -361,7 +363,7 @@ __exit:
session = NULL;
}
if(total_length<session->content_length){
if(total_length<content_length){
total_length = 0;
return -1;
}
+474 -102
View File
@@ -4,6 +4,7 @@
=================================================================*/
#include "MQTTClient.h"
#include "mqtt_connect_diag.h"
#include "ql_data_call.h"
#include "ql_application.h"
#include "sockets.h"
@@ -25,7 +26,7 @@
#define PROFILE_IDX 1
//#define DEFAULT_SERVER_DOMAIN "175.24.73.253"
#define DEFAULT_SERVER_DOMAIN "101.35.224.118"
#define DEFAULT_SERVER_DOMAIN "banban.api.talkingq.com"
#define DEFAULT_SERVER_CLIENT_USER "talkingq"
#define DEFAULT_SERVER_CLIENT_PASS "talkingq@2026"
#define DEFAULT_SERVER_CLIENT_ID "9376265c059e474e92c2e46208ae1dc5"
@@ -37,6 +38,14 @@
#define DEFAULT_MQTT_EVENT_TIME 5000
#define OTA_STATE_FILE "U:/ota_state.bin"
#define OTA_STATE_TMP_FILE "U:/ota_state.tmp"
#define OTA_STATE_MAGIC 0x4F544131U
#define OTA_STATE_FORMAT_VERSION 1U
#define OTA_PROMPT_PLAY_TIMEOUT_MS 10000
#define OTA_PROMPT_POLL_MS 50
#define OTA_ALLOWED_URL_PREFIX "http://101.35.224.118:8080/assets/firmware/"
static ql_timer_t mqtt_event_task_timer = NULL;
@@ -71,7 +80,8 @@ int g_nfc_bind_status = 0;
extern u8 g_cur_volume;
extern u8 g_pre_volume;
extern unsigned short g_vbat_vol;
extern unsigned short g_vbat_vol;
extern bat_level_t battery_get_level(void);
extern sleep_config_t g_sleep_cfg;
extern uint8_t g_sleep_config_valid;
@@ -92,8 +102,23 @@ int app_mqtt_connect_sem(char flag);
#define MQTT_PAYLOAD_SIZE 2048
static char payload[MQTT_PAYLOAD_SIZE] = {0};
extern int fota_start;
extern int fota_start;
int app_play_ota_tips_file(void);
int xiaoice_get_play_counter(void);
typedef struct {
uint32_t magic;
uint16_t format_version;
uint16_t reserved;
char previous_version[16];
char target_version[16];
uint32_t checksum;
} ota_state_t;
int app_mqtt_publish(char *buf, int len, int type);
void build_register(void);
static void ota_report_pending_success(void);
enum{
MQTT_WORK_CONNECT,
MQTT_WORK_RECV,
@@ -106,6 +131,177 @@ int get_mqtt_connect_status(void)
status = mqtt_connect;
MQTT_CONN_UNLOCK();
return status;
}
static uint32_t ota_state_checksum(const ota_state_t *state)
{
const uint8_t *data = (const uint8_t *)state;
uint32_t hash = 2166136261U;
size_t i;
for (i = 0; i < sizeof(*state) - sizeof(state->checksum); i++) {
hash ^= data[i];
hash *= 16777619U;
}
return hash;
}
static int ota_state_save(const char *previous_version, const char *target_version)
{
ota_state_t state;
QFILE *fp;
int ret;
if (previous_version == NULL || target_version == NULL) {
return -1;
}
memset(&state, 0, sizeof(state));
state.magic = OTA_STATE_MAGIC;
state.format_version = OTA_STATE_FORMAT_VERSION;
strncpy(state.previous_version, previous_version, sizeof(state.previous_version) - 1);
strncpy(state.target_version, target_version, sizeof(state.target_version) - 1);
state.checksum = ota_state_checksum(&state);
fp = ql_fopen(OTA_STATE_TMP_FILE, "wb+");
if (fp == NULL) {
RLOGE("OTA state open failed: %s", OTA_STATE_TMP_FILE);
return -1;
}
ret = ql_fwrite(&state, sizeof(state), 1, fp);
if (ret != (int)sizeof(state)) {
RLOGE("OTA state write failed: %d", ret);
ql_fclose(fp);
ql_remove(OTA_STATE_TMP_FILE);
return -1;
}
ret = ql_fsync(fp);
ql_fclose(fp);
if (ret != 0) {
RLOGE("OTA state sync failed: %d", ret);
ql_remove(OTA_STATE_TMP_FILE);
return -1;
}
ret = ql_rename(OTA_STATE_TMP_FILE, OTA_STATE_FILE);
if (ret != 0) {
RLOGE("OTA state rename failed: %d", ret);
ql_remove(OTA_STATE_TMP_FILE);
return -1;
}
return 0;
}
static int ota_state_load(ota_state_t *state)
{
QFILE *fp;
int ret;
uint32_t checksum;
if (state == NULL || ql_access(OTA_STATE_FILE, 0) != 0) {
return -1;
}
fp = ql_fopen(OTA_STATE_FILE, "rb");
if (fp == NULL) {
return -1;
}
memset(state, 0, sizeof(*state));
ret = ql_fread(state, sizeof(*state), 1, fp);
ql_fclose(fp);
if (ret != (int)sizeof(*state)) {
RLOGE("OTA state read failed: %d", ret);
return -1;
}
checksum = state->checksum;
state->checksum = 0;
if (state->magic != OTA_STATE_MAGIC ||
state->format_version != OTA_STATE_FORMAT_VERSION ||
checksum != ota_state_checksum(state)) {
RLOGE("OTA state validation failed");
return -1;
}
state->checksum = checksum;
state->previous_version[sizeof(state->previous_version) - 1] = '\0';
state->target_version[sizeof(state->target_version) - 1] = '\0';
return 0;
}
static void ota_state_clear(void)
{
ql_remove(OTA_STATE_TMP_FILE);
if (ql_access(OTA_STATE_FILE, 0) == 0) {
ql_remove(OTA_STATE_FILE);
}
}
static int ota_parse_version(const char *version, unsigned int parts[3])
{
char tail;
if (version == NULL || version[0] == '\0') {
return -1;
}
if (sscanf(version, "%u.%u.%u%c",
&parts[0], &parts[1], &parts[2], &tail) != 3) {
return -1;
}
return 0;
}
static int ota_compare_version(const char *left, const char *right, int *result)
{
unsigned int left_parts[3];
unsigned int right_parts[3];
int i;
if (result == NULL ||
ota_parse_version(left, left_parts) != 0 ||
ota_parse_version(right, right_parts) != 0) {
return -1;
}
*result = 0;
for (i = 0; i < 3; i++) {
if (left_parts[i] > right_parts[i]) {
*result = 1;
break;
}
if (left_parts[i] < right_parts[i]) {
*result = -1;
break;
}
}
return 0;
}
static int ota_url_is_allowed(const char *url)
{
size_t prefix_len = strlen(OTA_ALLOWED_URL_PREFIX);
if (url == NULL || url[0] == '\0') {
return 0;
}
if (strncmp(url, OTA_ALLOWED_URL_PREFIX, prefix_len) != 0) {
return 0;
}
if (url[prefix_len] == '\0' || strstr(url + prefix_len, "..") != NULL) {
return 0;
}
return strstr(url + prefix_len, ".bin") != NULL;
}
/* ==================== 解析函数 ==================== */
@@ -161,6 +357,11 @@ int parse_command(const char *json_str, command_t *cmd)
// 解析 params
cJSON *params = cJSON_GetObjectItem(root, "params");
if (cmd->type == MSG_OTA_UPGRADE && params == NULL) {
RLOGE("OTA command has no params");
cJSON_Delete(root);
return -1;
}
if (params != NULL) {
switch (cmd->type) {
case MSG_GPS_QUERY:
@@ -179,35 +380,27 @@ int parse_command(const char *json_str, command_t *cmd)
}
case MSG_OTA_UPGRADE: {
/*判断当前的固件版本与下发的是否一致,不一致,则触发升级*/
printf("MSG_OTA_UPGRADE\r\n");
cJSON *url = cJSON_GetObjectItem(params, "url");
cJSON *version = cJSON_GetObjectItem(params, "version");
if (cJSON_IsString(url)) {
strncpy(cmd->params.ota.url, url->valuestring,
sizeof(cmd->params.ota.url) - 1);
}
if (cJSON_IsString(version)) {
strncpy(cmd->params.ota.version, version->valuestring,
sizeof(cmd->params.ota.version) - 1);
if (!cJSON_IsString(url) || url->valuestring == NULL ||
!cJSON_IsString(version) || version->valuestring == NULL ||
url->valuestring[0] == '\0' || version->valuestring[0] == '\0' ||
strlen(url->valuestring) >= sizeof(cmd->params.ota.url) ||
strlen(version->valuestring) >= sizeof(cmd->params.ota.version)) {
RLOGE("invalid OTA command parameters");
cJSON_Delete(root);
return -1;
}
strncpy(cmd->params.ota.url, url->valuestring,
sizeof(cmd->params.ota.url) - 1);
strncpy(cmd->params.ota.version, version->valuestring,
sizeof(cmd->params.ota.version) - 1);
printf("ota.url:%s\r\n", cmd->params.ota.url);
printf("ota.version:%s\r\n", cmd->params.ota.version);
app_play_tips_file(3);
fota_start = 1;
ql_rtos_task_sleep_ms(6000);
app_file_check_del();
ql_fullfota_app_start(cmd->params.ota.url);
/*
{'msg_id': '003', 'params': {'url': 'http://175.24.73.253/assets/firmware/20260425203347-fota.bin', 'version': '2.0.1'}}
*/
break;
}
@@ -367,7 +560,7 @@ int parse_event_response(const char *json_str, response_t *resp)
}
else {
// ===== 原有逻辑: NFC事件 - 动态查找 url_x 字段 =====
for (int i = 1; i <= 5; i++) {
for (int i = 1; i <= 10; i++) {
char key[16];
snprintf(key, sizeof(key), "url_%d", i);
cJSON *url = cJSON_GetObjectItem(params, key);
@@ -390,11 +583,12 @@ int parse_event_response(const char *json_str, response_t *resp)
/* [FIX] 安全处理 topic 名称,避免越界访问 */
static void messageArrived(MessageData* data)
{
int ret;
uint32 queue_count = 0;
app_work_item *work_item;
static void messageArrived(MessageData* data)
{
int ret;
int payload_log_len;
uint32 queue_count = 0;
app_work_item *work_item;
// 安全获取 topic 字符串(复制到本地缓冲区)
char topic_buf[128] = {0};
@@ -402,10 +596,16 @@ static void messageArrived(MessageData* data)
if (topic_len >= sizeof(topic_buf)) {
topic_len = sizeof(topic_buf) - 1;
}
memcpy(topic_buf, data->topicName->lenstring.data, topic_len);
topic_buf[topic_len] = '\0';
// 比较 topic 类型
memcpy(topic_buf, data->topicName->lenstring.data, topic_len);
topic_buf[topic_len] = '\0';
payload_log_len = data->message->payloadlen > 512 ? 512 : (int)data->message->payloadlen;
printf("[MQTT-RX] topic=%s len=%d payload=%.*s%s\r\n",
topic_buf, (int)data->message->payloadlen, payload_log_len,
(const char *)data->message->payload,
data->message->payloadlen > (unsigned int)payload_log_len ? "..." : "");
// 比较 topic 类型
int is_command = (strstr(topic_buf, "command") != NULL);
int is_event_resp = (strstr(topic_buf, "event_resp") != NULL) || (strstr(topic_buf, imei_buf) != NULL);
@@ -448,6 +648,8 @@ int app_mqtt_connect()
MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
int mqtt_server_port=DEFAULT_SERVER_PORT;
unsigned int tcp_started;
int tcp_errno;
MQTT_CONN_LOCK();
if(mqtt_connect==1){
@@ -462,7 +664,14 @@ int app_mqtt_connect()
NetworkInit(&network, NULL, PROFILE_IDX);
MQTTClientInit(&client, &network, 30000, sendbuf, sizeof(sendbuf), readbuf, sizeof(readbuf));
char* address = DEFAULT_SERVER_DOMAIN;
if ((rc = NetworkConnect(&network, address, mqtt_server_port)) != 0)
printf("[MQTT-DIAG] TCP begin host=%s port=%d pdp=%d\r\n",
address, mqtt_server_port, app_get_data_call_net_status());
tcp_started = ql_rtos_get_systicks();
rc = NetworkConnect(&network, address, mqtt_server_port);
tcp_errno = rc != 0 ? ql_get_soc_errno() : 0;
printf("[MQTT-DIAG] TCP end rc=%d socket=%d elapsed_ms=%u errno_snapshot=%d\r\n",
rc, network.my_socket, (unsigned int)(ql_rtos_get_systicks() - tcp_started) * 5U, tcp_errno);
if (rc != 0)
{
RLOGE("Return code from network connect is %d", rc);
MQTTClientDeinit(&client);
@@ -486,7 +695,8 @@ int app_mqtt_connect()
connectData.cleansession = 0;
connectData.keepAliveInterval = 60;
if ((rc = MQTTConnect(&client, &connectData)) != 0)
printf("[MQTT-DIAG] saved_device_id=%d (registration needed when 0)\r\n", ret == 0);
if ((rc = talkingq_mqtt_connect_diagnosed(&client, &connectData)) != 0)
{
RLOGE("Return code from MQTT connect is %d", rc);
NetworkDisconnect(&network);
@@ -537,6 +747,8 @@ int app_mqtt_connect()
if(ret!=0){
build_register();
} else {
ota_report_pending_success();
}
RLOGD("========== app_mqtt_connect okay ==========");
@@ -607,15 +819,33 @@ sub_exit:
//0:代表respone command
//1:代表send event
/* [FIX] 将连接状态检查和发送放在同一锁保护区内,避免竞争 */
int app_mqtt_publish(char *buf, int len, int type)
{
static uint32_t mqtt_pub_cnt_err=0;
int rc = -1;
MQTTMessage message;
int cnt = 0;
static uint8_t publish_error = 0;
// 锁保护:检查连接状态并发送
int app_mqtt_publish(char *buf, int len, int type)
{
static uint32_t mqtt_pub_cnt_err=0;
int rc = -1;
int payload_log_len;
MQTTMessage message;
int cnt = 0;
static uint8_t publish_error = 0;
const char *publish_topic = NULL;
if (type == 0) {
publish_topic = respone_topicName;
} else if (type == 1) {
publish_topic = event_topicName;
} else if (type == 2) {
publish_topic = event_register;
} else {
RLOGE("invalid MQTT publish type=%d", type);
return -1;
}
payload_log_len = len > 512 ? 512 : len;
printf("[MQTT-TX] topic=%s len=%d payload=%.*s%s\r\n",
publish_topic, len, payload_log_len, buf,
len > payload_log_len ? "..." : "");
// 锁保护:检查连接状态并发送
int lock_ret = ql_rtos_mutex_lock(g_mqtt_public_mutex, 5000);
if (lock_ret != 0) {
RLOGE("MQTT mutex lock timeout");
@@ -632,18 +862,13 @@ int app_mqtt_publish(char *buf, int len, int type)
message.payload = buf;
message.payloadlen = len;
while (cnt++ < 3) {
u32 start = ql_rtos_get_systicks();
if (type == 0) {
rc = MQTTPublish(&client, respone_topicName, &message);
} else if (type == 1) {
rc = MQTTPublish(&client, event_topicName, &message);
} else if (type == 2) {
rc = MQTTPublish(&client, event_register, &message);
}
RLOGD("Pulish time:%d len: %d\n", (ql_rtos_get_systicks()-start)*5, len);
if (rc != 0) {
while (cnt++ < 3) {
u32 start = ql_rtos_get_systicks();
rc = MQTTPublish(&client, publish_topic, &message);
RLOGD("Pulish time:%d len: %d\n", (ql_rtos_get_systicks()-start)*5, len);
printf("[MQTT-TX] topic=%s attempt=%d rc=%d\r\n", publish_topic, cnt, rc);
if (rc != 0) {
mqtt_pub_cnt_err++;
mqtt_public_error = 1;
RLOGE("ERROR Return code from MQTT publish is %d, cnt %d", rc, mqtt_pub_cnt_err);
@@ -958,12 +1183,18 @@ char *build_wake_response(const char *msg_id, const char *switch_state)
/**
* @brief 构建OTA接受响应
*/
char *build_ota_accepted(const char *msg_id, const char *cur_ver, const char *tgt_ver)
{
cJSON *root = cJSON_CreateObject();
cJSON *data = cJSON_CreateObject();
cJSON_AddStringToObject(root, "msg_id", msg_id);
char *build_ota_accepted(const char *msg_id, const char *cur_ver, const char *tgt_ver)
{
cJSON *root = cJSON_CreateObject();
cJSON *data = cJSON_CreateObject();
if (root == NULL || data == NULL) {
cJSON_Delete(root);
cJSON_Delete(data);
return NULL;
}
cJSON_AddStringToObject(root, "msg_id", msg_id);
cJSON_AddStringToObject(root, "status", "accepted");
cJSON_AddStringToObject(data, "current_version", cur_ver);
cJSON_AddStringToObject(data, "target_version", tgt_ver);
@@ -975,6 +1206,66 @@ char *build_ota_accepted(const char *msg_id, const char *cur_ver, const char *tg
}
/**
* @brief 构建OTA升级成功响应
*/
static char *build_ota_success(const char *new_ver)
{
cJSON *root = cJSON_CreateObject();
cJSON *data = cJSON_CreateObject();
if (root == NULL || data == NULL) {
cJSON_Delete(root);
cJSON_Delete(data);
return NULL;
}
cJSON_AddStringToObject(root, "msg_id", "003");
cJSON_AddStringToObject(root, "status", "success");
cJSON_AddStringToObject(data, "new_version", new_ver);
cJSON_AddItemToObject(root, "data", data);
char *json_str = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return json_str;
}
static void ota_report_pending_success(void)
{
ota_state_t state;
char current_version[64] = {0};
char *resp;
int compare_result;
int ret;
if (ota_state_load(&state) != 0) {
return;
}
hal_os_get_build_ver(current_version);
if (ota_compare_version(current_version, state.target_version, &compare_result) != 0 ||
compare_result != 0) {
RLOGE("OTA pending state version mismatch: current=%s target=%s",
current_version, state.target_version);
return;
}
resp = build_ota_success(current_version);
if (resp == NULL) {
RLOGE("build OTA success response failed");
return;
}
ret = app_mqtt_publish(resp, strlen(resp), 0);
free(resp);
if (ret == 0) {
printf("OTA success response published, version=%s\r\n", current_version);
ota_state_clear();
} else {
RLOGE("OTA success response publish failed: %d", ret);
}
}
/**
* @brief 构建音量调节响应
*/
char *build_volume_response(const char *msg_id, int current_level, const char *time)
@@ -999,6 +1290,95 @@ char *build_volume_response(const char *msg_id, int current_level, const char *t
}
/* ==================== 业务处理函数 ==================== */
static void handle_ota_upgrade(command_t *cmd)
{
char current_version[64] = {0};
char *resp;
int compare_result;
int ret;
uint32_t prompt_start_time;
if (cmd == NULL || fota_start == 1) {
RLOGE("OTA request ignored: invalid command or OTA already running");
return;
}
if (!ota_url_is_allowed(cmd->params.ota.url)) {
RLOGE("OTA URL is not allowed: %s", cmd->params.ota.url);
return;
}
hal_os_get_build_ver(current_version);
if (ota_compare_version(current_version, cmd->params.ota.version, &compare_result) != 0) {
RLOGE("invalid OTA version: current=%s target=%s",
current_version, cmd->params.ota.version);
return;
}
if (compare_result > 0) {
RLOGE("OTA downgrade rejected: current=%s target=%s",
current_version, cmd->params.ota.version);
return;
}
if (ota_state_save(current_version, cmd->params.ota.version) != 0) {
RLOGE("OTA state save failed");
return;
}
resp = build_ota_accepted(cmd->msg_id,
current_version,
cmd->params.ota.version);
if (resp == NULL) {
RLOGE("build OTA accepted response failed");
ota_state_clear();
return;
}
ret = app_mqtt_publish(resp, strlen(resp), 0);
free(resp);
if (ret != 0) {
RLOGE("OTA accepted response publish failed: %d", ret);
ota_state_clear();
return;
}
if (compare_result == 0) {
printf("OTA target version is already installed: %s\r\n", current_version);
ota_report_pending_success();
return;
}
fota_start = 1;
ret = app_play_ota_tips_file();
if (ret != 0) {
RLOGE("OTA prompt could not start: %d", ret);
} else {
prompt_start_time = hal_os_up_time_ms();
while (xiaoice_get_play_counter() > 0) {
ql_rtos_task_sleep_ms(OTA_PROMPT_POLL_MS);
if (hal_os_up_time_ms() - prompt_start_time > OTA_PROMPT_PLAY_TIMEOUT_MS) {
RLOGE("OTA prompt playback timeout, counter=%d",
xiaoice_get_play_counter());
break;
}
}
if (xiaoice_get_play_counter() == 0) {
printf("OTA prompt playback completed\r\n");
}
}
/* Product requirement: keep deleting the configured audio files before OTA. */
app_file_check_del();
ret = ql_fullfota_app_start(cmd->params.ota.url);
if (ret != 0) {
RLOGE("start OTA failed: %d", ret);
fota_start = 0;
ota_state_clear();
}
}
/**
* @brief 命令分发处理
@@ -1074,26 +1454,7 @@ void handle_command(command_t *cmd)
}
case MSG_OTA_UPGRADE: {
// 立即响应accepted
char *resp = build_ota_accepted(cmd->msg_id,
hal_os_get_current_version(),
cmd->params.ota.version);
/*while(get_mqtt_public_mutex()!=0){
hal_os_task_sleep_ms(100);
}*/
/*
添加删除所有本地音频动作
*/
app_mqtt_publish(resp, strlen(resp), 0);
free(resp);
// 启动OTA任务
fota_start = 1;
ql_rtos_task_sleep_ms(5000);
ql_fullfota_app_start(cmd->params.ota.url);
handle_ota_upgrade(cmd);
break;
}
@@ -1239,23 +1600,34 @@ void handle_nfc_trigger(const char *uuid)
free(json_str);
}
void handle_nfc_bind(const char *uuid)
{
cJSON *root = cJSON_CreateObject();
cJSON *data = cJSON_CreateObject();
cJSON_AddStringToObject(root, "msg_id", "006");
cJSON_AddStringToObject(root, "status", "success");
cJSON_AddStringToObject(data, "uuid", uuid);
cJSON_AddItemToObject(root, "data", data);
char *json_str = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
app_mqtt_publish(json_str, strlen(json_str), 0);
free(json_str);
}
void handle_nfc_bind(const char *uuid)
{
char iccid[QL_SIM_ICCID_LEN * 2 + 1] = {0};
QL_SIM_ERROR_CODE sim_ret;
cJSON *root = cJSON_CreateObject();
cJSON *data = cJSON_CreateObject();
cJSON_AddStringToObject(root, "msg_id", "006");
cJSON_AddStringToObject(root, "status", "success");
cJSON_AddStringToObject(data, "uuid", uuid);
sim_ret = ql_sim_get_iccid(iccid, sizeof(iccid));
if (sim_ret == QL_SIM_SUCCESS && iccid[0] != '\0') {
printf("NFC bind ICCID:%s\r\n", iccid);
cJSON_AddStringToObject(data, "iccid", iccid);
} else {
printf("NFC bind get ICCID failed, ret:%d\r\n", sim_ret);
}
cJSON_AddItemToObject(root, "data", data);
char *json_str = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
app_mqtt_publish(json_str, strlen(json_str), 0);
free(json_str);
}
@@ -1435,7 +1807,7 @@ static void app_mqtt_task_event_thread(void *arvg)
app_heartbeat_time_record = hal_os_up_time_ms();
// 采集设备信息
info.power = calc_battery_level(g_vbat_vol); // 10,20,30...100
info.power = battery_get_level(); // 使用与设备内部一致的最终电量档位
info.voice = g_cur_volume; // 40,60,80,100
@@ -0,0 +1,107 @@
#include <stdio.h>
#include <string.h>
#include "mqtt_connect_diag.h"
#include "sockets.h"
/* Only installed during synchronous CONNECT. Never dump the CONNECT payload:
* it contains credentials. Keep only the first four incoming CONNACK bytes. */
static struct {
int (*read)(Network *, unsigned char *, int, int);
int (*write)(Network *, unsigned char *, int, int);
unsigned int read_calls, write_calls, received, sent;
unsigned char prefix[4];
unsigned int prefix_size;
} mqtt_diag;
static unsigned int mqtt_diag_elapsed(unsigned int start)
{
return (unsigned int)(ql_rtos_get_systicks() - start) * 5U;
}
static int mqtt_diag_read(Network *network, unsigned char *buffer, int size, int timeout)
{
unsigned int start = ql_rtos_get_systicks();
int result = mqtt_diag.read(network, buffer, size, timeout);
int socket_error = result <= 0 ? ql_get_soc_errno() : 0;
int i;
++mqtt_diag.read_calls;
if (result > 0) {
mqtt_diag.received += (unsigned int)result;
for (i = 0; i < result && i < size && mqtt_diag.prefix_size < sizeof(mqtt_diag.prefix); ++i)
mqtt_diag.prefix[mqtt_diag.prefix_size++] = buffer[i];
}
printf("[MQTT-DIAG] read request=%d rc=%d timeout_ms=%d elapsed_ms=%u errno_snapshot=%d\r\n",
size, result, timeout, mqtt_diag_elapsed(start), socket_error);
return result;
}
static int mqtt_diag_write(Network *network, unsigned char *buffer, int size, int timeout)
{
unsigned int start = ql_rtos_get_systicks();
int result = mqtt_diag.write(network, buffer, size, timeout);
int socket_error = result <= 0 ? ql_get_soc_errno() : 0;
++mqtt_diag.write_calls;
if (result > 0) mqtt_diag.sent += (unsigned int)result;
printf("[MQTT-DIAG] write request=%d rc=%d timeout_ms=%d elapsed_ms=%u errno_snapshot=%d\r\n",
size, result, timeout, mqtt_diag_elapsed(start), socket_error);
return result;
}
static const char *mqtt_diag_connack_reason(unsigned char code)
{
switch (code) {
case 0: return "accepted";
case 1: return "protocol_version_rejected";
case 2: return "client_id_rejected";
case 3: return "server_unavailable";
case 4: return "bad_username_or_password";
case 5: return "not_authorized";
default: return "reserved_code";
}
}
int talkingq_mqtt_connect_diagnosed(MQTTClient *client, MQTTPacket_connectData *options)
{
Network *network = client->ipstack;
MQTTConnackData connack = {255, 255};
unsigned int start;
int result;
int valid_connack;
memset(&mqtt_diag, 0, sizeof(mqtt_diag));
mqtt_diag.read = network->mqttread;
mqtt_diag.write = network->mqttwrite;
if (!mqtt_diag.read || !mqtt_diag.write) {
printf("[MQTT-DIAG] missing transport callbacks; calling SDK unchanged\r\n");
return MQTTConnectWithResults(client, options, &connack);
}
printf("[MQTT-DIAG] CONNECT begin version=%u clean=%u keepalive_s=%u timeout_ms=%u already_connected=%d\r\n",
(unsigned int)options->MQTTVersion, (unsigned int)options->cleansession,
(unsigned int)options->keepAliveInterval, client->command_timeout_ms, client->isconnected);
network->mqttread = mqtt_diag_read;
network->mqttwrite = mqtt_diag_write;
start = ql_rtos_get_systicks();
result = MQTTConnectWithResults(client, options, &connack);
network->mqttread = mqtt_diag.read;
network->mqttwrite = mqtt_diag.write;
printf("[MQTT-DIAG] CONNECT end rc=%d elapsed_ms=%u write_calls=%u tx_bytes=%u read_calls=%u rx_bytes=%u\r\n",
result, mqtt_diag_elapsed(start), mqtt_diag.write_calls, mqtt_diag.sent,
mqtt_diag.read_calls, mqtt_diag.received);
valid_connack = mqtt_diag.prefix_size == 4 && mqtt_diag.prefix[0] == 0x20 &&
mqtt_diag.prefix[1] == 2 && (mqtt_diag.prefix[2] & 0xfe) == 0 &&
mqtt_diag.prefix[3] <= 5 &&
(mqtt_diag.prefix[3] == 0 || mqtt_diag.prefix[2] == 0);
if (valid_connack) {
printf("[MQTT-DIAG] CONNACK code=%u reason=%s session_present=%u\r\n",
(unsigned int)mqtt_diag.prefix[3], mqtt_diag_connack_reason(mqtt_diag.prefix[3]),
(unsigned int)mqtt_diag.prefix[2]);
} else if (mqtt_diag.received) {
printf("[MQTT-DIAG] no valid initial CONNACK: received_prefix_bytes=%u\r\n", mqtt_diag.prefix_size);
} else {
printf("[MQTT-DIAG] no response bytes returned by SDK read\r\n");
}
if (result != 0 && !mqtt_diag.write_calls)
printf("[MQTT-DIAG] failed before transport write; inspect client state/CONNECT serialization\r\n");
/* SDK read maps both EOF and some socket errors to -1. The errno snapshot
* can be stale; do not claim a broker rejection without a valid CONNACK. */
return result;
}
@@ -327,14 +327,14 @@ void vad_audio_process(void) {
ql_audio_record_read(record_hd, mic_buf, &record_len);
//如果检测到声音再去传输
if(first_sound_detected){
//实际上每次编码需要输入1152字节的PCM数据
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){
// if(first_sound_detected){
//实际上每次编码需要输入1152字节的PCM数据
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){
//quec_usbcdc_write((uint8*)mic_buf, record_len);
ql_rtos_semaphore_release(sender_sem); //告诉线程去获取录音数据
}
//quec_usbcdc_write((uint8*)mic_buf, record_len);
ql_rtos_semaphore_release(sender_sem); //告诉线程去获取录音数据
}
// }
#if 1
for(index=0; index<320; index++)
{
@@ -354,12 +354,14 @@ void vad_audio_process(void) {
// 连续达到阈值才确认检测到声音
if (speech_detect_count >= SPEECH_DETECT_THRESHOLD) {
#if 0
//实际上每次编码需要输入1152字节的PCM数据
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){
//quec_usbcdc_write((uint8*)mic_buf, record_len);
ql_rtos_semaphore_release(sender_sem); //告诉线程去获取录音数据
}
#endif
first_sound_detected = true;
set_state(STATE_RECORDING_ACTIVE);
@@ -49,7 +49,7 @@ static int16 *ppbuff=mp3_buff;
static int onmessage_status = -1;
volatile int tts_playing = 0;
unsigned char wss_connect_start = 0;
static int player_mq_couter = 0;
static volatile int player_mq_couter = 0;
static unsigned char audio_in_task_cancel = 0;
static unsigned char xiaoice_wss_connect_status = 0;
@@ -105,6 +105,11 @@ static librws_app_t app = &_app;
static ql_sem_t ws_connect_sem = NULL;
extern int fota_start;
#define OTA_AUDIO_STOP_TIMEOUT_MS 3000
#define OTA_AUDIO_STOP_POLL_MS 20
void xiaoice_clear_player_mq(void);
static int tts_play_status = 0;//1播放中 2播放完成
@@ -234,8 +239,13 @@ void add_file_to_play_player_queue(char *path, int type)
//如果录音后 超过10s 还没有收到信息 提示本轮对话结束
static void onmessage_text(rws_socket socket, const char *text, const unsigned int len)
{
static void onmessage_text(rws_socket socket, const char *text, const unsigned int len)
{
if (fota_start == 1) {
printf("ignore websocket audio during OTA\r\n");
return;
}
// printf("message(txt), %d(byte): %s ", len, text);
char url_buff[128]={0x00};
@@ -820,6 +830,49 @@ int app_play_tips_file(int index)
return 0;
}
int app_play_ota_tips_file(void)
{
char filename_buff[64] = {0};
uint32_t timeout;
/* OTA owns the audio path from this point on. Stop the active conversation
* before queuing the prompt, otherwise recording/TTS rejects or masks it. */
if (vad_audio_is_recording()) {
vad_audio_stop_record();
}
if (get_xiaoice_connected_status() == 2) {
talkingq_ws_disconnect();
}
tts_playing = 2;
xiaoice_clear_player_mq();
timeout = hal_os_up_time_ms();
while (player_mq_couter > 0) {
ql_rtos_task_sleep_ms(OTA_AUDIO_STOP_POLL_MS);
if (hal_os_up_time_ms() - timeout > OTA_AUDIO_STOP_TIMEOUT_MS) {
printf("OTA wait previous audio timeout, counter:%d\r\n",
player_mq_couter);
return -3;
}
}
tts_play_status = 0;
tts_playing = 0;
/* Normal prompts are blocked while fota_start is set. Queue the dedicated
* OTA prompt directly after the old conversation has fully stopped. */
snprintf(filename_buff, sizeof(filename_buff), "U:/%s", mp3_file[3]);
printf("xiaoice_play_index:3\n");
if (ql_access(filename_buff, 0) != 0) {
return -2;
}
add_file_to_play_player_queue(filename_buff, 0);
return player_mq_couter > 0 ? 0 : -1;
}
void app_player_thread(void *arvg)
{
ice_work_item *player_item=NULL;
@@ -1044,12 +1097,12 @@ void xiaoice_clear_player_mq(void)
free(player_item->data);
player_item->data = NULL;
}
if(player_mq_couter>0) player_mq_couter--;
free(player_item);
player_item = NULL;
}
}
player_mq_couter=0;
}
void xiaoice_close_all_audio(void)
-7
View File
@@ -1,7 +0,0 @@
#ifndef NFC_GNSS_BOARD_FEATURES_H
#define NFC_GNSS_BOARD_FEATURES_H
/* Legacy board has no GNSS. Set to 1 and rebuild kernel + app for GNSS boards. */
#define NFC_GNSS_BOARD_HAS_GNSS 0
#endif
@@ -4,7 +4,6 @@
============================================================================*/
#include "gps_app.h"
#include "../../../../ql-config/config/board_features.h"
#include <string.h>
#include "gps_debug.h"
#include "quec_hx_agps.h"
@@ -841,10 +840,6 @@ void quec_get_ocname_by_gnss_task(void * argv)
void quec_get_gnssid_task(void)
{
#if !NFC_GNSS_BOARD_HAS_GNSS
RTI_LOG("[GNSS] Legacy board: skip GNSS ID detection");
return;
#else
QuecOSStatus err = kNoErr;
quec_task_t quec_ocname_get = NULL;
err = quec_rtos_task_create(&quec_ocname_get, 2048, QUEC_APP_TASK_PRIORITY,"q_gocg", quec_get_ocname_by_gnss_task, NULL);
@@ -854,7 +849,6 @@ void quec_get_gnssid_task(void)
RTI_LOG("quec_get_ocname_by_gnss_task create failed!");
}
return;
#endif
}
#endif
+1 -1
View File
@@ -6,7 +6,7 @@ import struct
import subprocess
root = Path(__file__).resolve().parents[1]
output = root / 'artifacts/wss/ecc-fix'
output = root / 'artifacts/wss/new-board'
output.mkdir(parents=True, exist_ok=True)
toolchain = root / 'ql-cross-tool/win32/owtoolchain'
gcc = toolchain / 'gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe'
+1 -1
View File
@@ -11,7 +11,7 @@ parser.add_argument('chain', nargs='?', default=str(root / 'artifacts/wss/retest
parser.add_argument('--compare-nist', action='store_true')
parser.add_argument('--compare-windows', action='store_true')
args = parser.parse_args()
output = root / 'artifacts/wss/tls-speed/host-cert'
output = root / 'artifacts/wss/new-board/host-cert'
output.mkdir(parents=True, exist_ok=True)
chain = Path(args.chain).resolve()
sources = '''asn1parse asn1write bignum base64 pem oid pk pkparse pk_wrap rsa rsa_internal
+107
View File
@@ -0,0 +1,107 @@
"""Exercise actual MQTT diagnostic callbacks without a broker or credentials."""
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[1]
output = root / 'artifacts/wss/new-board/mqtt-diag/tests'
output.mkdir(parents=True, exist_ok=True)
(output / 'MQTTClient.h').write_text(r'''
#ifndef TEST_MQTT_CLIENT_H
#define TEST_MQTT_CLIENT_H
typedef struct Network Network;
struct Network {
int (*mqttread)(Network *, unsigned char *, int, int);
int (*mqttwrite)(Network *, unsigned char *, int, int);
};
typedef struct { Network *ipstack; unsigned int command_timeout_ms; int isconnected; } MQTTClient;
typedef struct { unsigned char MQTTVersion, cleansession; unsigned short keepAliveInterval; } MQTTPacket_connectData;
typedef struct { unsigned char rc, sessionPresent; } MQTTConnackData;
int MQTTConnectWithResults(MQTTClient *, MQTTPacket_connectData *, MQTTConnackData *);
unsigned int ql_rtos_get_systicks(void);
#endif
''')
(output / 'sockets.h').write_text('int ql_get_soc_errno(void);\n')
harness = r'''
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "mqtt_connect_diag.h"
static unsigned int ticks;
static int mode, offset, writes, reads;
unsigned int ql_rtos_get_systicks(void) { return ticks; }
int ql_get_soc_errno(void) { return 104; }
static int fake_write(Network *n, unsigned char *buf, int size, int timeout) {
(void)n;
assert(timeout == 30000 && size == 19);
assert(memcmp(buf, "PRIVATE_TEST_SECRET", 19) == 0);
++writes; ticks += 2;
return mode == 2 ? -1 : size;
}
static int fake_read(Network *n, unsigned char *buf, int size, int timeout) {
unsigned char response[] = {0x20, 2, 0, 0};
(void)n;
assert(timeout == 30000 && (size == 1 || size == 2));
++reads; ticks += 3;
if (mode == 3) return -1;
if (mode == 4) return 0;
if (mode == 1) response[3] = 5;
if (mode == 5) response[0] = 0x30;
if (mode == 7) { response[2] = 1; response[3] = 5; }
memcpy(buf, response + offset, size); offset += size;
return size;
}
int MQTTConnectWithResults(MQTTClient *client, MQTTPacket_connectData *options, MQTTConnackData *ack) {
unsigned char packet[] = "PRIVATE_TEST_SECRET";
unsigned char response[4] = {0};
int result;
assert(options->MQTTVersion == 3);
assert(ack->rc == 255);
if (mode == 6) return -1;
result = client->ipstack->mqttwrite(client->ipstack, packet, 19, 30000);
if (result != 19) { assert(result == -1); return -1; }
/* SDK reads fixed header, remaining length, then the two-byte body. */
result = client->ipstack->mqttread(client->ipstack, response, 1, 30000);
if (mode == 3 || mode == 4) { assert(result == (mode == 3 ? -1 : 0)); return -1; }
assert(result == 1);
assert(client->ipstack->mqttread(client->ipstack, response + 1, 1, 30000) == 1);
assert(client->ipstack->mqttread(client->ipstack, response + 2, 2, 30000) == 2);
assert(response[1] == 2);
return mode == 0 ? 0 : (mode == 1 ? 5 : -1);
}
int main(void) {
Network network = {fake_read, fake_write};
MQTTClient client = {&network, 30000, 0};
MQTTPacket_connectData options = {3, 0, 60};
for (mode = 0; mode < 8; ++mode) {
int expected = mode == 0 ? 0 : (mode == 1 ? 5 : -1);
ticks = mode == 0 ? 0xfffffffeU : 0; offset = writes = reads = 0;
printf("CASE %d\n", mode);
assert(talkingq_mqtt_connect_diagnosed(&client, &options) == expected);
assert(network.mqttread == fake_read && network.mqttwrite == fake_write);
assert(writes == (mode == 6 ? 0 : 1));
if (mode == 6 || mode == 2) assert(reads == 0);
}
return 0;
}
'''
(output / 'test.c').write_text(harness)
include = root / 'ql-application/threadx/interface/custom_app/inc'
source = root / 'ql-application/threadx/interface/custom_app/mqtt_connect_diag.c'
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', '/I' + str(output), '/I' + str(include),
str(source), str(output / 'test.c'), '/Fe' + str(output / 'test.exe')], cwd=output, check=True)
result = subprocess.check_output([str(output / 'test.exe')], text=True)
assert 'PRIVATE_TEST_SECRET' not in result
cases = [part for part in result.split('CASE ')[1:]]
assert len(cases) == 8
assert 'CONNACK code=0 reason=accepted' in cases[0] and 'elapsed_ms=55' in cases[0]
assert 'CONNACK code=5 reason=not_authorized' in cases[1]
assert 'write request=19 rc=-1' in cases[2]
assert 'read request=1 rc=-1' in cases[3]
assert 'read request=1 rc=0' in cases[4]
assert 'no valid initial CONNACK' in cases[5]
assert 'failed before transport write' in cases[6]
assert 'no valid initial CONNACK' in cases[7]
for case in cases[2:]:
assert '[MQTT-DIAG] CONNACK code=' not in case
(output / 'test-output.log').write_text(result)
print('PASS: transport arguments/return values, callback restoration, split CONNACK, rejection, invalid response, errors, no-I/O failure, tick wrap and credential redaction')
+15 -9
View File
@@ -1,4 +1,4 @@
"""Verify the WSS flash package against the current legacy-board build."""
"""Verify the new-board WSS package, including enabled GNSS task entry points."""
import hashlib
import json
import re
@@ -8,7 +8,7 @@ import zipfile
from pathlib import Path
root = Path(__file__).resolve().parents[1]
package = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else root / 'artifacts/wss/full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip'
package = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else root / 'artifacts/wss/new-board/EG800AKCN_11LG-new-board-wss-x25519.zip'
output = package.parent
output.mkdir(parents=True, exist_ok=True)
objdump = root / 'ql-cross-tool/win32/owtoolchain/gcc-arm-none-eabi/bin/arm-none-eabi-objdump.exe'
@@ -33,8 +33,9 @@ clock_body = subprocess.check_output([str(objdump), '-d', '--disassemble=x509_ge
assert '<mbedtls_platform_gmtime_r>' in clock_body and '<GetCurrentCompTime>' not in clock_body
(output / 'x509-clock.disasm.txt').write_text(clock_body)
report = {'package': package.name, 'sha256': hashlib.sha256(package.read_bytes()).hexdigest(),
'board': 'new board with GNSS enabled',
'linked_symbols': required, 'kernel_symbols': kernel_required,
'hardware_test': '2026-09-16 14:38 device log: NIST TLS handshake 1820 ms and HTTP 101 passed; X25519 preference awaits device test',
'hardware_test': 'New-board package awaits device testing; legacy board verified X25519 full handshake at 1455 ms on 2026-09-16',
'backend': '2026-09-16 11:32 CST: WSS 443 /ws HTTP 101; 8080 plaintext WS'}
with zipfile.ZipFile(package) as archive:
assert archive.testzip() is None, 'ZIP CRC failure'
@@ -46,16 +47,21 @@ with zipfile.ZipFile(package) as archive:
assert b'wss://banban.api.talkingq.com:443/ws' in archive.read('customer_app.bin')
assert b'WSS TLS handshake total=' in archive.read('customer_app.bin')
assert b'WSS TLS full handshake: prefer X25519' in archive.read('customer_app.bin')
assert b'Legacy board:' not in archive.read('customer_app.bin')
assert b'Legacy board:' not in archive.read('cp.bin')
assert b'gnss_Task_Ref' in archive.read('customer_app.bin')
report['download_manifest'] = json.loads(archive.read('download.json'))
printf_address = int(re.search(r'^([0-9a-fA-F]+) T printf$', symbols, re.M).group(1), 16)
task_create_address = int(re.search(r'^([0-9a-fA-F]+) T ql_rtos_task_create$', symbols, re.M).group(1), 16)
for symbol, binary, expected in [
('quec_get_gnssid_task', root / 'ql-kernel/threadx/build/kernel.axf', '<RTI_LOG>'),
('app_gnss_init', app, '0x%08x' % (printf_address | 1)),
('set_gnss_mode', app, 'bx\tlr')]:
('quec_get_gnssid_task', root / 'ql-kernel/threadx/build/kernel.axf', '<quec_rtos_task_create>'),
('app_gnss_init', app, '0x%08x' % (task_create_address | 1)),
('set_gnss_mode', app, 'strb')]:
body = subprocess.check_output([str(objdump), '-d', '--disassemble=' + symbol, str(binary)], text=True)
assert '<' + symbol + '>:' in body and expected in body and 'task_create' not in body
assert '<' + symbol + '>:' in body and expected in body, 'GNSS entry point disabled or unexpected code: ' + symbol
(output / (symbol + '.disasm.txt')).write_text(body)
report['verification'] = 'PASS: ZIP CRC, current APP/kernel identity, GNSS bypass, WSS URL, ECDSA/GCM/RTC/NIST kernel symbols and handshake timing logs'
for symbol, table in [('quec_get_ocname_by_gnss_task', kernel_symbols), ('quec_gnss_test', symbols)]:
assert re.search(r'\b' + symbol + r'$', table, re.M), 'Missing GNSS worker: ' + symbol
report['verification'] = 'PASS: ZIP CRC, current APP/kernel identity, GNSS task creation, WSS URL, ECDSA/GCM/RTC/NIST kernel symbols and handshake timing logs'
(output / 'verification.json').write_text(json.dumps(report, indent=2))
print(report['verification'])
print('SHA256:', report['sha256'])