Author SHA1 Message Date
chenjintao d24c8e186a feat: add local random timeout prompts 2026-09-21 10:48:15 +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
26 changed files with 581 additions and 226 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 \
@@ -5,6 +5,7 @@
#include "ql_iic.h"
#include "ql_gpio.h"
#include "app_inc.h"
#include "audio_prompt.h"
#include "ql_fs.h"
#define DEFAULT_VOLUME 6
@@ -14,7 +15,6 @@
u8 g_cur_volume = 0;
u8 g_pre_volume = 0;
extern const char *mp3_file[];
extern int g_file_valid;
@@ -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;
@@ -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);
}
@@ -14,7 +14,9 @@
#include "sockets.h"
#include "layer3.h"
#include "ql_uart.h"
const char *mp3_file[] ={
#include "audio_prompt.h"
const char *mp3_file[AUDIO_PROMPT_FILE_COUNT] ={
"test.mp3",// 0
"network_connect_zh.mp3",//1 系统启动中,正在等待网络连接
"network_check.mp3",//2 网络不好 请检查网络
@@ -31,12 +33,19 @@ const char *mp3_file[] ={
"device_wake_up_zh.mp3",//13 设备已唤醒
"low_power_zh.mp3",//14 电量低
"low_power_off_zh.mp3",//15 电量严重不足,马上关机
"volume_up_zh.mp3",//16
"volume_down_zh.mp3",//17
"volume_up_zh.mp3",//16
"volume_down_zh.mp3",//17
"ai_active_zh.mp3",//18
/* 19~24:对话响应超时随机提示音,文件名与资源服务器保持一致 */
"tts_error01.mp3",//19
"tts_error02.mp3",//20
"tts_error03.mp3",//21
"tts_error04.mp3",//22
"tts_error05.mp3",//23
"tts_error06.mp3",//24
};
//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"
@@ -183,7 +192,8 @@ void app_file_check_download(void)
char filename_buff[64]={0x00};
int ret = 0, cnt = 0;
for(index=1;index<19;index++){
/* 遍历完整资源表,新增提示音不存在时会在联网后自动补下载 */
for(index=1;index<AUDIO_PROMPT_FILE_COUNT;index++){
if(index==4||index==5||index==6||index==7||index==8){
continue;
@@ -194,7 +204,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;
@@ -208,7 +218,7 @@ void app_file_check_del(void)
int index = 1;
char filename_buff[64]={0x00};
for(index=1;index<19;index++){
for(index=1;index<AUDIO_PROMPT_FILE_COUNT;index++){
if(index==4||index==5||index==6||index==7||index==8){
continue;
@@ -0,0 +1,19 @@
#ifndef _AUDIO_PROMPT_H_
#define _AUDIO_PROMPT_H_
/*
* 本地提示音索引约定。
* 现有索引保持不变,新增的 6 条对话响应超时音频从 19 开始追加,
* 避免影响项目中已经使用的其他提示音索引。
*/
#define AUDIO_PROMPT_TTS_ERROR_INDEX 9
#define AUDIO_PROMPT_TTS_ERROR_FIRST_INDEX 19
#define AUDIO_PROMPT_TTS_ERROR_LAST_INDEX 24
#define AUDIO_PROMPT_FILE_COUNT 25
extern const char *mp3_file[AUDIO_PROMPT_FILE_COUNT];
int app_play_tips_file(int index);
int app_play_random_tts_error(void);
#endif
@@ -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
@@ -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"
@@ -390,11 +391,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 +404,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 +456,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 +472,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 +503,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);
@@ -607,15 +625,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 +668,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);
@@ -1239,23 +1270,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);
}
@@ -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);
@@ -25,6 +25,7 @@
#include "ql_uart.h"
#include "hal_api.h"
#include "fvad.h"
#include "audio_prompt.h"
@@ -779,12 +780,16 @@ void sendRecordingAudioPacket(AudioPacket *packet) {
}
#endif
extern const char *mp3_file[];
int app_play_tips_file(int index)
{
int file_valid_check = 0;
/* 防止提示音索引越界访问文件名数组 */
if(index < 0 || index >= AUDIO_PROMPT_FILE_COUNT){
printf("invalid prompt index:%d\n", index);
return -3;
}
printf("xiaoice_play_index:%d\n", index);
//正在录音过程中不允许播放
@@ -820,6 +825,48 @@ int app_play_tips_file(int index)
return 0;
}
/*
* 随机播放一条对话响应超时提示音。
* 只把 LittleFS 中实际存在的文件加入候选列表,新增音频尚未下载完成时,
* 仍可回退播放原来的 tts_error.mp3,不会随机到不存在的文件。
*/
int app_play_random_tts_error(void)
{
static const int timeout_prompt_indices[] = {
AUDIO_PROMPT_TTS_ERROR_INDEX,
AUDIO_PROMPT_TTS_ERROR_FIRST_INDEX,
AUDIO_PROMPT_TTS_ERROR_FIRST_INDEX + 1,
AUDIO_PROMPT_TTS_ERROR_FIRST_INDEX + 2,
AUDIO_PROMPT_TTS_ERROR_FIRST_INDEX + 3,
AUDIO_PROMPT_TTS_ERROR_FIRST_INDEX + 4,
AUDIO_PROMPT_TTS_ERROR_LAST_INDEX
};
int valid_indices[sizeof(timeout_prompt_indices) / sizeof(timeout_prompt_indices[0])];
int valid_count = 0;
int selected_index;
int i;
char filename_buff[64] = {0x00};
for(i = 0; i < (int)(sizeof(timeout_prompt_indices) / sizeof(timeout_prompt_indices[0])); i++){
int prompt_index = timeout_prompt_indices[i];
sprintf(filename_buff, "U:/%s", mp3_file[prompt_index]);
if(ql_access(filename_buff, 0) == 0){ //检查filename_buff指向的文件是否存在
valid_indices[valid_count++] = prompt_index;
}
}
if(valid_count == 0){
printf("no local tts error prompt file\n");
return -2;
}
selected_index = valid_indices[hal_os_get_rand() % valid_count];
printf("random tts_error prompt index:%d, file:%s\n",
selected_index, mp3_file[selected_index]);
return app_play_tips_file(selected_index);
}
void app_player_thread(void *arvg)
{
ice_work_item *player_item=NULL;
@@ -1023,7 +1070,8 @@ static void check_connect_thread_entry(void *parameter)
printf("tts_error_set_speech1\n");
vad_set_speech_timeout_flag(1);
app_play_tips_file(9);
/* 对话响应超时后,从本地已有的 7 条提示音中随机播放一条 */
app_play_random_tts_error();
//talkingq_ws_send_text("REQUEST_PROMPT_SOUND:tts_error");
//改为播放本地音频 听不清 请再说一遍
}
-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'])