Author SHA1 Message Date
chenjintao 54bf67cafa Document WSS fixes, algorithms and verified device performance
Add the detailed Chinese WSS implementation and troubleshooting report, including commit history, firmware hashes and X25519 device results. Update the existing guides with the verified 1455 ms full TLS handshake.
2026-09-16 16:17:12 +08:00
chenjintao 551a7bb1e6 Prefer X25519 for legacy WSS full handshakes
Prioritize X25519 while retaining enabled curve fallbacks and log the negotiated ECDHE group. Extend ABI checks, crypto benchmarks and backend group probes, and document the updated APP package.
2026-09-16 15:32:22 +08:00
chenjintao 3a0714c735 Accelerate legacy WSS elliptic-curve operations and time TLS phases
Enable mbedTLS NIST modular reduction and report per-phase handshake timings. Add handshake-loop regression tests and a certificate verification benchmark, and document the full kernel and APP package requirement.
2026-09-16 14:38:43 +08:00
chenjintao 5c3e7eee6d Fix legacy WSS certificate support and receive retries
Share the APP/kernel TLS profile, enable ECDSA and AES-GCM, and use RTC time for certificate validation. Fix WANT_READ/WANT_WRITE handling during WebSocket upgrade and add ABI, certificate, and receive regression checks.
2026-09-16 14:19:55 +08:00
chenjintao 3e463f57b6 Add verified WSS transport for legacy boards
Configure the default WSS endpoint and ISRG Root X2 trust anchor, preserve device authentication, enforce TLS and WebSocket handshake validation, and handle partial I/O and connection cleanup. Add host tests, server probes, package verification and integration documentation. Backend 443 /ws still returns HTTP 404; on-device validation remains pending.
2026-09-16 11:23:33 +08:00
chenjintao 4b565958f9 Set default volume to 100 percent on legacy boards 2026-09-15 17:45:53 +08:00
chenjintao f283223b23 Support legacy boards without GNSS 2026-09-15 17:03:13 +08:00
29 changed files with 622 additions and 1673 deletions
-3
View File
@@ -2,9 +2,6 @@
/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
@@ -0,0 +1,9 @@
# 旧板专用分支: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
@@ -0,0 +1,6 @@
# Local firmware exports, build logs, verification output and build-file backups.
*.zip
*.log
*.disasm.txt
/verification.json
/build-backup/
+53
View File
@@ -0,0 +1,53 @@
# 无 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 等所需功能。
构建成功不能代替上述板上验证。
@@ -0,0 +1,45 @@
"""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"])
+7 -11
View File
@@ -1,13 +1,9 @@
# WSS 联调包
# 旧板 WSS 联调包
最新域名试验包:new-board/mqtt-domain/EG800AKCN_11LG-new-board-wss-mqtt-domain.zip。MQTT 改为 banban.api.talkingq.com:1883,保留诊断日志;用户已确认改域名后新板连接成功,详细成功日志和其他业务验收尚未提供。
最新优化包为 `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-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 忽略;提交源码与文档不会把这些本地包一并上传。
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`。
+3 -1
View File
@@ -1,6 +1,8 @@
@echo off
set CURDIR=%~dp0
for /f %%i in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMddHHmmss"') do set appbuildtime=%%i
set time0=%time: =0%
set hour=%time0:~0,2%
set appbuildtime=%date:~0,4%%date:~5,2%%date:~8,2%%hour%%time:~3,2%%time:~6,2%
set appbuildrev=NULL
set appbuildmodel=EG800AKCN_11LG
set appbuildver=beta1
@@ -1,7 +1,5 @@
# 旧板 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
@@ -1,125 +0,0 @@
# 新板 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,7 +1,5 @@
# 旧板 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,7 +81,6 @@ 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 = 70;
g_cur_volume = 100;
hal_kv_set("volume", &g_cur_volume, 1);
return VOLUME_70;
return VOLUME_100;
}
g_cur_volume = value;
@@ -19,11 +19,9 @@
#include "ql_atcmd.h"
#include "app_inc.h"
#include "ql_uart.h"
#include "ql_application.h"
#include "ql_log.h"
#include "ql_adc.h"
#include "ql_power.h"
#include "hal_api.h"
#include "ql_application.h"
#include "ql_log.h"
#include "hal_api.h"
// 电量阈值定义
#define BAT_VOLTAGE_100 4200
@@ -38,24 +36,25 @@
#define BAT_VOLTAGE_10 3550
#define BAT_VOLTAGE_MIN 3500
#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
// ========== 新增: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内判定为稳定
unsigned short g_vbat_vol = 0;
unsigned short vbat_sample[6] = {0}; // 修改:样本数改为6
unsigned char battery_first_update_flag = 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;
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; // 状态稳定计数
bat_level_t calc_battery_level(uint16_t voltage_mv)
{
@@ -72,64 +71,101 @@ bat_level_t calc_battery_level(uint16_t voltage_mv)
else return BAT_LEVEL_0;
}
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;
}
}
/**
* @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;
}
/* 修改后的 battery_check:分阶段采样,采样窗口为6次 */
void battery_check(void)
@@ -138,17 +174,15 @@ 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 if (g_low_probe_active) {
do_sample = 1; // 低电风险时每5秒快速确认
} else {
// 2分钟后,每6次调用采样一次 (6*5s=30秒)
if (call_count % BAT_NORMAL_SAMPLE_CALLS == 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) {
do_sample = 1;
}
}
@@ -161,21 +195,9 @@ 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();
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;
ql_adc_onff(0);
vbat_vol = ql_get_battery_vol();
sample_buf[sample_idx] = vbat_vol;
sample_idx++;
/* 当收集满6个样本时进行平均处理 */
@@ -193,29 +215,47 @@ void battery_check(void)
vol_all = vol_all - min - max;
vbat_vol = vol_all / 4; // 6个样本去掉最大最小,剩余4个平均
g_vbat_vol = vbat_vol;
printf("vbat_vol:%d\r\n", g_vbat_vol);
/* 更新电压历史(用于充电趋势判断) */
update_voltage_history(vbat_vol);
/* 计算电量等级并限制跳变 */
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);
/* 判断充电状态 */
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);
if (battery_first_update_flag == 0) {
battery_first_update_flag = 1;
}
/* 极低电关机由原始电压独立处理,普通低电提示只播放一次 */
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;
}
/* 低电量处理 */
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);
}
sample_idx = 0; // 重置样本索引,准备下一轮采集
}
@@ -15,6 +15,7 @@
#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
@@ -320,9 +321,14 @@ 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)
@@ -531,6 +537,11 @@ 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
}
@@ -30,40 +30,9 @@ extern int fota_start;
#define KEY_LONG_PRESS_1_5S 1500/5
#define KEY_LONG_PRESS_3S 3000/5
#define KEY_LONG_PRESS_5S 5000/5
#define KEY_LONG_PRESS_10S 10000/5
#define NFC_TX_CLICK_GAP 2000/5
#define NFC_TX_CLICK_COUNT 5
static unsigned char key_down_event = 0;
static unsigned char nfc_tx_click_count = 0;
static u32 nfc_tx_last_click_time = 0;
extern void app_nfc_tx_mode_start(void);
static int key_mic_register_short_click(void)
{
u32 now = ql_rtos_get_systicks();
if (nfc_tx_click_count == 0 ||
(u32)(now - nfc_tx_last_click_time) > NFC_TX_CLICK_GAP) {
nfc_tx_click_count = 1;
} else {
nfc_tx_click_count++;
}
nfc_tx_last_click_time = now;
printf("NFC TX key clicks:%u/%u\r\n",
nfc_tx_click_count, NFC_TX_CLICK_COUNT);
if (nfc_tx_click_count >= NFC_TX_CLICK_COUNT) {
nfc_tx_click_count = NFC_TX_CLICK_COUNT;
app_nfc_tx_mode_start();
return 1;
}
/* Keep the first click's existing voice action. Suppress clicks 2-4 so
* a rapid NFC gesture does not repeatedly start/interrupt voice work. */
return nfc_tx_click_count > 1;
}
#define KEY_LONG_PRESS_10S 10000/5
static unsigned char key_down_event = 0;
static void key_press_mic(void *args)
{
@@ -145,36 +114,26 @@ static void key_thread_entry(void *parameter)
if(level!=0){ //up
key_press_mic_status = 0;
if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S){
nfc_tx_click_count = 0;
//app_play_tips_file(10);
if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S){
//app_play_tips_file(10);
// power_down_status = 1;
ql_rtos_task_sleep_s(3);
ql_power_down(1);
}
else if(ql_rtos_get_systicks() - key_press_mic_time > KEY_WAIT_TIME){
int nfc_click_handled = key_mic_register_short_click();
//休眠唤醒其它线程
if(app_get_app_sleep_flag()==0){
app_set_app_operation(1);
}
if (!nfc_click_handled) {
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);
}
}else{
app_play_tips_file(2);
}
}
}
else if(ql_rtos_get_systicks() - key_press_mic_time > KEY_WAIT_TIME){
//休眠唤醒其它线程
if(app_get_app_sleep_flag()==0){
app_set_app_operation(1);
}
if(app_get_data_call_net_status()&&get_mqtt_connect_status()==1){
if(tts_play_interrupt_func()==-2){
app_play_tips_file(2);
}
}else{
app_play_tips_file(2);
}
}
key_press_mic_time = ql_rtos_get_systicks();
key_press_mic_counter = 0;
@@ -184,9 +143,8 @@ static void key_thread_entry(void *parameter)
key_press_mic_status = 1;//表示已经按下了
}
if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S && power_down_status==0){
nfc_tx_click_count = 0;
power_down_status = 1;
if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S && power_down_status==0){
power_down_status = 1;
app_play_tips_file(10);
power_down_status = 2;
//ql_rtos_task_sleep_s(3);
@@ -36,7 +36,7 @@ const char *mp3_file[] ={
"ai_active_zh.mp3",//18
};
//http://banban.api.talkingq.com:8080/assets/roles/banban/tts_error.mp3
//http://175.24.73.253/assets/roles/zhuli/zh/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://banban.api.talkingq.com:8080/assets/roles/banban/%s", mp3_file[index]);
sprintf(url_buff, "http://101.35.224.118:8080/assets/roles/banban/%s", mp3_file[index]);
ret = http_file_download(url_buff);
if(ret!=0&&cnt++<3) goto down_loop;
@@ -1,9 +0,0 @@
#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,11 +280,10 @@ 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;
uint32_t content_length = 0;
int error_cnt = 0;
int resp_status = -1;
uint32_t nw = length;
uint32_t page_pos = 0;
int error_cnt = 0;
//printf("fd_fetch start\n");
session = webclient_session_create(1024);
@@ -307,12 +306,11 @@ 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;
}
content_length = session->content_length;
if (session->content_length == 0)
{
printf("Request file size is 0!");
goto __exit;
}
printf("start read\n");
if(total_length < session->content_length)
@@ -327,7 +325,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() || tts_playing_ret()==2) {
if(vad_audio_is_recording()) {
break;
}
@@ -363,7 +361,7 @@ __exit:
session = NULL;
}
if(total_length<content_length){
if(total_length<session->content_length){
total_length = 0;
return -1;
}
+102 -474
View File
@@ -4,7 +4,6 @@
=================================================================*/
#include "MQTTClient.h"
#include "mqtt_connect_diag.h"
#include "ql_data_call.h"
#include "ql_application.h"
#include "sockets.h"
@@ -26,7 +25,7 @@
#define PROFILE_IDX 1
//#define DEFAULT_SERVER_DOMAIN "175.24.73.253"
#define DEFAULT_SERVER_DOMAIN "banban.api.talkingq.com"
#define DEFAULT_SERVER_DOMAIN "101.35.224.118"
#define DEFAULT_SERVER_CLIENT_USER "talkingq"
#define DEFAULT_SERVER_CLIENT_PASS "talkingq@2026"
#define DEFAULT_SERVER_CLIENT_ID "9376265c059e474e92c2e46208ae1dc5"
@@ -38,14 +37,6 @@
#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;
@@ -80,8 +71,7 @@ int g_nfc_bind_status = 0;
extern u8 g_cur_volume;
extern u8 g_pre_volume;
extern unsigned short g_vbat_vol;
extern bat_level_t battery_get_level(void);
extern unsigned short g_vbat_vol;
extern sleep_config_t g_sleep_cfg;
extern uint8_t g_sleep_config_valid;
@@ -102,23 +92,8 @@ int app_mqtt_connect_sem(char flag);
#define MQTT_PAYLOAD_SIZE 2048
static char payload[MQTT_PAYLOAD_SIZE] = {0};
extern int fota_start;
int app_play_ota_tips_file(void);
int xiaoice_get_play_counter(void);
extern int fota_start;
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,
@@ -131,177 +106,6 @@ 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;
}
/* ==================== 解析函数 ==================== */
@@ -357,11 +161,6 @@ 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:
@@ -380,27 +179,35 @@ 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) || 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;
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);
}
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;
}
@@ -560,7 +367,7 @@ int parse_event_response(const char *json_str, response_t *resp)
}
else {
// ===== 原有逻辑: NFC事件 - 动态查找 url_x 字段 =====
for (int i = 1; i <= 10; i++) {
for (int i = 1; i <= 5; i++) {
char key[16];
snprintf(key, sizeof(key), "url_%d", i);
cJSON *url = cJSON_GetObjectItem(params, key);
@@ -583,12 +390,11 @@ int parse_event_response(const char *json_str, response_t *resp)
/* [FIX] 安全处理 topic 名称,避免越界访问 */
static void messageArrived(MessageData* data)
{
int ret;
int payload_log_len;
uint32 queue_count = 0;
app_work_item *work_item;
static void messageArrived(MessageData* data)
{
int ret;
uint32 queue_count = 0;
app_work_item *work_item;
// 安全获取 topic 字符串(复制到本地缓冲区)
char topic_buf[128] = {0};
@@ -596,16 +402,10 @@ 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';
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 类型
memcpy(topic_buf, data->topicName->lenstring.data, topic_len);
topic_buf[topic_len] = '\0';
// 比较 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);
@@ -648,8 +448,6 @@ 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){
@@ -664,14 +462,7 @@ int app_mqtt_connect()
NetworkInit(&network, NULL, PROFILE_IDX);
MQTTClientInit(&client, &network, 30000, sendbuf, sizeof(sendbuf), readbuf, sizeof(readbuf));
char* address = DEFAULT_SERVER_DOMAIN;
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)
if ((rc = NetworkConnect(&network, address, mqtt_server_port)) != 0)
{
RLOGE("Return code from network connect is %d", rc);
MQTTClientDeinit(&client);
@@ -695,8 +486,7 @@ int app_mqtt_connect()
connectData.cleansession = 0;
connectData.keepAliveInterval = 60;
printf("[MQTT-DIAG] saved_device_id=%d (registration needed when 0)\r\n", ret == 0);
if ((rc = talkingq_mqtt_connect_diagnosed(&client, &connectData)) != 0)
if ((rc = MQTTConnect(&client, &connectData)) != 0)
{
RLOGE("Return code from MQTT connect is %d", rc);
NetworkDisconnect(&network);
@@ -747,8 +537,6 @@ int app_mqtt_connect()
if(ret!=0){
build_register();
} else {
ota_report_pending_success();
}
RLOGD("========== app_mqtt_connect okay ==========");
@@ -819,33 +607,15 @@ 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;
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 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 lock_ret = ql_rtos_mutex_lock(g_mqtt_public_mutex, 5000);
if (lock_ret != 0) {
RLOGE("MQTT mutex lock timeout");
@@ -862,13 +632,18 @@ 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();
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) {
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) {
mqtt_pub_cnt_err++;
mqtt_public_error = 1;
RLOGE("ERROR Return code from MQTT publish is %d, cnt %d", rc, mqtt_pub_cnt_err);
@@ -1183,18 +958,12 @@ 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();
if (root == NULL || data == NULL) {
cJSON_Delete(root);
cJSON_Delete(data);
return NULL;
}
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();
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);
@@ -1206,66 +975,6 @@ 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)
@@ -1290,95 +999,6 @@ 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 命令分发处理
@@ -1454,7 +1074,26 @@ void handle_command(command_t *cmd)
}
case MSG_OTA_UPGRADE: {
handle_ota_upgrade(cmd);
// 立即响应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);
break;
}
@@ -1600,34 +1239,23 @@ void handle_nfc_trigger(const char *uuid)
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);
}
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);
}
@@ -1807,7 +1435,7 @@ static void app_mqtt_task_event_thread(void *arvg)
app_heartbeat_time_record = hal_os_up_time_ms();
// 采集设备信息
info.power = battery_get_level(); // 使用与设备内部一致的最终电量档位
info.power = calc_battery_level(g_vbat_vol); // 10,20,30...100
info.voice = g_cur_volume; // 40,60,80,100
@@ -1,107 +0,0 @@
#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;
}
+152 -474
View File
@@ -1,488 +1,166 @@
#if 1
#include <stdio.h>
#include <string.h>
#include "ql_rtos.h"
#if 1
#include <stdio.h>
#include "ql_rtos.h"
#include "ql_application.h"
#include "ql_audio.h"
#include "ql_iic.h"
#include "ql_gpio.h"
#include "app_inc.h"
#include "ql_fs.h"
#include "DP1312EA.h"
#include "TypeA.h"
#include "LPCD.h"
#include "hal_api.h"
#define DP1312EA_PIN_IRQ GPIO_PIN_NO_85
#define NFC_TX_MODE_DURATION_MS 30000U
#define NFC_TICKS_PER_MS_DIVISOR 5U
extern int fota_start;
extern int audio_packet_type;
extern volatile int tts_playing;
extern int g_testmode;
extern uint8_t get_sleep_mode(void);
extern int get_mqtt_connect_status(void);
extern bool vad_audio_is_recording(void);
extern int xiaoice_get_play_counter(void);
static ql_sem_t nfc_irq_sem = NULL;
// NFC 任务状态
#include "app_inc.h"
#include "ql_fs.h"
extern int fota_start;
extern int audio_packet_type;
extern volatile int tts_playing;
extern int g_testmode;
static ql_sem_t nfc_irq_sem = NULL;
#define DP1312EA_PIN_IRQ GPIO_PIN_NO_85
// NFC 任务状态
typedef enum {
NFC_STATE_LPCD_WAIT, // 低功耗等待中断
NFC_STATE_POLLING, // 500ms 轮询读卡中
} nfc_state_t;
static volatile nfc_state_t g_nfc_state = NFC_STATE_LPCD_WAIT;
static volatile int g_poll_fail_count = 0;
static volatile int g_nfc_irq_trigger = 0;
static volatile u8 g_nfc_tx_mode_requested = 0;
static volatile u32 g_nfc_tx_mode_deadline = 0;
#define NFC_CARD_FRAME_MAX 64
#define NFC_CARD_TX_POLLS 20
#define NFC_CARD_RX_POLLS 200
#define NFC_CARD_SESSION_POLL_MS 1
#define NFC_TYPE2_PAGE_SIZE 4
#define NFC_TYPE2_READ_SIZE 16
#define NFC_TYPE2_MEMORY_SIZE 160
#define NFC_TYPE2_NDEF_OFFSET 16
#define NFC_IRQ_TX 0x40
#define NFC_IRQ_RX 0x20
#define NFC_IRQ_ERR 0x02
extern int hal_os_get_device_sn(char *pdevice_sn);
extern s8 Test_NTAG213(void);
static u8 nfc_type2_memory[NFC_TYPE2_MEMORY_SIZE];
static u8 nfc_tx_mode_is_active(void)
{
u32 now;
if (!g_nfc_tx_mode_requested) {
return 0;
}
now = ql_rtos_get_systicks();
if ((s32)(now - g_nfc_tx_mode_deadline) >= 0) {
g_nfc_tx_mode_requested = 0;
return 0;
}
return 1;
}
void app_nfc_tx_mode_start(void)
{
g_nfc_tx_mode_deadline = ql_rtos_get_systicks() +
(NFC_TX_MODE_DURATION_MS / NFC_TICKS_PER_MS_DIVISOR);
g_nfc_tx_mode_requested = 1;
if (nfc_irq_sem != NULL) {
ql_rtos_semaphore_release(nfc_irq_sem);
}
printf("NFC TX mode requested for 30 seconds\r\n");
}
static void nfc_card_emulation_log_status(const char *reason)
{
printf("NFC card emulation %s: cmd=%02X irq=%02X err=%02X status1=%02X status2=%02X fifo=%02X txm=%02X rxm=%02X framing=%02X\r\n",
reason,
DP1312EA_ReadRawRC(CommandReg),
DP1312EA_ReadRawRC(ComIrqReg),
DP1312EA_ReadRawRC(ErrorReg),
DP1312EA_ReadRawRC(Status1Reg),
DP1312EA_ReadRawRC(Status2Reg),
DP1312EA_ReadRawRC(FIFOLevelReg),
DP1312EA_ReadRawRC(TxModeReg),
DP1312EA_ReadRawRC(RxModeReg),
DP1312EA_ReadRawRC(BitFramingReg));
}
static u8 nfc_card_emulation_read_frame(u8 *frame, u8 capacity)
{
u8 fifo_len = DP1312EA_ReadRawRC(FIFOLevelReg) & 0x7F;
u8 read_len = fifo_len;
if (read_len > capacity) {
read_len = capacity;
}
if (read_len > 0) {
DP1312EA_ReadFIFO(frame, read_len);
}
while (read_len < fifo_len) {
(void)DP1312EA_ReadRawRC(FIFODataReg);
read_len++;
}
return fifo_len > capacity ? capacity : fifo_len;
}
static void nfc_card_emulation_log_frame(const char *prefix,
const u8 *frame,
u8 length)
{
u8 i;
printf("NFC %s (%u):", prefix, length);
for (i = 0; i < length; i++) {
printf(" %02X", frame[i]);
}
printf("\r\n");
}
static u8 nfc_card_emulation_send_frame(const u8 *frame, u8 length)
{
u16 poll_count;
u8 irq;
/* AutoColl has activated target mode and changed to Transceive. Keep it
* active: writing Idle here clears TargetActivated in Status2Reg. */
DP1312EA_WriteRawRC(ComIrqReg, 0x7F);
DP1312EA_SetBitMask(FIFOLevelReg, 0x80);
DP1312EA_SetBitMask(TxModeReg, 0x80);
DP1312EA_SetBitMask(RxModeReg, 0x80);
DP1312EA_WriteRawRC(BitFramingReg, 0x00);
DP1312EA_WriteFIFO((u8 *)frame, length);
DP1312EA_SetBitMask(BitFramingReg, 0x80);
for (poll_count = 0; poll_count < NFC_CARD_TX_POLLS; poll_count++) {
irq = DP1312EA_ReadRawRC(ComIrqReg);
if ((irq & NFC_IRQ_TX) != 0) {
DP1312EA_WriteRawRC(BitFramingReg, 0x00);
return 1;
}
if ((irq & NFC_IRQ_ERR) != 0) {
break;
}
ql_rtos_task_sleep_ms(NFC_CARD_SESSION_POLL_MS);
}
DP1312EA_WriteRawRC(BitFramingReg, 0x00);
nfc_card_emulation_log_status("frame transmit failed");
return 0;
}
static void nfc_type2_build_ndef(void)
{
char device_id[32] = {0};
char serial_number[32] = {0};
char json[128];
int json_len;
u8 *ndef;
u8 record_len;
if (hal_os_get_device_id(device_id) != 0) {
strcpy(device_id, "unknown");
}
if (hal_os_get_device_sn(serial_number) != 0) {
strcpy(serial_number, "unknown");
}
json_len = snprintf(json, sizeof(json),
"{\"device_id\":\"%s\",\"serial_number\":\"%s\"}",
device_id, serial_number);
if (json_len < 0) {
json_len = 0;
} else if (json_len >= (int)sizeof(json)) {
json_len = sizeof(json) - 1;
}
memset(nfc_type2_memory, 0, sizeof(nfc_type2_memory));
/* Static UID/manufacturer pages followed by a read-only Type 2 CC. */
nfc_type2_memory[0] = 0x08;
nfc_type2_memory[1] = 0xAA;
nfc_type2_memory[2] = 0xBB;
nfc_type2_memory[3] = 0xCC;
nfc_type2_memory[12] = 0xE1;
nfc_type2_memory[13] = 0x10;
nfc_type2_memory[14] = 0x12; /* 144 data bytes (18 x 8). */
nfc_type2_memory[15] = 0x0F; /* Read-only access. */
ndef = &nfc_type2_memory[NFC_TYPE2_NDEF_OFFSET];
record_len = (u8)(7 + json_len);
ndef[0] = 0x03; /* NDEF Message TLV. */
ndef[1] = record_len;
ndef[2] = 0xD1; /* MB, ME, SR, TNF well-known. */
ndef[3] = 0x01; /* Type length. */
ndef[4] = (u8)(3 + json_len); /* Text status/lang plus JSON. */
ndef[5] = 'T';
ndef[6] = 0x02; /* UTF-8, language length 2. */
ndef[7] = 'e';
ndef[8] = 'n';
memcpy(&ndef[9], json, json_len);
ndef[9 + json_len] = 0xFE; /* Terminator TLV. */
printf("NFC Type 2 NDEF: %s\r\n", json);
}
static void nfc_type2_read_pages(u8 start_page, u8 *response)
{
u16 start = (u16)start_page * NFC_TYPE2_PAGE_SIZE;
u8 i;
for (i = 0; i < NFC_TYPE2_READ_SIZE; i++) {
u16 offset = start + i;
response[i] = offset < NFC_TYPE2_MEMORY_SIZE
? nfc_type2_memory[offset]
: 0x00;
}
}
static void nfc_card_emulation_configure_type2(void)
{
static const u8 card_config[] = {
0x04, 0x00, /* ATQA: single-size (4-byte) UID */
0xAA, 0xBB, 0xCC, /* NFCID1; first byte 08 is added by PN512 */
0x00, /* SEL_RES / SAK: Type 2, no ISO-DEP */
0x01, 0xFE, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0xFF, 0xFF,
0x88
};
DP1312EA_PcdConfigISOType('K');
ql_rtos_task_sleep_ms(2);
DP1312EA_WriteFIFO((u8 *)card_config, sizeof(card_config));
ql_rtos_task_sleep_ms(2);
DP1312EA_WriteRawRC(CommandReg, DP1312EA_MEM);
DP1312EA_WriteRawRC(CommandReg, DP1312EA_AUTOCOLL);
}
static void nfc_card_emulation_start(void)
{
DP1312EA_WriteRawRC(CommandReg, DP1312EA_IDLE);
DP1312EA_WriteRawRC(ComIrqReg, 0x7F);
DP1312EA_WriteRawRC(DivIrqReg, 0x7F);
DP1312EA_SetBitMask(FIFOLevelReg, 0x80);
nfc_type2_build_ndef();
nfc_card_emulation_configure_type2();
nfc_card_emulation_log_status("started");
printf("NFC Type 2 tag: ATQA=04 00, UID=08 AA BB CC, SAK=00\r\n");
}
static void nfc_card_emulation_task(void)
{
u8 frame[NFC_CARD_FRAME_MAX];
u8 response[NFC_TYPE2_READ_SIZE];
u8 frame_len;
u16 poll_count;
u8 last_command = 0xFF;
u8 command;
nfc_card_emulation_start();
while (nfc_tx_mode_is_active()) {
command = DP1312EA_ReadRawRC(CommandReg) & 0x0F;
if (command != last_command) {
nfc_card_emulation_log_status("state changed");
last_command = command;
}
if (command == DP1312EA_TRANSCEIVE) {
frame_len = nfc_card_emulation_read_frame(frame, sizeof(frame));
while (nfc_tx_mode_is_active() &&
frame_len >= 2 && frame[0] == 0x30) {
nfc_card_emulation_log_frame("Type 2 READ RX", frame,
frame_len);
nfc_type2_read_pages(frame[1], response);
if (!nfc_card_emulation_send_frame(response,
sizeof(response))) {
break;
}
printf("NFC Type 2 READ page %u TX complete\r\n", frame[1]);
nfc_card_emulation_log_frame("Type 2 READ TX", response,
sizeof(response));
DP1312EA_WriteRawRC(ComIrqReg, NFC_IRQ_TX);
for (poll_count = 0;
poll_count < NFC_CARD_RX_POLLS &&
nfc_tx_mode_is_active();
poll_count++) {
u8 irq = DP1312EA_ReadRawRC(ComIrqReg);
if ((irq & NFC_IRQ_ERR) != 0) {
nfc_card_emulation_log_status("Type 2 receive error");
break;
}
if ((irq & NFC_IRQ_RX) != 0 &&
(DP1312EA_ReadRawRC(FIFOLevelReg) & 0x7F) != 0) {
frame_len = nfc_card_emulation_read_frame(
frame, sizeof(frame));
break;
}
ql_rtos_task_sleep_ms(NFC_CARD_SESSION_POLL_MS);
}
if (poll_count == NFC_CARD_RX_POLLS) {
nfc_card_emulation_log_status("Type 2 receive timeout");
break;
}
if ((DP1312EA_ReadRawRC(ComIrqReg) & NFC_IRQ_ERR) != 0) {
break;
}
}
if (frame_len > 0 && !(frame_len >= 2 && frame[0] == 0x30)) {
nfc_card_emulation_log_frame("Type 2 session end RX", frame,
frame_len);
}
nfc_card_emulation_log_status("session complete");
nfc_card_emulation_start();
last_command = DP1312EA_AUTOCOLL;
} else if (command != DP1312EA_AUTOCOLL) {
nfc_card_emulation_log_status("unexpected command");
nfc_card_emulation_start();
last_command = DP1312EA_AUTOCOLL;
}
ql_rtos_task_sleep_ms(1);
}
DP1312EA_WriteRawRC(CommandReg, DP1312EA_IDLE);
DP1312EA_WriteRawRC(ComIrqReg, 0x7F);
DP1312EA_SetBitMask(FIFOLevelReg, 0x80);
printf("NFC TX mode expired, restoring reader mode\r\n");
}
static void nfc_irq_wake(void *args)
{
PIN_LEVEL_E level = PIN_LEVEL_LOW;
ql_eint_disable_wakeup(DP1312EA_PIN_IRQ);
ql_gpio_get_level(DP1312EA_PIN_IRQ, &level);
printf("DP1312EA_PIN_IRQ level:%d\n", level);
ql_eint_enable_wakeup(DP1312EA_PIN_IRQ, PIN_RISING_EDGE);
if (g_nfc_tx_mode_requested) {
return;
}
if(g_nfc_irq_trigger==0){
g_nfc_irq_trigger = 1;
ql_rtos_semaphore_release(nfc_irq_sem);
}
}
static void nfc_reader_prepare(void)
{
DP1312EA_Reset();
Check_Chip();
g_nfc_state = NFC_STATE_LPCD_WAIT;
g_poll_fail_count = 0;
g_nfc_irq_trigger = 0;
}
static void nfc_reader_task_step(void)
{
s8 read_status;
int can_read = 0;
if (g_nfc_state == NFC_STATE_LPCD_WAIT) {
DP1312EA_LPCD_Start();
printf("NFC enter LPCD mode, waiting for IRQ...\r\n");
ql_rtos_semaphore_wait(nfc_irq_sem, 0xFFFFFFFF);
if (g_nfc_tx_mode_requested) {
return;
}
DP1312EA_CLEAR_IRQ();
ql_rtos_task_sleep_ms(10);
printf("NFC IRQ wake up, switch to polling mode\r\n");
g_nfc_state = NFC_STATE_POLLING;
g_poll_fail_count = 0;
return;
}
if (fota_start == 0 &&
get_sleep_mode() != 1 &&
get_mqtt_connect_status() == 1) {
if ((vad_audio_is_recording() != 1 &&
audio_packet_type == 0 &&
tts_playing == 0 &&
xiaoice_get_play_counter() == 0) ||
g_testmode == 1) {
can_read = 1;
}
}
if (!can_read) {
ql_rtos_task_sleep_ms(300);
return;
}
read_status = Test_NTAG213();
if (read_status == 0) {
printf("NFC read OK, back to LPCD\r\n");
g_nfc_state = NFC_STATE_LPCD_WAIT;
g_poll_fail_count = 0;
g_nfc_irq_trigger = 0;
ql_rtos_task_sleep_ms(10);
return;
}
g_poll_fail_count++;
printf("NFC read failed, fail_count=%d\r\n", g_poll_fail_count);
if (g_poll_fail_count >= 3) {
printf("NFC fail 3 times, card removed, back to LPCD\r\n");
g_nfc_state = NFC_STATE_LPCD_WAIT;
g_poll_fail_count = 0;
g_nfc_irq_trigger = 0;
ql_rtos_task_sleep_ms(10);
} else {
ql_rtos_task_sleep_ms(300);
}
}
void ql_nfc_task_thread(void *arvg)
{
printf("NFC work mode: READER (5 clicks enable Type 2 TX)\r\n");
while (1) {
if (nfc_tx_mode_is_active()) {
printf("NFC TX mode enabled\r\n");
DP1312EA_Reset();
Check_Chip();
nfc_card_emulation_task();
nfc_reader_prepare();
continue;
}
nfc_reader_task_step();
}
}
static volatile nfc_state_t g_nfc_state = NFC_STATE_LPCD_WAIT;
static volatile int g_poll_fail_count = 0;
static volatile int g_nfc_irq_trigger = 0;
static void nfc_irq_wake(void *args)
{
unsigned char level = 0;
ql_eint_disable_wakeup(DP1312EA_PIN_IRQ);
ql_gpio_get_level(DP1312EA_PIN_IRQ, &level);
printf("DP1312EA_PIN_IRQ level:%d\n", level);
ql_eint_enable_wakeup(DP1312EA_PIN_IRQ, PIN_RISING_EDGE);
if(g_nfc_irq_trigger==0){
g_nfc_irq_trigger = 1;
ql_rtos_semaphore_release(nfc_irq_sem);
}
}
void DP1312EA_Init(void)
{
ql_rtos_semaphore_create(&nfc_irq_sem, 0);
ql_gpio_init(DP1312EA_PIN_IRQ, PIN_DIRECTION_IN, PIN_PULL_PD, PIN_LEVEL_LOW);
ql_eint_register(DP1312EA_PIN_IRQ, PIN_RISING_EDGE, PIN_PULL_PD, NULL, nfc_irq_wake);
ql_eint_enable_wakeup(DP1312EA_PIN_IRQ, PIN_RISING_EDGE);
DP1312EA_Reset();
Check_Chip();
g_nfc_state = NFC_STATE_LPCD_WAIT;
g_poll_fail_count = 0;
ql_task_t nfcTaskRef = NULL;
void ql_nfc_task_thread(void *arvg)
{
s8 read_status;
while(1)
{
// ========== 状态1: 低功耗等待中断 ==========
if (g_nfc_state == NFC_STATE_LPCD_WAIT) {
DP1312EA_LPCD_Start(); // 进入低功耗检卡,等待中断
printf("NFC enter LPCD mode, waiting for IRQ...\r\n");
// 阻塞等待中断唤醒
ql_rtos_semaphore_wait(nfc_irq_sem, 0xFFFFFFFF);
DP1312EA_CLEAR_IRQ();
ql_rtos_task_sleep_ms(10); // 防抖延迟
printf("NFC IRQ wake up, switch to polling mode\r\n");
// 切换到轮询模式,重置失败计数
g_nfc_state = NFC_STATE_POLLING;
g_poll_fail_count = 0;
// 立即执行第一次读卡(不等待200ms)
continue;
}
// ========== 状态2: 200ms 轮询读卡 ==========
if (g_nfc_state == NFC_STATE_POLLING) {
// 检查是否允许读卡(业务条件)
int can_read = 0;
if (fota_start == 0 &&
get_sleep_mode() != 1 &&
get_mqtt_connect_status() == 1) {
if ((get_mqtt_connect_status() == 1 &&
vad_audio_is_recording() != 1 &&
audio_packet_type == 0 &&
tts_playing == 0 &&
xiaoice_get_play_counter() == 0) ||
g_testmode == 1) {
can_read = 1;
}
}
if (!can_read) {
// 当前业务状态不允许读卡,继续等待下次轮询
// printf("NFC business condition not met, keep polling\r\n");
ql_rtos_task_sleep_ms(300);
continue;
}
// 执行读卡
// printf("NFC polling read, fail_count=%d\r\n", g_poll_fail_count);
read_status = Test_NTAG213();
if (read_status == 0) {
// ===== 读卡成功 =====
printf("NFC read OK, continue polling\r\n");
// 直接回到 LPCD,不再轮询
g_nfc_state = NFC_STATE_LPCD_WAIT;
g_poll_fail_count = 0;
g_nfc_irq_trigger = 0;
ql_rtos_task_sleep_ms(10);
continue; // 回到 LPCD 等待
} else {
// ===== 读卡失败 =====
g_poll_fail_count++;
printf("NFC read failed, fail_count=%d\r\n", g_poll_fail_count);
if (g_poll_fail_count >= 3) {
// 连续失败 3 次,认为卡已离开
printf("NFC fail 3 times, card removed, back to LPCD\r\n");
// 清理状态,回到低功耗
g_nfc_state = NFC_STATE_LPCD_WAIT;
g_poll_fail_count = 0;
g_nfc_irq_trigger = 0;
// 短暂延迟确保硬件稳定
ql_rtos_task_sleep_ms(10);
} else {
// 继续轮询,可能卡只是位置偏移
ql_rtos_task_sleep_ms(300);
}
}
}
}
}
void DP1312EA_Init(void)
{
// 创建信号量
ql_rtos_semaphore_create(&nfc_irq_sem, 0);
// 配置中断引脚
ql_gpio_init(DP1312EA_PIN_IRQ, PIN_DIRECTION_IN, PIN_PULL_PD, PIN_LEVEL_LOW);
ql_eint_register(DP1312EA_PIN_IRQ, PIN_RISING_EDGE, PIN_PULL_PD, NULL, nfc_irq_wake);
ql_eint_enable_wakeup(DP1312EA_PIN_IRQ, PIN_RISING_EDGE);
// I2C 和芯片初始化
DP1312EA_Reset();
Check_Chip();
// 初始状态为低功耗等待
g_nfc_state = NFC_STATE_LPCD_WAIT;
g_poll_fail_count = 0;
// 创建任务
ql_task_t nfcTaskRef = NULL;
ql_rtos_task_create(&nfcTaskRef, 1024*6, 100, "nfc_Task_Ref", ql_nfc_task_thread, NULL);
}
#endif
@@ -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,14 +354,12 @@ 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);
@@ -21,7 +21,6 @@
#include "ql_ping.h"
#include "ql_nw.h"
#include "ql_atcmd.h"
#include "ql_platform.h"
#include "layer3.h"
#include "ql_uart.h"
#include "hal_api.h"
@@ -30,8 +29,6 @@
#define PCM_FRAME_LENGTH 1152
#define CLOUD_TTS_ERROR_URL_FORMAT \
"http://101.35.224.118:8080/assets/roles/banban/tts_error/tts_error%02u.mp3"
static int g_sim_ready = 0;
@@ -52,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 volatile int player_mq_couter = 0;
static int player_mq_couter = 0;
static unsigned char audio_in_task_cancel = 0;
static unsigned char xiaoice_wss_connect_status = 0;
@@ -108,11 +105,6 @@ 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播放完成
@@ -195,56 +187,6 @@ static void remove_underscores(char *str) {
*dst = '\0'; // 添加字符串结束符
}
/*
* 将后台下发的任意角色对话超时音频 URL 改为 banban 角色的 HTTP 地址,
* 并在 tts_error00~tts_error09 中随机选择一条。
*/
static int randomize_tts_error_url(char *url, size_t url_size)
{
static uint32_t random_sequence = 0;
static unsigned int last_random_index = 10;
char *file_name;
uint32_t random_value;
unsigned int random_index;
int written;
if(url == NULL || url_size == 0){
return 0;
}
file_name = strrchr(url, '/');
file_name = (file_name == NULL) ? url : file_name + 1;
if(strncmp(file_name, "tts_error", strlen("tts_error")) != 0 &&
strncmp(file_name, "tts_err", strlen("tts_err")) != 0){
return 0;
}
/*
* 某些设备上硬件随机接口可能持续返回 0,因此混合伪随机数、系统 tick
* 和递增序列;若仍与上一次相同,则向后偏移,避免连续重复播放。
*/
random_sequence++;
random_value = hal_os_get_rand();
random_value ^= ql_pseudo_random_number();
random_value ^= ql_rtos_get_systicks();
random_value ^= random_sequence * 0x9E3779B9U;
random_index = random_value % 10;
if(random_index == last_random_index){
random_index = (random_index + 1 + (random_sequence % 9)) % 10;
}
last_random_index = random_index;
written = snprintf(url, url_size, CLOUD_TTS_ERROR_URL_FORMAT, random_index);
if(written < 0 || (size_t)written >= url_size){
printf("random tts error url buffer too small\n");
return -1;
}
printf("random cloud tts error index:%02u, url:%s\n", random_index, url);
return 1;
}
void add_file_to_play_player_queue(char *path, int type)
{
ice_work_item *item = NULL;
@@ -292,13 +234,8 @@ 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)
{
if (fota_start == 1) {
printf("ignore websocket audio during OTA\r\n");
return;
}
static void onmessage_text(rws_socket socket, const char *text, const unsigned int len)
{
// printf("message(txt), %d(byte): %s ", len, text);
char url_buff[128]={0x00};
@@ -319,32 +256,14 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
}else{//TTS_URL:audio_url
wss_connect_start = 0;
char *ret_str1 = strstr(text, "https://");
char *ret_str2 = NULL;
size_t url_len = 0;
int is_tts_error_url = 0;
if(ret_str1 == NULL){
ret_str1 = strstr(text, "http://");
}
if(ret_str1 != NULL){
ret_str2 = strstr(ret_str1, ".mp3");
}
char *ret_str1= strstr(text, "http:");
char *ret_str2= strstr(text, ".mp3");
int len = 0;
if(ret_str1!=NULL && ret_str2!=NULL){
/* 提取完整的 HTTP/HTTPS MP3 URL,结尾额外包含“.mp3”四个字符 */
url_len = (size_t)(ret_str2 - ret_str1) + strlen(".mp3");
if(url_len >= sizeof(url_buff)){
printf("audio url too long:%d\n", (int)url_len);
return;
}
memcpy(url_buff, ret_str1, url_len);
url_buff[url_len] = '\0';
/* 两个角色统一使用 banban 的 HTTP 随机超时提示音 */
is_tts_error_url = randomize_tts_error_url(url_buff, sizeof(url_buff));
if(is_tts_error_url < 0){
return;
}
len = strlen(ret_str1)-strlen(ret_str2);
strncpy(url_buff, ret_str1, len);
sprintf(url_buff+len, "%s", ".mp3");
memcpy(url_buff_bak, url_buff, strlen(url_buff)+1);
@@ -363,7 +282,7 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
tts_play_status = 2;
}
if(is_tts_error_url == 1 || strstr(url_buff, "message_ok")!=NULL){
if(strstr(url_buff, "tts_error")!=NULL || strstr(url_buff, "message_ok")!=NULL){
printf("tts_error or message_ok\n");
vad_set_speech_timeout_flag(1);
}
@@ -901,49 +820,6 @@ 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;
@@ -1168,12 +1044,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
@@ -0,0 +1,7 @@
#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,6 +4,7 @@
============================================================================*/
#include "gps_app.h"
#include "../../../../ql-config/config/board_features.h"
#include <string.h>
#include "gps_debug.h"
#include "quec_hx_agps.h"
@@ -840,6 +841,10 @@ 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);
@@ -849,6 +854,7 @@ 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/new-board'
output = root / 'artifacts/wss/ecc-fix'
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/new-board/host-cert'
output = root / 'artifacts/wss/tls-speed/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
@@ -1,107 +0,0 @@
"""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')
+9 -15
View File
@@ -1,4 +1,4 @@
"""Verify the new-board WSS package, including enabled GNSS task entry points."""
"""Verify the WSS flash package against the current legacy-board build."""
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/new-board/EG800AKCN_11LG-new-board-wss-x25519.zip'
package = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else root / 'artifacts/wss/full-handshake/EG800AKCN_11LG-legacy-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,9 +33,8 @@ 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': 'New-board package awaits device testing; legacy board verified X25519 full handshake at 1455 ms on 2026-09-16',
'hardware_test': '2026-09-16 14:38 device log: NIST TLS handshake 1820 ms and HTTP 101 passed; X25519 preference awaits device test',
'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'
@@ -47,21 +46,16 @@ 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'))
task_create_address = int(re.search(r'^([0-9a-fA-F]+) T ql_rtos_task_create$', symbols, re.M).group(1), 16)
printf_address = int(re.search(r'^([0-9a-fA-F]+) T printf$', symbols, re.M).group(1), 16)
for symbol, binary, expected in [
('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')]:
('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')]:
body = subprocess.check_output([str(objdump), '-d', '--disassemble=' + symbol, str(binary)], text=True)
assert '<' + symbol + '>:' in body and expected in body, 'GNSS entry point disabled or unexpected code: ' + symbol
assert '<' + symbol + '>:' in body and expected in body and 'task_create' not in body
(output / (symbol + '.disasm.txt')).write_text(body)
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'
report['verification'] = 'PASS: ZIP CRC, current APP/kernel identity, GNSS bypass, 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'])