Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f8c4d048e |
@@ -1,168 +0,0 @@
|
||||
# OTA 下载进度上报协议说明
|
||||
|
||||
## 1. 目的
|
||||
|
||||
在当前 OTA 协议基础上增加下载进度上报。
|
||||
|
||||
本次只增加进度,不修改以下内容:
|
||||
|
||||
- OTA 下发主题不变;
|
||||
- OTA 下发报文不变;
|
||||
- OTA 响应主题不变;
|
||||
- `msg_id` 继续使用 `003`;
|
||||
- 当前的 `accepted` 和 `success` 响应保持不变;
|
||||
- 不增加任务 ID、错误码、新主题或新的 OTA 状态机。
|
||||
|
||||
设备端可以先发布。后端尚未处理进度消息时,不影响设备执行 OTA;后端后续只需增加对 `status=updating` 的解析。
|
||||
|
||||
## 2. 当前 OTA 下发协议
|
||||
|
||||
下发主题:
|
||||
|
||||
```text
|
||||
device/{imei}/command
|
||||
```
|
||||
|
||||
下发报文保持不变:
|
||||
|
||||
```json
|
||||
{
|
||||
"msg_id": "003",
|
||||
"time": "1791853200000",
|
||||
"params": {
|
||||
"url": "http://banban.api.talkingq.com:8080/path/fota.bin",
|
||||
"version": "2.0.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. OTA 上报主题
|
||||
|
||||
设备继续使用当前响应主题:
|
||||
|
||||
```text
|
||||
device/{imei}/response
|
||||
```
|
||||
|
||||
## 4. 接受 OTA
|
||||
|
||||
设备接受 OTA 命令后,继续发送当前报文:
|
||||
|
||||
```json
|
||||
{
|
||||
"msg_id": "003",
|
||||
"status": "accepted",
|
||||
"data": {
|
||||
"current_version": "2.0.3",
|
||||
"target_version": "2.0.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该报文表示设备已经接受 OTA 命令,不表示下载已经完成。
|
||||
|
||||
## 5. 新增下载进度上报
|
||||
|
||||
设备下载 FOTA 包时,每增加 10% 上报一次:
|
||||
|
||||
```json
|
||||
{
|
||||
"msg_id": "003",
|
||||
"status": "updating",
|
||||
"data": {
|
||||
"target_version": "2.0.4",
|
||||
"progress": 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `msg_id` | string | 固定为 `003` |
|
||||
| `status` | string | 下载进度消息固定为 `updating` |
|
||||
| `data.target_version` | string | 本次 OTA 目标版本 |
|
||||
| `data.progress` | number | FOTA 包下载百分比,范围为 `0~100` |
|
||||
|
||||
设备建议上报以下进度值:
|
||||
|
||||
```text
|
||||
10、20、30、40、50、60、70、80、90、100
|
||||
```
|
||||
|
||||
不需要每下载一个数据块就上报,避免产生过多 MQTT 消息。
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{"msg_id":"003","status":"updating","data":{"target_version":"2.0.4","progress":10}}
|
||||
```
|
||||
|
||||
```json
|
||||
{"msg_id":"003","status":"updating","data":{"target_version":"2.0.4","progress":50}}
|
||||
```
|
||||
|
||||
```json
|
||||
{"msg_id":"003","status":"updating","data":{"target_version":"2.0.4","progress":100}}
|
||||
```
|
||||
|
||||
注意:`progress=100` 只表示 FOTA 包下载完成,不表示新固件已经启动成功。
|
||||
|
||||
## 6. OTA 成功
|
||||
|
||||
设备下载、校验、重启并进入新 APP 后,继续发送当前成功报文:
|
||||
|
||||
```json
|
||||
{
|
||||
"msg_id": "003",
|
||||
"status": "success",
|
||||
"data": {
|
||||
"new_version": "2.0.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
后端只有收到 `status=success` 时,才能将本次 OTA 判断为最终成功。
|
||||
|
||||
## 7. 后端需要修改的内容
|
||||
|
||||
后端只需要在现有 `003` 响应解析中增加一种状态:
|
||||
|
||||
```text
|
||||
status = updating
|
||||
```
|
||||
|
||||
解析逻辑:
|
||||
|
||||
1. 收到 `accepted`:记录设备已接受 OTA。
|
||||
2. 收到 `updating`:读取 `data.progress` 并更新下载进度。
|
||||
3. 收到 `success`:记录设备已经进入目标版本,OTA 最终成功。
|
||||
|
||||
MQTT 当前使用 QoS 1,同一条进度消息可能重复到达。后端收到重复进度时直接覆盖原进度即可,不要重复创建 OTA 任务。
|
||||
|
||||
如果后端收到的进度小于已经保存的进度,可以忽略较小的进度,避免进度倒退。
|
||||
|
||||
## 8. 设备端实现约定
|
||||
|
||||
1. 当前 `accepted` 和 `success` 报文不修改。
|
||||
2. 下载任务每达到一个新的 10% 档位,上报一次 `updating`。
|
||||
3. 进度上报仍发送到当前 `response` 主题。
|
||||
4. 进度上报失败只记录日志,不中止 OTA 下载。
|
||||
5. 下载完成后上报 `progress=100`。
|
||||
6. `progress=100` 后设备继续执行文件校验和重启。
|
||||
7. 新 APP 启动后仍使用当前 `success` 报文确认最终成功。
|
||||
|
||||
## 9. 完整交互示例
|
||||
|
||||
```text
|
||||
后端 -> 设备:003 OTA 命令
|
||||
设备 -> 后端:003 accepted
|
||||
设备 -> 后端:003 updating progress=10
|
||||
设备 -> 后端:003 updating progress=20
|
||||
设备 -> 后端:003 updating progress=30
|
||||
...
|
||||
设备 -> 后端:003 updating progress=100
|
||||
设备重启并安装新 APP
|
||||
设备 -> 后端:003 success
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
# APP 启动校时
|
||||
|
||||
PDP 首次激活后,APP 通过现有 SDK 的 `ql_nw_get_nitz_time_info` 获取运营商 NITZ 快照。按 UTC 解析日期,用 `ql_localtime_r` 转换到当前 PMIC 时区,再调用 `ql_rtc_set_time` 写入 APP RTC。读取 RTC 和 `time(NULL)`,两项误差都不超过 3 秒才打印 `[APP-TIME] PASS`。
|
||||
|
||||
每次开机只启动一次;读取失败、日期无效或查询耗时超过 2 秒时,间隔 5 秒重试,最多 3 次。选中首个有效快照后最多写入一次,即使读回失败也不重复写入。保留原有 NITZ 开关,不增加网络域名或端口,不修改内核,不依赖 NTP、AT 命令、SYS 探测或额外内核诊断。支持现有内核导出接口下的纯 APP OTA。
|
||||
|
||||
限制:SDK 返回快照未提供接收时间,读回验证只证明 APP 时钟与快照一致,不能证明快照新鲜度。本方案保留实机验证通过的首次 PDP 后快照策略,不扩展为持续校时。首次 WSS 建立仍需等待实际 TLS 握手验证;本次修复不修改连接调度或对话回复超时。
|
||||
|
||||
验证:`DEVELOPER_DIR=/Library/Developer/CommandLineTools python3 tools/test_app_time_calibrate.py`,实际 C 源码通过 26 组行为场景和日期解析验证(ASan/UBSan),覆盖 UTC/时区、无效和静态快照、重试、tick 回绕、RTC 写入/读回失败。迁移依据为 test/time-fix 的 884ab54;仅调整模块命名与日志,不改变已验证校时算法。2026-10-26 17:29:27 完整串口日志中,原版本校时读回误差为 0 秒,WSS 证书验证与 HTTP 101 成功,第二次对话收到 TTS。
|
||||
@@ -1,64 +0,0 @@
|
||||
# 对话无回复诊断日志
|
||||
|
||||
设备端新增四类日志,按 `round`、`session`、单调运行时间关联。诊断不调整对话协议、VAD 阈值或回复等待时限。
|
||||
|
||||
| 标记 | 内容 | 可以缩小的范围 |
|
||||
| --- | --- | --- |
|
||||
| `MIC-DIAG` | 录音打开结果及句柄、读取返回值/字节数、全零帧、峰值/平均绝对幅值、录音事件、PCM 缓冲写入丢失 | 区分未取得 PCM、全零输入、已有音频但没有通过 VAD、音频交给编码器前丢失 |
|
||||
| `VOICE-TX` | 认证消息入队结果;START/END 的序号、类型、字节数、返回值和入队耗时;音频入队失败 | 区分请求未入队、内存分配失败、正常提交发送队列 |
|
||||
| `WSS-IO` | 实际 TLS 写入结果、累计写入次数和字节;TLS 错误码、部分写入进度和重试;实际接收字节;关闭时的累计收发及未消费接收字节 | 区分入队后未完成写入、对端关闭、收到字节但应用层未获得完整消息 |
|
||||
| `VOICE-RX` | 接收文本长度、前 192 字节、当前会话、是否通过会话准入检查;二进制消息计数 | 显示 START_ACK/END_ACK、未知回复、被准入检查丢弃的消息。二进制消息仍沿用原有忽略行为 |
|
||||
| `VOICE-DIAG` | START/END 入队后的摘要、超时断开前和断开回调时的摘要 | 汇总入队成功/失败数、音频载荷字节、文本/二进制消息数、ACK 次数、末次收包距今时长和回复等待时长 |
|
||||
|
||||
拾音统计按一次录音累计,每秒及停止时输出。`peak`/`mean_abs` 是 16 位有符号 PCM 的绝对幅值,不是分贝;`zero` 统计有效读取长度内全部为零的帧。`short` 包括零长度以及所有非 640 字节的读取。PCM 写入不足或失败计入 `ring_drops`,同时记录丢失字节数。录音 tick 回调不打印,其他重复事件限频。
|
||||
|
||||
`VOICE-TX ret=0` 只代表入队成功。`queue_bytes` 只计成功入队的音频载荷,不含自定义包头、WebSocket 帧头、认证和控制消息。`WSS-IO tx_bytes` 是传给传输层并成功返回的明文字节数,含握手/帧头,不能与 `queue_bytes` 直接比较;TLS 失败时已写出的部分字节由 `tls_write_failed`/`tls_write_timeout` 的 `sent` 单独报告。正常写入首条和每 50 次打印一次,实际接收前 20 次及每 50 次打印,断开时输出累计值。`tick`/`elapsed_ticks` 使用 RTOS tick,其他带 `ms` 的字段使用单调毫秒。
|
||||
|
||||
`start_ack`/`end_ack` 按收到的文本子串统计,包含被准入检查拒绝的消息;它们不是对当前 session 的服务端确认保证。应结合 `VOICE-RX` 原文中的会话标识判断归属。若服务端协议不发送 ACK,ACK 为零本身不代表失败。`accepted=1` 表示通过外层会话准入检查,后续仍可能因 OTA、打断状态、URL 或 session 校验而未播放。文本仅保留前 192 字节,原有错误消息完整分块日志继续保留。
|
||||
|
||||
复现时保留开机到失败后第二次对话结束的完整串口日志。推荐按以下顺序判断:
|
||||
|
||||
1. `MIC-DIAG` 检查读取长度、返回值和电平,结合既有 FVAD 语音确认日志判断拾音与 VAD。
|
||||
2. `VOICE-TX` 检查 START、音频、END 入队;`queue_fail` 和 `ring_drops` 应为零。
|
||||
3. `WSS-IO` 检查实际写入是否失败,关闭时是否有接收字节和残留未消费数据。
|
||||
4. `VOICE-RX` 检查 ACK、未知错误文本、被拒绝的回复或二进制数据。
|
||||
5. `timeout-before-disconnect` 检查 `wait_ms`。`wait=2` 是等待回复,`wait=1` 是无语音,`wait=3` 是 END 入队失败。`response` 表示现有业务逻辑已接收可用音频 URL,不等同于收到任意文本或 TTS_START。
|
||||
|
||||
即使 TLS 写入成功,也不能证明服务器应用收到完整录音。若已经看到归属正确的 END_ACK,却没有后续 TTS,优先按 session 查服务端识别和生成链路;若没有 ACK,也需要结合服务端接收记录判断。设备单侧日志不能完全区分服务端未处理、网络中断和回复延迟。
|
||||
|
||||
本机验证:`python3 tools/test_voice_diag.py`、`python3 tools/test_conversation_timeout.py`、`python3 tools/test_wss_io.py`、`python3 tools/test_wss_receive.py`。诊断测试直接编译实际函数并注入录音、缓冲区、队列及收包结果;不替代目标固件构建及设备实测。
|
||||
|
||||
## GNSS 日志开关
|
||||
|
||||
`inc/app_gnss_log.h` 中的 `APP_GNSS_LOG_ENABLED` 默认是 `0`,关闭应用侧全部 GNSS/NMEA 原始输出、解析失败、模式切换、卫星搜索、坐标打印和 GPS 查询提示。需要恢复时可在编译参数中设置 `-DAPP_GNSS_LOG_ENABLED=1`。语音与 NITZ 时间校准诊断继续输出。
|
||||
|
||||
GNSS 数据解析、定位上报和硬件开关流程继续运行。原先放在打印参数内的四处 `ql_gnss_close()` 已分离为独立操作,避免禁用日志时跳过关闭定位。`python3 tools/test_gnss_logging.py` 在日志开启和关闭两种配置下验证真实解析代码、无卫星退出、卫星不足退出、搜索超时退出及 FAST 切换 SLOW 的关闭路径。
|
||||
|
||||
## 连接失败与提示音诊断
|
||||
|
||||
`WSS-DIAG` 使用 `stage` 区分 TLS 握手、TLS 读取、WebSocket 升级。日志记录阶段、实际耗时、超时配置、错误码和收包情况,不输出原始响应头、Cookie 或音频内容。TLS 握手仍是 20 秒总窗口,WebSocket 升级仍是 10 秒窗口,HTTP select 等待仍是 2 秒。
|
||||
|
||||
| 日志字段 | 含义和排查方向 |
|
||||
| --- | --- |
|
||||
| `stage=tls_handshake result=timeout phase=server hello` | TLS 等待服务器 Hello 超时,不能据此判断证书错误。结合 `last_step`、`want_read`/`want_write`、`phase_ms`、`calls_ms` 判断等待和本地计算耗时 |
|
||||
| `stage=tls_connect failed_at=… verify_flags=…` | 记录 TLS 初始化/建连的失败步骤和证书校验标志;校验标志仅在已进行证书校验时有解释意义 |
|
||||
| `stage=tls_read reason=peer_close_notify` | 对端发送 TLS 关闭通知;仅证明关闭由对端通知,不证明为何关闭 |
|
||||
| `stage=tls_read reason=tls_eof/tls_read_timeout/tls_read_error` | 分别表示读取返回 0、TLS 读取超时、其他读取错误;保留原始十进制和十六进制返回值 |
|
||||
| `stage=ws_upgrade reason=no_response_timeout` | 升级等待超时,缓冲中没有响应数据;对照 `rx_bytes=0`,服务端需核查请求是否到达及是否返回 101 |
|
||||
| `stage=ws_upgrade reason=incomplete_headers_timeout` | 收到部分数据,但在窗口内未收到完整 HTTP 响应头 |
|
||||
| `stage=ws_upgrade reason=http_rejected http=…` | 收到了完整响应头及非 101 状态;按实际 HTTP 状态检查代理或后端 |
|
||||
| `stage=ws_upgrade reason=invalid_upgrade_response` | 收到非法或不符合 WebSocket 校验的响应;例如 101 响应的 Accept/Upgrade/Connection 不符合要求,或响应格式错误;当前不进一步细分具体头字段 |
|
||||
| `stage=ws_upgrade reason=transport_error` | 等待升级期间底层连接出错,结合紧邻的 `tls_read` 日志及断开回调的具体错误描述 |
|
||||
| `HTTP-DIAG stage=tcp_connect/response_header/response_body` | HTTP 在建连、响应头或响应体等待期间失败;`timeout` 表示 select 返回 0,`select_error` 表示负值,并记录立即保存的 errno;超时的 errno 字段为 0,不能用历史 errno 推断原因 |
|
||||
| `NET-GATE leave recording=… pending=… mqtt=… action=…` | 记录留言键为什么被拦截,区分正在录音、等待留言请求回复和 MQTT 离线。忙碌分支当前仍播放原有网络提示并清除 pending,此次仅增加诊断 |
|
||||
| `NET-GATE tip=network_ok reason=mqtt_connected voice_service=not_checked` | “已连上网络”仅由 MQTT 连通触发,不表示 TLS/WebSocket/语音业务已验证 |
|
||||
|
||||
例如此次无响应升级会输出类似以下摘要(示例,并非当前固件已有的历史日志):
|
||||
|
||||
```text
|
||||
[WSS-DIAG] stage=ws_upgrade result=failed reason=no_response_timeout http=0 elapsed_ms=10000 timeout_ms=10000 rx_bytes=0 buffered=0 header_bytes=0 parser=0 polls=...
|
||||
```
|
||||
|
||||
`http=0` 表示尚未解析到有效状态码,不是服务器返回的 HTTP 状态码。成功 TLS 写入只证明本地传输层接受了数据,设备端仍不能独自证明请求到达服务器或定位中间链路丢包。诊断不会改变现有提示音、重试或业务状态机。
|
||||
|
||||
验证:`python3 tools/test_wss_receive.py` 覆盖完整/分段 101、零响应超时、部分响应头超时、503 拒绝、非法 Accept、对端关闭、TLS 读取超时等分支;`python3 tools/test_wss_handshake.py` 验证真实握手函数的超时/失败摘要及原有时限、重试和 tick 回绕。以上为主机侧函数测试,需重新构建并烧录目标固件后采集新日志。
|
||||
@@ -82,7 +82,6 @@ SRC_FILES+= \
|
||||
DP1312EA.c \
|
||||
mqtt.c \
|
||||
mqtt_connect_diag.c \
|
||||
app_time_calibrate.c \
|
||||
vad_det.c \
|
||||
sleep_wake.c \
|
||||
battery.c \
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include "ql_rtos.h"
|
||||
#include "ql_func.h"
|
||||
#include "ql_nw.h"
|
||||
#include "ql_rtc.h"
|
||||
#include "app_time_calibrate.h"
|
||||
|
||||
/* SDK wrapper uses the same PMIC timezone as time(); no libc TZ assumptions. */
|
||||
extern struct tm *ql_localtime_r(const time_t *, struct tm *);
|
||||
#define MIN_EPOCH 1704067200LL
|
||||
#define MAX_EPOCH 2147483647LL
|
||||
#define ATTEMPTS 3
|
||||
|
||||
static ql_task_t calibration_task;
|
||||
static int calibration_started;
|
||||
|
||||
typedef struct {
|
||||
time_t utc;
|
||||
unsigned int ticks;
|
||||
int zone_quarters;
|
||||
} nitz_sample;
|
||||
|
||||
static int leap(int year)
|
||||
{
|
||||
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
||||
}
|
||||
|
||||
/* Explicit Gregorian conversion: no mktime() or APP clock dependency. */
|
||||
static int calendar_epoch(int year, int month, int day, int hour, int minute,
|
||||
int second, long long *result)
|
||||
{
|
||||
static const int days[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||
long long total = 0;
|
||||
int y, m;
|
||||
if (year < 1970 || year > 2038 || month < 1 || month > 12 ||
|
||||
day < 1 || day > days[month - 1] + (month == 2 && leap(year)) ||
|
||||
hour < 0 || hour > 23 || minute < 0 || minute > 59 ||
|
||||
second < 0 || second > 59) return -1;
|
||||
for (y = 1970; y < year; ++y) total += 365 + leap(y);
|
||||
for (m = 1; m < month; ++m) total += days[m - 1] + (m == 2 && leap(year));
|
||||
*result = ((total + day - 1) * 24 + hour) * 3600 + minute * 60 + second;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int two_digits(const char *s)
|
||||
{
|
||||
if (s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9') return -1;
|
||||
return (s[0] - '0') * 10 + s[1] - '0';
|
||||
}
|
||||
|
||||
/* Accept actual "YY/MM/DD HH:MM:SS 32 0", documented signed format, and compact
|
||||
* "YY/MM/DD,HH:MM:SS+32". NITZ UT is UTC; zone is metadata, not subtracted.
|
||||
* abs_time/leap_sec have insufficient documented semantics to use as epoch. */
|
||||
static int parse_nitz(const char *s, time_t *epoch, int *zone)
|
||||
{
|
||||
size_t n = strlen(s), pos = 17;
|
||||
int yy, mo, dd, hh, mm, ss, q, dst, negative = 0;
|
||||
size_t start;
|
||||
long long value;
|
||||
if (n < 20 || s[2] != '/' || s[5] != '/' ||
|
||||
(s[8] != ' ' && s[8] != ',') || s[11] != ':' || s[14] != ':') return -1;
|
||||
yy = two_digits(s); mo = two_digits(s + 3); dd = two_digits(s + 6);
|
||||
hh = two_digits(s + 9); mm = two_digits(s + 12); ss = two_digits(s + 15);
|
||||
if (yy < 0 || calendar_epoch(2000 + yy, mo, dd, hh, mm, ss, &value) != 0 ||
|
||||
value < MIN_EPOCH || value > MAX_EPOCH) return -2;
|
||||
/* Actual SDK output is also "26/10/26 08:00:10 32 0". */
|
||||
if (s[pos] == ' ') ++pos;
|
||||
else if (s[pos] != '+' && s[pos] != '-') return -3;
|
||||
if (s[pos] == '+' || s[pos] == '-') negative = s[pos++] == '-';
|
||||
start = pos;
|
||||
q = 0;
|
||||
while (pos < n && s[pos] >= '0' && s[pos] <= '9' && pos - start < 2)
|
||||
q = q * 10 + s[pos++] - '0';
|
||||
if (pos == start || q > 56 || (negative && q > 48)) return -3;
|
||||
*zone = negative ? -q : q;
|
||||
if (pos < n) {
|
||||
if (s[pos++] != ' ') return -4;
|
||||
start = pos;
|
||||
dst = 0;
|
||||
while (pos < n && s[pos] >= '0' && s[pos] <= '9' && pos - start < 2)
|
||||
dst = dst * 10 + s[pos++] - '0';
|
||||
if (pos == start || pos != n || dst > 2) return -4;
|
||||
}
|
||||
*epoch = (time_t)value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_sample(int attempt, int index, nitz_sample *sample)
|
||||
{
|
||||
QL_NW_NITZ_TIME_INFO_T info;
|
||||
unsigned int began = ql_rtos_get_systicks();
|
||||
int rc, parsed;
|
||||
memset(&info, 0, sizeof(info));
|
||||
rc = ql_nw_get_nitz_time_info(&info);
|
||||
sample->ticks = ql_rtos_get_systicks();
|
||||
printf("[APP-TIME] sample attempt=%d index=%d rc=%d raw=\"%.*s\" "
|
||||
"abs_time=%lu leap_sec=%u query_ms=%lu\r\n", attempt, index, rc,
|
||||
(int)sizeof(info.nitz_time), info.nitz_time, info.abs_time,
|
||||
(unsigned int)info.leap_sec,
|
||||
(unsigned long)(sample->ticks - began) * 5UL);
|
||||
if (rc != QL_NW_SUCCESS ||
|
||||
memchr(info.nitz_time, '\0', sizeof(info.nitz_time)) == NULL) return -1;
|
||||
parsed = parse_nitz(info.nitz_time, &sample->utc, &sample->zone_quarters);
|
||||
if (parsed != 0) {
|
||||
printf("[APP-TIME] rejected stage=parse reason=%d\r\n", parsed);
|
||||
return -1;
|
||||
}
|
||||
/* Bound query delay; this does not establish the age of a cached NITZ. */
|
||||
if (sample->ticks - began > 400U) {
|
||||
printf("[APP-TIME] rejected stage=slow_query rtc_not_written=1\r\n");
|
||||
return -1;
|
||||
}
|
||||
printf("[APP-TIME] parsed utc=%ld zone_quarters=%d zone_minutes=%d\r\n",
|
||||
(long)sample->utc, sample->zone_quarters, sample->zone_quarters * 15);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_and_verify(const nitz_sample *reference)
|
||||
{
|
||||
struct tm local;
|
||||
ql_rtc_time_t target, actual;
|
||||
time_t epoch, before = time(NULL), after;
|
||||
long long expected, local_epoch, delta, actual_local = 0;
|
||||
int rc, read_rc, calendar_ok;
|
||||
expected = (long long)reference->utc +
|
||||
(ql_rtos_get_systicks() - reference->ticks) / 200U;
|
||||
if (expected > MAX_EPOCH) {
|
||||
printf("[APP-TIME] FAIL stage=epoch_range\r\n");
|
||||
return -1;
|
||||
}
|
||||
epoch = (time_t)expected;
|
||||
memset(&local, 0, sizeof(local));
|
||||
if (ql_localtime_r(&epoch, &local) == NULL ||
|
||||
calendar_epoch(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
|
||||
local.tm_hour, local.tm_min, local.tm_sec, &local_epoch) != 0 ||
|
||||
local_epoch < MIN_EPOCH || local_epoch > MAX_EPOCH ||
|
||||
local_epoch - expected < -43200 || local_epoch - expected > 50400) {
|
||||
printf("[APP-TIME] FAIL stage=local_calendar\r\n");
|
||||
return -1;
|
||||
}
|
||||
memset(&target, 0, sizeof(target));
|
||||
target.tm_year = local.tm_year + 1900;
|
||||
target.tm_mon = local.tm_mon + 1;
|
||||
target.tm_mday = local.tm_mday;
|
||||
target.tm_hour = local.tm_hour;
|
||||
target.tm_min = local.tm_min;
|
||||
target.tm_sec = local.tm_sec;
|
||||
target.tm_wday = local.tm_wday;
|
||||
printf("[APP-TIME] set_begin app_before=%ld expected_utc=%ld "
|
||||
"pmic_zone_minutes=%ld app_calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
||||
(long)before, (long)epoch, (long)((local_epoch - expected) / 60),
|
||||
target.tm_year, target.tm_mon, target.tm_mday,
|
||||
target.tm_hour, target.tm_min, target.tm_sec);
|
||||
rc = ql_rtc_set_time(&target);
|
||||
memset(&actual, 0, sizeof(actual));
|
||||
read_rc = ql_rtc_get_time(&actual);
|
||||
after = time(NULL);
|
||||
expected = (long long)reference->utc +
|
||||
(ql_rtos_get_systicks() - reference->ticks) / 200U;
|
||||
delta = (long long)after - expected;
|
||||
calendar_ok = calendar_epoch(actual.tm_year, actual.tm_mon, actual.tm_mday,
|
||||
actual.tm_hour, actual.tm_min, actual.tm_sec,
|
||||
&actual_local) == 0;
|
||||
actual_local -= local_epoch + (expected - (long long)epoch);
|
||||
printf("[APP-TIME] readback set_rc=%d rtc_rc=%d app_after=%ld "
|
||||
"expected_utc=%lld delta_s=%lld calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
||||
rc, read_rc, (long)after, expected, delta, actual.tm_year, actual.tm_mon,
|
||||
actual.tm_mday, actual.tm_hour, actual.tm_min, actual.tm_sec);
|
||||
if (rc != 0 || read_rc != 0 || after < MIN_EPOCH ||
|
||||
delta < -3 || delta > 3 || !calendar_ok ||
|
||||
actual_local < -3 || actual_local > 3) {
|
||||
printf("[APP-TIME] FAIL stage=app_clock_verification\r\n");
|
||||
return -1;
|
||||
}
|
||||
printf("[APP-TIME] PASS source=nitz app_utc=verified "
|
||||
"wss_requires_separate_handshake_check=1\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void time_calibrate_worker(void *arg)
|
||||
{
|
||||
nitz_sample sample;
|
||||
int attempt;
|
||||
(void)arg;
|
||||
printf("[APP-TIME] begin version=1 source=operator_nitz ntp=off "
|
||||
"policy=first_valid_after_pdp sys_probe=off\r\n");
|
||||
if (!m_get_api_ptr || !m_get_api_ptr("ql_nw_get_nitz_time_info") ||
|
||||
!m_get_api_ptr("ql_localtime_r") || !m_get_api_ptr("ql_rtc_set_time") ||
|
||||
!m_get_api_ptr("ql_rtc_get_time")) {
|
||||
printf("[APP-TIME] FAIL stage=sdk_api_unavailable\r\n");
|
||||
goto done;
|
||||
}
|
||||
/* Use the first valid operator snapshot promptly after
|
||||
* PDP activation. A snapshot need not advance between API reads. Its actual
|
||||
* network reception time is unknown; readback alone cannot establish age. */
|
||||
for (attempt = 1; attempt <= ATTEMPTS; ++attempt) {
|
||||
if (get_sample(attempt, 1, &sample) == 0) {
|
||||
printf("[APP-TIME] snapshot_selected attempt=%d utc=%ld "
|
||||
"age=unknown first_valid_after_pdp=1 max_rtc_writes=1\r\n",
|
||||
attempt, (long)sample.utc);
|
||||
write_and_verify(&sample);
|
||||
goto done; /* No repeated write, even on setter/readback failure. */
|
||||
}
|
||||
if (attempt < ATTEMPTS) ql_rtos_task_sleep_ms(5000);
|
||||
}
|
||||
printf("[APP-TIME] FAIL stage=no_valid_nitz rtc_not_written=1\r\n");
|
||||
done:
|
||||
printf("[APP-TIME] finished once_per_boot=1\r\n");
|
||||
ql_rtos_task_delete(NULL);
|
||||
}
|
||||
|
||||
void app_time_calibrate_start(void)
|
||||
{
|
||||
int rc;
|
||||
if (calibration_started) return; /* PDP management is the sole caller. */
|
||||
calibration_started = 1;
|
||||
rc = ql_rtos_task_create(&calibration_task, 6 * 1024, 110, "app_time_calibrate",
|
||||
time_calibrate_worker, NULL);
|
||||
if (rc != 0) {
|
||||
calibration_started = 0;
|
||||
printf("[APP-TIME] FAIL stage=create_task rc=%d\r\n", rc);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "sockets.h"
|
||||
#include "netdb.h"
|
||||
#include "app_inc.h"
|
||||
#include "app_gnss_log.h"
|
||||
|
||||
// 新增宏定义(原代码中未定义)
|
||||
#define GNSS_FAST_MODE_HOLD_TIME (5 * 60 * 1000) // FAST模式持续5分钟后切SLOW
|
||||
@@ -212,7 +211,10 @@ void quec_gnss_callback(char *data, int len)
|
||||
char nmea_buffer[512];
|
||||
int fix_status = 0;
|
||||
|
||||
APP_GNSS_LOG("%.*s", len, data);
|
||||
// 打印原始 NMEA 数据
|
||||
for (i = 0; i < len; i++) {
|
||||
printf("%c", data[i]);
|
||||
}
|
||||
|
||||
// 在 data 中定位 GGA 句子的起止位置
|
||||
// GGA 格式: $xxGGA,...,*CS\r\n
|
||||
@@ -265,7 +267,7 @@ void quec_gnss_callback(char *data, int len)
|
||||
g_gps_location.is_valid = (fix_status >= 1) ? 1 : 0;
|
||||
} else {
|
||||
g_gps_location.is_valid = 0;
|
||||
APP_GNSS_LOG("\n[GNSS Parse failed] FS=%d, %s\n",
|
||||
printf("\n[GNSS Parse failed] FS=%d, %s\n",
|
||||
fix_status,
|
||||
(fix_status == 0) ? "No Fix (Invalid)" : "Format error");
|
||||
}
|
||||
@@ -319,7 +321,7 @@ void quec_gnss_callback(char *data, int len)
|
||||
void set_gnss_mode(gnss_mode_t mode)
|
||||
{
|
||||
g_gnss_mode = mode;
|
||||
APP_GNSS_LOG("[GNSSMode] Set to %s Mode\n",
|
||||
printf("[GNSSMode] Set to %s Mode\n",
|
||||
(mode == GNSS_MODE_FAST) ? "Fast (30s)" : "Slow (5min)");
|
||||
}
|
||||
|
||||
@@ -351,11 +353,11 @@ gps_location_info_t* get_gps_location_info(void)
|
||||
void print_gps_location_info(void)
|
||||
{
|
||||
if (g_gps_location.is_valid) {
|
||||
APP_GNSS_LOG("[GPSinfo] Latitude: %.8f, Longitude: %.8f\n",
|
||||
printf("[GPSinfo] Latitude: %.8f, Longitude: %.8f\n",
|
||||
g_gps_location.latitude, g_gps_location.longitude);
|
||||
APP_GNSS_LOG("[GPSinfo] rawNMEA: %s\n", g_gps_location.raw_nmea);
|
||||
printf("[GPSinfo] rawNMEA: %s\n", g_gps_location.raw_nmea);
|
||||
} else {
|
||||
APP_GNSS_LOG("[GPSinfo] No valid position data\n");
|
||||
printf("[GPSinfo] No valid position data\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,29 +395,26 @@ static void quec_gnss_test(void * argv)
|
||||
/* ========== FAST MODE ========== */
|
||||
if (fast_mode_start_time == 0) {
|
||||
fast_mode_start_time = hal_os_up_time_ms();
|
||||
APP_GNSS_LOG("[GNSS] Enter FAST mode\n");
|
||||
printf("[GNSS] Enter FAST mode\n");
|
||||
}
|
||||
|
||||
if (!gnss_is_open) {
|
||||
gnss_is_open = 1;
|
||||
app_set_app_operation(1);
|
||||
ql_gnss_open(quec_gnss_callback);
|
||||
APP_GNSS_LOG("[GNSS] FAST: GPS OPEN\n");
|
||||
printf("[GNSS] FAST: GPS OPEN\n");
|
||||
}
|
||||
|
||||
// 5分钟超时切SLOW
|
||||
if (hal_os_up_time_ms() - fast_mode_start_time >= GNSS_FAST_MODE_HOLD_TIME) {
|
||||
APP_GNSS_LOG("[GNSS] FAST 5min timeout -> SLOW\n");
|
||||
printf("[GNSS] FAST 5min timeout -> SLOW\n");
|
||||
g_gnss_mode = GNSS_MODE_SLOW;
|
||||
fast_mode_start_time = 0;
|
||||
slow_mode_start_time = 0;
|
||||
slow_next_wake_time = 0;
|
||||
|
||||
gnss_is_open = 0;
|
||||
|
||||
int close_ret = ql_gnss_close();
|
||||
(void)close_ret;
|
||||
APP_GNSS_LOG("[GNSS] SLOW: GPS CLOSED ret:%d\n", close_ret);
|
||||
printf("[GNSS] SLOW: GPS CLOSED ret:%d\n", ql_gnss_close());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -427,12 +426,12 @@ static void quec_gnss_test(void * argv)
|
||||
if (slow_mode_start_time == 0) {
|
||||
slow_mode_start_time = now;
|
||||
slow_next_wake_time = now; // 立即尝试第一次定位
|
||||
APP_GNSS_LOG("[GNSS] Enter SLOW mode (fixed 15min interval)\n");
|
||||
printf("[GNSS] Enter SLOW mode (fixed 15min interval)\n");
|
||||
}
|
||||
|
||||
// 检查是否到达唤醒时间
|
||||
if (now < slow_next_wake_time) {
|
||||
APP_GNSS_LOG("[GNSS] SLOW: next wake in %d s\n",
|
||||
printf("[GNSS] SLOW: next wake in %d s\n",
|
||||
(slow_next_wake_time - now) / 1000);
|
||||
}
|
||||
else if (!gnss_is_open) {
|
||||
@@ -445,7 +444,7 @@ static void quec_gnss_test(void * argv)
|
||||
app_set_app_operation(1);
|
||||
ql_gnss_open(quec_gnss_callback);
|
||||
|
||||
APP_GNSS_LOG("[GNSS] SLOW: GPS OPEN, search start\n");
|
||||
printf("[GNSS] SLOW: GPS OPEN, search start\n");
|
||||
|
||||
/* ---- 第一阶段:60秒卫星检测 ---- */
|
||||
int sat_found = 0;
|
||||
@@ -455,7 +454,7 @@ static void quec_gnss_test(void * argv)
|
||||
elapsed++;
|
||||
if (g_gnss_sat_count > 1) {
|
||||
sat_found = 1;
|
||||
APP_GNSS_LOG("[GNSS] SLOW: Sat found! count=%d, after %ds\n",
|
||||
printf("[GNSS] SLOW: Sat found! count=%d, after %ds\n",
|
||||
g_gnss_sat_count, elapsed);
|
||||
break;
|
||||
}
|
||||
@@ -463,13 +462,10 @@ static void quec_gnss_test(void * argv)
|
||||
|
||||
if (!sat_found) {
|
||||
// 60秒无卫星 → 立即关闭,等待15分钟
|
||||
|
||||
int close_ret = ql_gnss_close();
|
||||
(void)close_ret;
|
||||
APP_GNSS_LOG("[GNSS] SLOW: No satellite in 30s, stop ret:%d.\n", close_ret);
|
||||
printf("[GNSS] SLOW: No satellite in 30s, stop ret:%d.\n", ql_gnss_close());
|
||||
gnss_is_open = 0;
|
||||
slow_next_wake_time = search_start_time + (GNSS_SEARCH_INTERVAL * 1000);
|
||||
APP_GNSS_LOG("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
|
||||
printf("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -482,20 +478,17 @@ static void quec_gnss_test(void * argv)
|
||||
|
||||
// 1.5分钟时刻检查卫星数
|
||||
int sat_count_now = g_gnss_sat_count;
|
||||
APP_GNSS_LOG("[GNSS] SLOW: 1min check, satellite count = %d\n", sat_count_now);
|
||||
printf("[GNSS] SLOW: 1min check, satellite count = %d\n", sat_count_now);
|
||||
if (sat_count_now < GNSS_SAT_MIN_COUNT) {
|
||||
|
||||
int close_ret = ql_gnss_close();
|
||||
(void)close_ret;
|
||||
APP_GNSS_LOG("[GNSS] SLOW: Sat count < %d, stop search ret:%d.\n", GNSS_SAT_MIN_COUNT, close_ret);
|
||||
printf("[GNSS] SLOW: Sat count < %d, stop search ret:%d.\n", GNSS_SAT_MIN_COUNT,ql_gnss_close());
|
||||
gnss_is_open = 0;
|
||||
slow_next_wake_time = search_start_time + (GNSS_SEARCH_INTERVAL * 1000);
|
||||
APP_GNSS_LOG("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
|
||||
printf("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- 第三阶段:卫星足够,继续定位,但总时间不超过3分钟 ---- */
|
||||
APP_GNSS_LOG("[GNSS] SLOW: Sat count OK, continue for fix (max %ds total)\n",
|
||||
printf("[GNSS] SLOW: Sat count OK, continue for fix (max %ds total)\n",
|
||||
GNSS_MAX_SEARCH_TIME);
|
||||
|
||||
// 继续等待直到总时间达到 GNSS_MAX_SEARCH_TIME (180秒)
|
||||
@@ -505,30 +498,28 @@ static void quec_gnss_test(void * argv)
|
||||
|
||||
// 如果获得有效定位,打印提示(但继续等待至3分钟)
|
||||
if (g_gps_location.is_valid) {
|
||||
APP_GNSS_LOG("[GNSS] SLOW: Fix obtained at %ds!\n", elapsed);
|
||||
printf("[GNSS] SLOW: Fix obtained at %ds!\n", elapsed);
|
||||
print_gps_location_info();
|
||||
// 若希望在定位成功后立即关闭,可在此 break,但此处按“最长3分钟”处理
|
||||
}
|
||||
}
|
||||
|
||||
// 3分钟时间到,关闭GPS
|
||||
APP_GNSS_LOG("[GNSS] SLOW: Max search time reached, stop.\n");
|
||||
printf("[GNSS] SLOW: Max search time reached, stop.\n");
|
||||
if (g_gps_location.is_valid) {
|
||||
APP_GNSS_LOG("[GNSS] SLOW: Final valid position:\n");
|
||||
printf("[GNSS] SLOW: Final valid position:\n");
|
||||
print_gps_location_info();
|
||||
} else {
|
||||
APP_GNSS_LOG("[GNSS] SLOW: No valid fix within 3min.\n");
|
||||
printf("[GNSS] SLOW: No valid fix within 3min.\n");
|
||||
}
|
||||
|
||||
int close_ret = ql_gnss_close();
|
||||
(void)close_ret;
|
||||
APP_GNSS_LOG("[GNSS] SLOW: GPS CLOSED ret:%d", close_ret);
|
||||
printf("[GNSS] SLOW: GPS CLOSED ret:%d",ql_gnss_close());
|
||||
gnss_is_open = 0;
|
||||
g_gps_location.is_valid = 0; // 重置状态
|
||||
|
||||
// 设置下次唤醒时间为 15分钟 后
|
||||
slow_next_wake_time = search_start_time + (GNSS_SEARCH_INTERVAL * 1000);
|
||||
APP_GNSS_LOG("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
|
||||
printf("[GNSS] SLOW: GPS CLOSED, next search at +%ds\n", GNSS_SEARCH_INTERVAL);
|
||||
}
|
||||
// else: GPS正在运行中(理论上不会到这里)
|
||||
}
|
||||
|
||||
@@ -168,7 +168,6 @@ static void key_thread_entry(void *parameter)
|
||||
!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){
|
||||
printf("[NET-GATE] mic tip=network_check reason=voice_connect_failed pdp=%d mqtt=%d\r\n", mic_pdp, mic_mqtt);
|
||||
app_play_tips_file(2);
|
||||
}
|
||||
}else{
|
||||
@@ -214,14 +213,7 @@ static void key_thread_entry(void *parameter)
|
||||
build_alarm();
|
||||
}
|
||||
else if(ql_rtos_get_systicks() - key_press_alarm_time > KEY_WAIT_TIME){
|
||||
int leave_recording = vad_audio_is_recording();
|
||||
int leave_mqtt = get_mqtt_connect_status();
|
||||
printf("[NET-GATE] leave recording=%d pending=%d mqtt=%d action=%s\r\n",
|
||||
leave_recording, key_start_leave, leave_mqtt,
|
||||
leave_recording ? "blocked_recording" :
|
||||
(key_start_leave ? "blocked_pending" :
|
||||
(leave_mqtt != 1 ? "blocked_mqtt_offline" : "request_leave")));
|
||||
if(!leave_recording&&key_start_leave==0&&leave_mqtt==1){
|
||||
if(!vad_audio_is_recording()&&key_start_leave==0&&get_mqtt_connect_status()==1){
|
||||
key_start_leave = 1;
|
||||
build_leave_to_parent();
|
||||
}else{
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef APP_GNSS_LOG_H
|
||||
#define APP_GNSS_LOG_H
|
||||
|
||||
/* GNSS diagnostics are quiet by default while investigating voice replies.
|
||||
* Define APP_GNSS_LOG_ENABLED=1 at build time to restore application logs.
|
||||
* Keep state changes and hardware operations outside log arguments. */
|
||||
#ifndef APP_GNSS_LOG_ENABLED
|
||||
#define APP_GNSS_LOG_ENABLED 0
|
||||
#endif
|
||||
|
||||
#if APP_GNSS_LOG_ENABLED
|
||||
#include <stdio.h>
|
||||
#define APP_GNSS_LOG(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define APP_GNSS_LOG(...) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,6 +0,0 @@
|
||||
#ifndef APP_TIME_CALIBRATE_H
|
||||
#define APP_TIME_CALIBRATE_H
|
||||
|
||||
void app_time_calibrate_start(void);
|
||||
|
||||
#endif
|
||||
@@ -16,11 +16,8 @@ typedef enum {
|
||||
} banban_fullfota2_result_t;
|
||||
|
||||
typedef void (*banban_fullfota2_finished_cb_t)(int result);
|
||||
/* Download percentage in 10% steps; reporting must not cancel the download. */
|
||||
typedef void (*banban_fullfota2_progress_cb_t)(int progress);
|
||||
|
||||
int banban_fullfota2_start(const char *url,
|
||||
banban_fullfota2_progress_cb_t progress_cb,
|
||||
banban_fullfota2_finished_cb_t finished_cb);
|
||||
int banban_fullfota2_is_running(void);
|
||||
void banban_fullfota2_discard_staged_package(void);
|
||||
|
||||
@@ -96,9 +96,6 @@ typedef struct _rws_socket_struct
|
||||
int command;
|
||||
|
||||
unsigned int next_message_id;
|
||||
/* Worker-owned transport counters; socket creation zero-initializes these. */
|
||||
unsigned int diag_tx_calls, diag_tx_bytes, diag_tx_failed;
|
||||
unsigned int diag_rx_calls, diag_rx_bytes;
|
||||
|
||||
rws_bool is_connected; // sock connected + handshake done
|
||||
|
||||
|
||||
@@ -193,30 +193,17 @@ static int rws_tls_send_all(_rws_socket *s, const unsigned char *data, size_t si
|
||||
{
|
||||
size_t sent = 0;
|
||||
unsigned int started = ql_rtos_get_systicks();
|
||||
unsigned int retries = 0;
|
||||
if (!s->ssl) {
|
||||
printf("[WSS-IO] tls_write_no_context requested=%u\n", (unsigned int)size);
|
||||
return -1;
|
||||
}
|
||||
if (!s->ssl) return -1;
|
||||
while (sent < size) {
|
||||
int ret = mbedtls_ssl_write(&s->ssl->ssl_ctx, data + sent, size - sent);
|
||||
if (ret > 0) {
|
||||
sent += (size_t)ret;
|
||||
} else if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
printf("[WSS-IO] tls_write_failed ret=%d sent=%u requested=%u retries=%u elapsed_ticks=%u\n",
|
||||
ret, (unsigned int)sent, (unsigned int)size, retries,
|
||||
(unsigned int)(ql_rtos_get_systicks() - started));
|
||||
return -1;
|
||||
} else {
|
||||
retries++;
|
||||
rws_thread_sleep(5);
|
||||
}
|
||||
if (sent < size && (unsigned int)(ql_rtos_get_systicks() - started) >= 1000) {
|
||||
printf("[WSS-IO] tls_write_timeout last_ret=%d sent=%u requested=%u retries=%u elapsed_ticks=%u\n",
|
||||
ret, (unsigned int)sent, (unsigned int)size, retries,
|
||||
(unsigned int)(ql_rtos_get_systicks() - started));
|
||||
return -1;
|
||||
}
|
||||
if (sent < size && (unsigned int)(ql_rtos_get_systicks() - started) >= 1000) return -1;
|
||||
}
|
||||
return (int)sent;
|
||||
}
|
||||
@@ -231,6 +218,7 @@ rws_bool rws_socket_send(_rws_socket *s, const void *data, const size_t data_siz
|
||||
#ifdef LIBRWS_USING_MBED_TLS
|
||||
if (s->scheme && strcmp(s->scheme, "wss") == 0)
|
||||
{
|
||||
LOG_D("[T:%.8d, L:%.5d, %*.*s] try send data with tls", ql_rtos_get_systicks(), __LINE__, DBG_FUNCTION_NUM, DBG_FUNCTION_NUM, __FUNCTION__);
|
||||
sended = rws_tls_send_all(s, (const unsigned char *)data, data_size);
|
||||
}
|
||||
else
|
||||
@@ -255,14 +243,6 @@ rws_bool rws_socket_send(_rws_socket *s, const void *data, const size_t data_siz
|
||||
}
|
||||
#endif
|
||||
|
||||
s->diag_tx_calls++;
|
||||
if (sended > 0) s->diag_tx_bytes += (unsigned int)sended;
|
||||
if (sended != (int)data_size) s->diag_tx_failed++;
|
||||
if (s->diag_tx_calls == 1 || s->diag_tx_calls % 50 == 0 || sended != (int)data_size) {
|
||||
printf("[WSS-IO] write tick=%u requested=%u ret=%d calls=%u bytes=%u failed=%u\n",
|
||||
ql_rtos_get_systicks(), (unsigned int)data_size, sended,
|
||||
s->diag_tx_calls, s->diag_tx_bytes, s->diag_tx_failed);
|
||||
}
|
||||
if (sended == (int)data_size)
|
||||
{
|
||||
rws_socket_error_cnt = 0;
|
||||
@@ -303,6 +283,7 @@ rws_bool rws_socket_recv(_rws_socket *s)
|
||||
rws_bool result = rws_true;
|
||||
int is_reading = 1, error_number = -1, len = -1;
|
||||
char *received = NULL;
|
||||
size_t total_len = 0;
|
||||
// char *buff = 0;
|
||||
|
||||
// LOG_D("[T:%.8d, L:%.5d, %*.*s] receive data", ql_rtos_get_systicks(), __LINE__, DBG_FUNCTION_NUM, DBG_FUNCTION_NUM, __FUNCTION__);
|
||||
@@ -340,13 +321,8 @@ rws_bool rws_socket_recv(_rws_socket *s)
|
||||
break;
|
||||
}
|
||||
if (len <= 0) {
|
||||
const char *reason = len == 0 ? "tls_eof" :
|
||||
(len == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ? "peer_close_notify" :
|
||||
(len == MBEDTLS_ERR_SSL_TIMEOUT ? "tls_read_timeout" : "tls_read_error"));
|
||||
printf("[WSS-DIAG] stage=tls_read reason=%s ret=%d hex=-0x%04x command=%d rx_bytes=%u buffered=%u\n",
|
||||
reason, len, (unsigned int)-len, s->command,
|
||||
s->diag_rx_bytes, (unsigned int)s->received_len);
|
||||
s->error = rws_error_new_code_descr(rws_error_code_read_write_socket, reason);
|
||||
LOG_E("WSS TLS peer closed or read failed: %d\n", len);
|
||||
s->error = rws_error_new_code_descr(rws_error_code_read_write_socket, "TLS peer closed or read failed");
|
||||
s->command = COMMAND_INFORM_DISCONNECTED;
|
||||
result = rws_false;
|
||||
goto _exit;
|
||||
@@ -389,12 +365,7 @@ rws_bool rws_socket_recv(_rws_socket *s)
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
s->diag_rx_calls++;
|
||||
s->diag_rx_bytes += (unsigned int)len;
|
||||
if (s->diag_rx_calls <= 20 || s->diag_rx_calls % 50 == 0)
|
||||
printf("[WSS-IO] read tick=%u bytes=%d calls=%u total=%u buffered=%u command=%d\n",
|
||||
ql_rtos_get_systicks(), len, s->diag_rx_calls, s->diag_rx_bytes,
|
||||
(unsigned int)s->received_len, s->command);
|
||||
total_len += len;
|
||||
if (s->command == COMMAND_WAIT_HANDSHAKE_RESPONCE &&
|
||||
s->received_len + (size_t)len > RWS_HANDSHAKE_MAX + RWS_RECEIVE_BUFF_SIZE) {
|
||||
s->error = rws_error_new_code_descr(rws_error_code_parse_handshake, "WebSocket headers too large");
|
||||
@@ -1037,14 +1008,10 @@ void rws_socket_idle_send(_rws_socket *s)
|
||||
|
||||
void rws_socket_wait_handshake_responce(_rws_socket *s)
|
||||
{
|
||||
unsigned int started = ql_rtos_get_systicks(), polls = 0;
|
||||
unsigned int rx_before = s->diag_rx_bytes;
|
||||
unsigned int started = ql_rtos_get_systicks();
|
||||
size_t header_length = 0;
|
||||
int status = 0, result = 0;
|
||||
const char *reason;
|
||||
printf("[WSS-DIAG] stage=ws_upgrade event=begin timeout_ms=10000\n");
|
||||
while (result == 0) {
|
||||
++polls;
|
||||
rws_socket_recv(s);
|
||||
if (s->error) break;
|
||||
result = rws_check_handshake((const char *)s->received, s->received_len,
|
||||
@@ -1054,9 +1021,6 @@ void rws_socket_wait_handshake_responce(_rws_socket *s)
|
||||
rws_thread_sleep(10);
|
||||
}
|
||||
if (result == 1 && !s->error) {
|
||||
printf("[WSS-DIAG] stage=ws_upgrade result=ok http=101 elapsed_ms=%u rx_bytes=%u header_bytes=%u polls=%u\n",
|
||||
(unsigned int)(ql_rtos_get_systicks() - started) * 5,
|
||||
s->diag_rx_bytes - rx_before, (unsigned int)header_length, polls);
|
||||
s->received_len -= header_length;
|
||||
if (s->received_len)
|
||||
memmove(s->received, (char *)s->received + header_length, s->received_len);
|
||||
@@ -1064,15 +1028,10 @@ void rws_socket_wait_handshake_responce(_rws_socket *s)
|
||||
s->command = COMMAND_INFORM_CONNECTED;
|
||||
LOG_I("WebSocket upgrade verified: HTTP 101\n");
|
||||
} else {
|
||||
reason = s->error ? "transport_error" :
|
||||
(result == 0 ? (s->received_len ? "incomplete_headers_timeout" : "no_response_timeout") :
|
||||
(status && status != 101 ? "http_rejected" : "invalid_upgrade_response"));
|
||||
printf("[WSS-DIAG] stage=ws_upgrade result=failed reason=%s http=%d elapsed_ms=%u timeout_ms=10000 rx_bytes=%u buffered=%u header_bytes=%u parser=%d polls=%u\n",
|
||||
reason, status, (unsigned int)(ql_rtos_get_systicks() - started) * 5,
|
||||
s->diag_rx_bytes - rx_before, (unsigned int)s->received_len,
|
||||
(unsigned int)header_length, result, polls);
|
||||
LOG_E("WebSocket upgrade failed: HTTP %d (0 means incomplete/invalid headers)\n", status);
|
||||
if (!s->error)
|
||||
s->error = rws_error_new_code_descr(rws_error_code_parse_handshake, reason);
|
||||
s->error = rws_error_new_code_descr(rws_error_code_parse_handshake,
|
||||
"Invalid or timed out WebSocket upgrade");
|
||||
rws_socket_close(s);
|
||||
s->command = COMMAND_INFORM_DISCONNECTED;
|
||||
}
|
||||
@@ -1691,12 +1650,9 @@ void rws_socket_resize_received(_rws_socket *s, const size_t size)
|
||||
|
||||
void rws_socket_close(_rws_socket *s)
|
||||
{
|
||||
s->received_len = 0;
|
||||
if (s->socket != RWS_INVALID_SOCKET)
|
||||
{
|
||||
printf("[WSS-IO] close tick=%u tx_calls=%u tx_bytes=%u tx_failed=%u rx_calls=%u rx_bytes=%u buffered=%u command=%d\n",
|
||||
ql_rtos_get_systicks(), s->diag_tx_calls, s->diag_tx_bytes,
|
||||
s->diag_tx_failed, s->diag_rx_calls, s->diag_rx_bytes,
|
||||
(unsigned int)s->received_len, s->command);
|
||||
#ifdef LIBRWS_USING_MBED_TLS
|
||||
if (s->scheme && strcmp(s->scheme, "wss") == 0)
|
||||
{
|
||||
@@ -1713,7 +1669,6 @@ void rws_socket_close(_rws_socket *s)
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
s->received_len = 0;
|
||||
s->is_connected = rws_false;
|
||||
}
|
||||
|
||||
@@ -1922,19 +1877,6 @@ static const char *rws_tls_phase_name(int state)
|
||||
case MBEDTLS_SSL_SERVER_KEY_EXCHANGE: return "server key exchange";
|
||||
case MBEDTLS_SSL_CLIENT_KEY_EXCHANGE: return "client key exchange";
|
||||
case MBEDTLS_SSL_SERVER_FINISHED: return "server finished";
|
||||
case MBEDTLS_SSL_HELLO_REQUEST: return "hello request";
|
||||
case MBEDTLS_SSL_CERTIFICATE_REQUEST: return "certificate request";
|
||||
case MBEDTLS_SSL_SERVER_HELLO_DONE: return "server hello done";
|
||||
case MBEDTLS_SSL_CLIENT_CERTIFICATE: return "client certificate";
|
||||
case MBEDTLS_SSL_CERTIFICATE_VERIFY: return "certificate verify";
|
||||
case MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC: return "client change cipher spec";
|
||||
case MBEDTLS_SSL_CLIENT_FINISHED: return "client finished";
|
||||
case MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC: return "server change cipher spec";
|
||||
case MBEDTLS_SSL_FLUSH_BUFFERS: return "flush buffers";
|
||||
case MBEDTLS_SSL_HANDSHAKE_WRAPUP: return "handshake wrapup";
|
||||
case MBEDTLS_SSL_HANDSHAKE_OVER: return "handshake over";
|
||||
case MBEDTLS_SSL_SERVER_NEW_SESSION_TICKET: return "server new session ticket";
|
||||
case MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT: return "server hello verify request sent";
|
||||
default: return "other";
|
||||
}
|
||||
}
|
||||
@@ -1943,14 +1885,10 @@ static int rws_tls_handshake_timed(mbedtls_ssl_context *ctx)
|
||||
{
|
||||
unsigned int started = ql_rtos_get_systicks();
|
||||
unsigned int phase_started = started, call_ticks = 0, retries = 0;
|
||||
int state = ctx->state, ret = 0, last_step_ret = 0;
|
||||
unsigned int want_read = 0, want_write = 0;
|
||||
int state = ctx->state, ret = 0;
|
||||
while (ctx->state != MBEDTLS_SSL_HANDSHAKE_OVER) {
|
||||
unsigned int before = ql_rtos_get_systicks();
|
||||
ret = mbedtls_ssl_handshake_step(ctx);
|
||||
last_step_ret = ret;
|
||||
if (ret == MBEDTLS_ERR_SSL_WANT_READ) ++want_read;
|
||||
if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) ++want_write;
|
||||
call_ticks += (unsigned int)(ql_rtos_get_systicks() - before);
|
||||
if (ret != 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
break;
|
||||
@@ -1980,12 +1918,6 @@ static int rws_tls_handshake_timed(mbedtls_ssl_context *ctx)
|
||||
}
|
||||
LOG_I("WSS TLS handshake total=%u ms, state=%d, ret=%d\n",
|
||||
(unsigned int)(ql_rtos_get_systicks() - started) * 5, ctx->state, ret);
|
||||
LOG_I("[WSS-DIAG] stage=tls_handshake result=%s phase=%s state=%d elapsed_ms=%u timeout_ms=20000 ret=%d hex=-0x%04x last_step=%d want_read=%u want_write=%u phase_ms=%u calls_ms=%u\n",
|
||||
ret == 0 ? "ok" : (ret == MBEDTLS_ERR_SSL_TIMEOUT ? "timeout" : "failed"),
|
||||
rws_tls_phase_name(ctx->state), ctx->state,
|
||||
(unsigned int)(ql_rtos_get_systicks() - started) * 5, ret, (unsigned int)-ret,
|
||||
last_step_ret, want_read, want_write,
|
||||
(unsigned int)(ql_rtos_get_systicks() - phase_started) * 5, call_ticks * 5);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -2077,13 +2009,7 @@ int rws_ssl_conn(_rws_socket *s)
|
||||
exit:
|
||||
LOG_E("WSS TLS connection failed at %s: %d (-0x%04x)\n", stage, ret, (unsigned int)-ret);
|
||||
rws_error_delete_clean(&s->error);
|
||||
if (s->ssl)
|
||||
LOG_E("[WSS-DIAG] stage=tls_connect failed_at=%s ret=%d verify_flags=0x%08lx\n",
|
||||
stage, ret, (unsigned long)mbedtls_ssl_get_verify_result(&s->ssl->ssl_ctx));
|
||||
s->error = rws_error_new_code_descr(rws_error_code_connect_to_host,
|
||||
ret == MBEDTLS_ERR_SSL_TIMEOUT ? "TLS handshake timeout" :
|
||||
(ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ? "TLS certificate verification failed" :
|
||||
"TLS connection setup or handshake failed"));
|
||||
s->error = rws_error_new_code_descr(rws_error_code_connect_to_host, "TLS connection or certificate verification failed");
|
||||
if (s->ssl) {
|
||||
/* Do not send close_notify for an incomplete handshake. */
|
||||
mbedtls_net_free(&s->ssl->net_ctx);
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <webclient.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
@@ -132,14 +131,12 @@ static int webclient_read_line(struct webclient_session *session, char *buffer,
|
||||
FD_SET(session->socket, &read_fds);
|
||||
|
||||
ret = select(session->socket + 1, &read_fds, NULL, NULL, &t);
|
||||
int select_errno = ret < 0 ? errno : 0;
|
||||
|
||||
// printf("select ret: %d\r\n", ret);
|
||||
|
||||
if(ret <= 0)
|
||||
{
|
||||
printf("[HTTP-DIAG] stage=response_header reason=%s select_ret=%d errno_snapshot=%d timeout_ms=2000\r\n",
|
||||
ret == 0 ? "timeout" : "select_error", ret, select_errno);
|
||||
printf("*** select timeout or error ***\r\n");
|
||||
return -WEBCLIENT_ERROR;
|
||||
}
|
||||
|
||||
@@ -564,14 +561,12 @@ static int webclient_connect(struct webclient_session *session, const char *URI)
|
||||
FD_SET(socket_handle, &write_fds);
|
||||
|
||||
rc= select(socket_handle + 1, &read_fds, &write_fds, NULL, &t);
|
||||
int select_errno = rc < 0 ? errno : 0;
|
||||
|
||||
printf("select ret: %d\r\n", rc);
|
||||
|
||||
if(rc <= 0)
|
||||
{
|
||||
printf("[HTTP-DIAG] stage=tcp_connect reason=%s select_ret=%d errno_snapshot=%d timeout_ms=2000\r\n",
|
||||
rc == 0 ? "timeout" : "select_error", rc, select_errno);
|
||||
printf("*** select timeout or error ***\r\n");
|
||||
rc = -WEBCLIENT_CONNECT_FAILED;
|
||||
goto __exit;
|
||||
}
|
||||
@@ -1381,14 +1376,12 @@ int webclient_read(struct webclient_session *session, void *buffer, size_t lengt
|
||||
FD_SET(session->socket, &read_fds);
|
||||
|
||||
ret = select(session->socket + 1, &read_fds, NULL, NULL, &t);
|
||||
int select_errno = ret < 0 ? errno : 0;
|
||||
|
||||
// printf("select ret: %d\r\n", ret);
|
||||
|
||||
if(ret <= 0)
|
||||
{
|
||||
printf("[HTTP-DIAG] stage=response_body reason=%s select_ret=%d errno_snapshot=%d timeout_ms=2000\r\n",
|
||||
ret == 0 ? "timeout" : "select_error", ret, select_errno);
|
||||
printf("*** select timeout or error ***\r\n");
|
||||
return -WEBCLIENT_ERROR;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "ql_application.h"
|
||||
#include "sockets.h"
|
||||
#include "app_inc.h"
|
||||
#include "app_gnss_log.h"
|
||||
#include "cjson.h"
|
||||
#include "ql_fs.h"
|
||||
#include "ql_fota.h"
|
||||
@@ -124,7 +123,6 @@ typedef struct {
|
||||
int app_mqtt_publish(char *buf, int len, int type);
|
||||
void build_register(void);
|
||||
static void ota_report_pending_success(void);
|
||||
static char ota_progress_target_version[16] = {0};
|
||||
|
||||
enum{
|
||||
MQTT_WORK_CONNECT,
|
||||
@@ -262,39 +260,7 @@ static void ota_state_clear(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void ota_fullfota2_progress(int progress)
|
||||
{
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON *data = cJSON_CreateObject();
|
||||
char *payload;
|
||||
|
||||
if (root == NULL || data == NULL) {
|
||||
cJSON_Delete(root);
|
||||
cJSON_Delete(data);
|
||||
RLOGE("allocate OTA progress response failed");
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(root, "msg_id", "003");
|
||||
cJSON_AddStringToObject(root, "status", "updating");
|
||||
cJSON_AddStringToObject(data, "target_version", ota_progress_target_version);
|
||||
cJSON_AddNumberToObject(data, "progress", progress);
|
||||
cJSON_AddItemToObject(root, "data", data);
|
||||
payload = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (payload == NULL) {
|
||||
RLOGE("build OTA progress response failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Progress is best effort; a publish failure must not abort FULLOTA2. */
|
||||
if (app_mqtt_publish(payload, strlen(payload), 0) != 0) {
|
||||
RLOGE("OTA progress publish failed: %d%%", progress);
|
||||
}
|
||||
free(payload);
|
||||
}
|
||||
|
||||
static void ota_fullfota2_finished(int result)
|
||||
static void ota_fullfota2_finished(int result)
|
||||
{
|
||||
if (result == BANBAN_FULLOTA2_OK) {
|
||||
return;
|
||||
@@ -443,7 +409,7 @@ int parse_command(const char *json_str, command_t *cmd)
|
||||
if (params != NULL) {
|
||||
switch (cmd->type) {
|
||||
case MSG_GPS_QUERY:
|
||||
APP_GNSS_LOG("MSG_GPS_QUERY\r\n");
|
||||
printf("MSG_GPS_QUERY\r\n");
|
||||
/*返回当前的GPS坐标信息*/
|
||||
break;
|
||||
|
||||
@@ -821,7 +787,6 @@ int app_mqtt_connect()
|
||||
MQTT_CONN_UNLOCK();
|
||||
|
||||
// [ADD] 连接成功提示音
|
||||
printf("[NET-GATE] tip=network_ok reason=mqtt_connected voice_service=not_checked\r\n");
|
||||
app_play_tips_file(11);
|
||||
|
||||
if(ret!=0){
|
||||
@@ -1450,10 +1415,7 @@ static void handle_ota_upgrade(command_t *cmd)
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(ota_progress_target_version, sizeof(ota_progress_target_version),
|
||||
"%s", cmd->params.ota.version);
|
||||
ret = banban_fullfota2_start(cmd->params.ota.url,
|
||||
ota_fullfota2_progress,
|
||||
ret = banban_fullfota2_start(cmd->params.ota.url,
|
||||
ota_fullfota2_finished);
|
||||
if (ret != 0) {
|
||||
RLOGE("start FULLOTA2 task failed: %d", ret);
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "ql_atcmd.h"
|
||||
#include "http_downfile.h"
|
||||
#include "ota_post_update.h"
|
||||
#include "app_time_calibrate.h"
|
||||
|
||||
//#define NTP_SERVER_DOMAIN "ntp.aliyun.com"
|
||||
|
||||
@@ -142,7 +141,6 @@ void ql_datacall_demo_thread(void *arvg)
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
app_time_calibrate_start();
|
||||
ql_get_data_call_info(1, 0, &info);
|
||||
printf("info.profile_idx: %d\r\n", info.profile_idx);
|
||||
printf("info.ip_version: %d\r\n", info.ip_version);
|
||||
|
||||
@@ -49,7 +49,6 @@ static volatile u32 g_nfc_tx_mode_deadline = 0;
|
||||
#define NFC_TYPE2_READ_SIZE 16
|
||||
#define NFC_TYPE2_MEMORY_SIZE 160
|
||||
#define NFC_TYPE2_NDEF_OFFSET 16
|
||||
#define NFC_IMEI_DIGITS 15
|
||||
|
||||
#define NFC_IRQ_TX 0x40
|
||||
#define NFC_IRQ_RX 0x20
|
||||
@@ -173,37 +172,11 @@ static u8 nfc_card_emulation_send_frame(const u8 *frame, u8 length)
|
||||
|
||||
static void nfc_type2_build_ndef(void)
|
||||
{
|
||||
char device_id[32] = {0};
|
||||
char serial_number[32] = {0};
|
||||
char imei[32] = {0};
|
||||
char json[128];
|
||||
int json_len;
|
||||
static const char uri_suffix[] = "example.com/";
|
||||
size_t uri_len = sizeof(uri_suffix) - 1;
|
||||
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");
|
||||
}
|
||||
if (ql_dev_get_imei(imei, sizeof(imei)) != QL_DEV_SUCCESS ||
|
||||
imei[0] == '\0') {
|
||||
strcpy(imei, "unknown");
|
||||
} else {
|
||||
imei[NFC_IMEI_DIGITS] = '\0';
|
||||
}
|
||||
|
||||
json_len = snprintf(json, sizeof(json),
|
||||
"{\"device_id\":\"%s\",\"serial_number\":\"%s\","
|
||||
"\"imei\":\"%s\"}",
|
||||
device_id, serial_number, imei);
|
||||
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. */
|
||||
@@ -217,20 +190,18 @@ static void nfc_type2_build_ndef(void)
|
||||
nfc_type2_memory[15] = 0x0F; /* Read-only access. */
|
||||
|
||||
ndef = &nfc_type2_memory[NFC_TYPE2_NDEF_OFFSET];
|
||||
record_len = (u8)(7 + json_len);
|
||||
record_len = (u8)(5 + uri_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. */
|
||||
ndef[4] = (u8)(1 + uri_len); /* URI prefix code plus suffix. */
|
||||
ndef[5] = 'U'; /* NFC Forum URI record. */
|
||||
ndef[6] = 0x04; /* URI prefix: https:// */
|
||||
memcpy(&ndef[7], uri_suffix, uri_len);
|
||||
ndef[7 + uri_len] = 0xFE; /* Terminator TLV. */
|
||||
|
||||
printf("NFC Type 2 NDEF: %s\r\n", json);
|
||||
printf("NFC Type 2 NDEF URI: https://%s\r\n", uri_suffix);
|
||||
}
|
||||
|
||||
static void nfc_type2_read_pages(u8 start_page, u8 *response)
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
typedef struct {
|
||||
char url[BANBAN_FULLOTA2_URL_MAX];
|
||||
banban_fullfota2_progress_cb_t progress_cb;
|
||||
banban_fullfota2_finished_cb_t finished_cb;
|
||||
} banban_fullfota2_request_t;
|
||||
|
||||
@@ -172,9 +171,6 @@ static void banban_fullfota2_task(void *argument)
|
||||
printf("[FULLOTA2] download progress=%d%% bytes=%d/%d\r\n",
|
||||
progress, written, content_length);
|
||||
while (next_progress <= progress) {
|
||||
if (g_fullfota2_request.progress_cb != NULL) {
|
||||
g_fullfota2_request.progress_cb(next_progress);
|
||||
}
|
||||
next_progress += 10;
|
||||
}
|
||||
}
|
||||
@@ -223,7 +219,6 @@ failed:
|
||||
}
|
||||
|
||||
int banban_fullfota2_start(const char *url,
|
||||
banban_fullfota2_progress_cb_t progress_cb,
|
||||
banban_fullfota2_finished_cb_t finished_cb)
|
||||
{
|
||||
int ret;
|
||||
@@ -244,7 +239,6 @@ int banban_fullfota2_start(const char *url,
|
||||
|
||||
memset(&g_fullfota2_request, 0, sizeof(g_fullfota2_request));
|
||||
memcpy(g_fullfota2_request.url, url, url_length + 1);
|
||||
g_fullfota2_request.progress_cb = progress_cb;
|
||||
g_fullfota2_request.finished_cb = finished_cb;
|
||||
g_fullfota2_running = 1;
|
||||
|
||||
|
||||
@@ -71,55 +71,6 @@ static int vad_tts_playback_finished_status = 0;
|
||||
// 统计
|
||||
static uint32 frame_count = 0;
|
||||
static uint32 speech_frame_count = 0;
|
||||
static struct {
|
||||
unsigned int reads, short_reads, read_errors, zero_frames, samples, peak;
|
||||
unsigned int ring_drops, ring_drop_bytes;
|
||||
uint64_t abs_sum, last_log_ms, started_ms;
|
||||
} mic_diag;
|
||||
|
||||
static void mic_diag_log(const char *stage)
|
||||
{
|
||||
printf("[MIC-DIAG] %s uptime_ms=%u age_ms=%u reads=%u short=%u read_errors=%u zero=%u peak=%u mean_abs=%u ring_drops=%u ring_drop_bytes=%u\n",
|
||||
stage, (unsigned int)hal_os_up_time_ms(),
|
||||
(unsigned int)(hal_os_up_time_ms() - mic_diag.started_ms),
|
||||
mic_diag.reads, mic_diag.short_reads, mic_diag.read_errors, mic_diag.zero_frames,
|
||||
mic_diag.peak, mic_diag.samples ? (unsigned int)(mic_diag.abs_sum / mic_diag.samples) : 0,
|
||||
mic_diag.ring_drops, mic_diag.ring_drop_bytes);
|
||||
}
|
||||
|
||||
/* Observe both preroll and live PCM writes; retain the caller's wakeup policy. */
|
||||
static int mic_diag_write(const void *data, int len)
|
||||
{
|
||||
int written = hal_os_rb_write(ring_sender, data, len, 25);
|
||||
if (written < len) {
|
||||
mic_diag.ring_drops++;
|
||||
mic_diag.ring_drop_bytes += (unsigned int)(len - (written > 0 ? written : 0));
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
static void mic_diag_read(const uint8_t *data, int len, int ret)
|
||||
{
|
||||
int i, bounded = len > VAD_FRAME_BYTES ? VAD_FRAME_BYTES : len;
|
||||
unsigned int peak = 0;
|
||||
mic_diag.reads++;
|
||||
if (ret < 0) mic_diag.read_errors++;
|
||||
if (len != VAD_FRAME_BYTES) mic_diag.short_reads++;
|
||||
for (i = 0; i + 1 < bounded; i += 2) {
|
||||
int sample = (int16_t)((unsigned int)data[i] | ((unsigned int)data[i + 1] << 8));
|
||||
unsigned int magnitude = (unsigned int)(sample < 0 ? -sample : sample);
|
||||
if (magnitude > peak) peak = magnitude;
|
||||
mic_diag.abs_sum += magnitude;
|
||||
mic_diag.samples++;
|
||||
}
|
||||
if (bounded > 0 && peak == 0) mic_diag.zero_frames++;
|
||||
if (peak > mic_diag.peak) mic_diag.peak = peak;
|
||||
if (mic_diag.reads == 1 || hal_os_up_time_ms() - mic_diag.last_log_ms >= 1000) {
|
||||
printf("[MIC-DIAG] read ret=%d bytes=%d frame_peak=%u state=%d\n", ret, len, peak, g_state);
|
||||
mic_diag_log("sampling");
|
||||
mic_diag.last_log_ms = hal_os_up_time_ms();
|
||||
}
|
||||
}
|
||||
extern int audio_packet_type;
|
||||
|
||||
|
||||
@@ -133,17 +84,7 @@ static audio_record_handle record_hd;
|
||||
|
||||
static void ql_audio_record_callback(acm_audio_track_handle handle, acm_audio_record_event_t event)
|
||||
{
|
||||
static int last_event = -1;
|
||||
static uint64_t last_log_ms;
|
||||
uint64_t now;
|
||||
(void)handle;
|
||||
if (event == AUDIO_RECORD_EVENT_TICK || event == AUDIO_RECORD_EVENT_NONE) return;
|
||||
now = hal_os_up_time_ms();
|
||||
if ((int)event != last_event || now - last_log_ms >= 1000) {
|
||||
printf("[MIC-DIAG] callback event=%d uptime_ms=%u\n", event, (unsigned int)now);
|
||||
last_event = (int)event;
|
||||
last_log_ms = now;
|
||||
}
|
||||
//printf("audio_record_callback event:%d\r\n", event);
|
||||
}
|
||||
|
||||
void vad_tts_playback_finished()
|
||||
@@ -265,13 +206,7 @@ int vad_audio_init(void) {
|
||||
|
||||
// 启动录音
|
||||
int vad_audio_start(void) {
|
||||
memset(&mic_diag, 0, sizeof(mic_diag));
|
||||
mic_diag.started_ms = hal_os_up_time_ms();
|
||||
{
|
||||
int init_ret = vad_audio_init();
|
||||
printf("[MIC-DIAG] open ret=%d handle=%u uptime_ms=%u\n",
|
||||
init_ret, (unsigned int)record_hd, (unsigned int)hal_os_up_time_ms());
|
||||
}
|
||||
vad_audio_init();
|
||||
|
||||
frame_count = 0;
|
||||
speech_frame_count = 0;
|
||||
@@ -282,7 +217,6 @@ int vad_audio_start(void) {
|
||||
|
||||
// 停止录音
|
||||
int vad_audio_stop(void) {
|
||||
mic_diag_log("stop");
|
||||
|
||||
printf("Audio recording stopped, frames: %d, speech: %d",
|
||||
frame_count, speech_frame_count);
|
||||
@@ -301,7 +235,6 @@ int vad_audio_stop(void) {
|
||||
|
||||
// 停止录音
|
||||
int vad_audio_stop_no_wait_resp(void) {
|
||||
mic_diag_log("cancel");
|
||||
conversation_cancel_wait();
|
||||
|
||||
printf("Audio recording stopped no_wait_resp, frames: %d, speech: %d",
|
||||
@@ -448,10 +381,7 @@ void vad_audio_process(void) {
|
||||
case STATE_RECORDING_ACTIVE:
|
||||
memset(mic_buf, 0, sizeof(mic_buf));
|
||||
//每次读取640字节音频数据
|
||||
{
|
||||
int read_ret = ql_audio_record_read(record_hd, mic_buf, &record_len);
|
||||
mic_diag_read(mic_buf, record_len, read_ret);
|
||||
}
|
||||
ql_audio_record_read(record_hd, mic_buf, &record_len);
|
||||
|
||||
if (g_state == STATE_RECORDING_WAIT_SOUND) {
|
||||
/* 首次开口确认前不向服务端发送静音。只在本地保留最近500ms,
|
||||
@@ -462,7 +392,7 @@ void vad_audio_process(void) {
|
||||
vad_preroll_count++;
|
||||
}
|
||||
} else {
|
||||
if(mic_diag_write(mic_buf, record_len)>0){
|
||||
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){
|
||||
ql_rtos_semaphore_release(sender_sem);
|
||||
}
|
||||
}
|
||||
@@ -507,7 +437,7 @@ void vad_audio_process(void) {
|
||||
int preroll_queued = 0;
|
||||
for (n = 0; n < vad_preroll_count; n++) {
|
||||
size_t frame_index = (first + n) % VAD_PREROLL_FRAMES;
|
||||
if (mic_diag_write(vad_preroll[frame_index], VAD_FRAME_BYTES) > 0) {
|
||||
if (hal_os_rb_write(ring_sender, vad_preroll[frame_index], VAD_FRAME_BYTES, 25) > 0) {
|
||||
preroll_queued = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,6 @@ enum { WAIT_NONE, WAIT_NO_SPEECH, WAIT_REPLY, WAIT_SEND_FAILURE };
|
||||
static int conversation_wait = WAIT_NONE;
|
||||
static int timeout_action_busy = 0;
|
||||
static int round_response_received = 0;
|
||||
/* Queue admission is distinct from a completed TLS write and from server ACK. */
|
||||
static struct {
|
||||
unsigned int tx_packets, tx_bytes, tx_failed, rx_text, rx_bin;
|
||||
unsigned int start_ack, end_ack;
|
||||
uint64_t started_ms, last_rx_ms;
|
||||
} voice_diag;
|
||||
static unsigned int active_session_round = 0;
|
||||
static int active_session_valid = 0;
|
||||
static uint64_t startup_start_ms = 0;
|
||||
@@ -93,10 +87,6 @@ static void conversation_unlock(void)
|
||||
static void conversation_begin_round(void)
|
||||
{
|
||||
conversation_round++;
|
||||
memset(&voice_diag, 0, sizeof(voice_diag));
|
||||
voice_diag.started_ms = hal_os_up_time_ms();
|
||||
printf("[VOICE-DIAG] begin round=%u uptime_ms=%u\n",
|
||||
conversation_round, (unsigned int)voice_diag.started_ms);
|
||||
conversation_wait = WAIT_NONE;
|
||||
round_response_received = 0;
|
||||
active_session_valid = 0;
|
||||
@@ -227,20 +217,6 @@ unsigned char get_xiaoice_connected_status(void)
|
||||
return xiaoice_wss_connect_status;
|
||||
}
|
||||
|
||||
/* Called with conversation_mutex held. Ages use monotonic milliseconds. */
|
||||
static void voice_diag_snapshot(const char *stage)
|
||||
{
|
||||
uint64_t now = hal_os_up_time_ms();
|
||||
printf("[VOICE-DIAG] %s round=%u session=%s age_ms=%u queue_pkts=%u queue_bytes=%u queue_fail=%u rx_text=%u rx_bin=%u start_ack=%u end_ack=%u last_rx_age_ms=%d wait=%d wait_ms=%u response=%d\n",
|
||||
stage, conversation_round, active_session_valid ? sessionId : "none",
|
||||
(unsigned int)(now - voice_diag.started_ms), voice_diag.tx_packets,
|
||||
voice_diag.tx_bytes, voice_diag.tx_failed, voice_diag.rx_text,
|
||||
voice_diag.rx_bin, voice_diag.start_ack, voice_diag.end_ack,
|
||||
voice_diag.rx_text || voice_diag.rx_bin ? (int)(now - voice_diag.last_rx_ms) : -1,
|
||||
conversation_wait, conversation_wait == WAIT_REPLY ?
|
||||
(unsigned int)(now - reply_wait_start_ms) : 0, round_response_received);
|
||||
}
|
||||
|
||||
static void onopen(rws_socket socket)
|
||||
{
|
||||
if (socket != app->socket || !app->init) return;
|
||||
@@ -269,9 +245,6 @@ static void onclose(rws_socket socket)
|
||||
}
|
||||
|
||||
if (socket != app->socket) return;
|
||||
conversation_lock();
|
||||
voice_diag_snapshot("disconnected");
|
||||
conversation_unlock();
|
||||
xiaoice_wss_connect_status = 0;
|
||||
app->socket = NULL;
|
||||
/* The worker owns and releases this socket after the callback. */
|
||||
@@ -676,16 +649,6 @@ static void conversation_log_error_message(const char *text, unsigned int len)
|
||||
static void onmessage_text(rws_socket socket, const char *text, const unsigned int len)
|
||||
{
|
||||
conversation_lock();
|
||||
voice_diag.rx_text++;
|
||||
voice_diag.last_rx_ms = hal_os_up_time_ms();
|
||||
if (strstr(text, "START_ACK")) voice_diag.start_ack++;
|
||||
if (strstr(text, "END_ACK")) voice_diag.end_ack++;
|
||||
/* Bounded preview includes unrecognized replies and ACKs before filtering. */
|
||||
printf("[VOICE-RX] round=%u session=%s uptime_ms=%u len=%u accepted=%d preview=%.*s%s\n",
|
||||
conversation_round, active_session_valid ? sessionId : "none",
|
||||
(unsigned int)voice_diag.last_rx_ms, len,
|
||||
conversation_accept_voice_message(text), (int)(len > 192 ? 192 : len), text,
|
||||
len > 192 ? " [truncated]" : "");
|
||||
if (strstr(text, "tts_error") != NULL || strstr(text, "tts_err") != NULL ||
|
||||
strstr(text, "PROMPT_SOUND_URL") != NULL) {
|
||||
conversation_log_error_message(text, len);
|
||||
@@ -700,16 +663,8 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
|
||||
}
|
||||
|
||||
static void onmessage_bin(rws_socket socket, const void * data, const unsigned int len)
|
||||
{
|
||||
(void)socket;
|
||||
(void)data;
|
||||
conversation_lock();
|
||||
voice_diag.rx_bin++;
|
||||
voice_diag.last_rx_ms = hal_os_up_time_ms();
|
||||
if (voice_diag.rx_bin == 1 || voice_diag.rx_bin % 50 == 0)
|
||||
printf("[VOICE-RX] binary round=%u len=%u count=%u ignored=1\n",
|
||||
conversation_round, len, voice_diag.rx_bin);
|
||||
conversation_unlock();
|
||||
{
|
||||
LOG_D("message(bin): %d ", len);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -856,11 +811,7 @@ int talkingq_ws_send_text(char *senddata)
|
||||
|
||||
LOG_D("talkingq_wss_send_text string = %s, len = %d", senddata, strlen(senddata));
|
||||
|
||||
{
|
||||
int queued = rws_socket_send_text(app->socket, senddata);
|
||||
printf("[VOICE-TX] auth/text queue uptime_ms=%u bytes=%u accepted=%d\n",
|
||||
(unsigned int)hal_os_up_time_ms(), (unsigned int)strlen(senddata), queued != 0);
|
||||
}
|
||||
rws_socket_send_text(app->socket, senddata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1090,7 +1041,6 @@ void sendStartPacket(void) {
|
||||
startPacket.dataSize = 0;
|
||||
|
||||
active_session_valid = sendRecordingAudioPacket(&startPacket) == 0;
|
||||
voice_diag_snapshot("start-queued");
|
||||
conversation_unlock();
|
||||
// rt_kprintf("sendStartPacket, seq=%u\r\n", startPacket.sequenceNumber);
|
||||
}
|
||||
@@ -1109,7 +1059,6 @@ void sendEndPacket(void) {
|
||||
reply_wait_start_ms = hal_os_up_time_ms();
|
||||
conversation_wait = end_ret == 0 ? WAIT_REPLY : WAIT_SEND_FAILURE;
|
||||
}
|
||||
voice_diag_snapshot("end-queued");
|
||||
}
|
||||
conversation_unlock();
|
||||
// rt_kprintf("sendEndPacket, seq=%u\r\n", endPacket.sequenceNumber);
|
||||
@@ -1125,19 +1074,12 @@ void sendAudioPacket(const uint8_t* data, size_t dataSize) {
|
||||
audioPacket.data = malloc(audioPacket.dataSize);
|
||||
if(audioPacket.data==NULL){
|
||||
RLOGE("audioPacket.data NULL");
|
||||
conversation_lock();
|
||||
voice_diag.tx_failed++;
|
||||
printf("[VOICE-TX] alloc_failed round=%u seq=%u bytes=%u\n",
|
||||
conversation_round, audioPacket.sequenceNumber, (unsigned int)dataSize);
|
||||
conversation_unlock();
|
||||
return;
|
||||
}
|
||||
memcpy(audioPacket.data, data, dataSize);
|
||||
}
|
||||
|
||||
conversation_lock();
|
||||
sendRecordingAudioPacket(&audioPacket);
|
||||
conversation_unlock();
|
||||
// rt_kprintf("sendAudioPacket, seq=%u\r\n", audioPacket.sequenceNumber);
|
||||
if(audioPacket.data){
|
||||
free(audioPacket.data);
|
||||
@@ -1150,9 +1092,7 @@ void sendInterruptPacket(void) {
|
||||
interruptPacket.packetType = 3;
|
||||
interruptPacket.dataSize = 0;
|
||||
|
||||
conversation_lock();
|
||||
sendRecordingAudioPacket(&interruptPacket);
|
||||
conversation_unlock();
|
||||
}
|
||||
|
||||
void sendAuthentication()
|
||||
@@ -1182,9 +1122,6 @@ int sendRecordingAudioPacket(AudioPacket *packet) {
|
||||
sendBuffer = malloc(DEVICE_ID_SIZE + SESSION_ID_SIZE + sizeof(uint32_t) * 2 + sizeof(uint32_t) + packet->dataSize);
|
||||
if(sendBuffer==NULL){
|
||||
RLOGE("sendBuffer NULL");
|
||||
voice_diag.tx_failed++;
|
||||
printf("[VOICE-TX] alloc_failed seq=%u type=%u bytes=%u\n",
|
||||
packet->sequenceNumber, packet->packetType, (unsigned int)packet->dataSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1214,21 +1151,7 @@ int sendRecordingAudioPacket(AudioPacket *packet) {
|
||||
// memcpy(recorder->save_buf, (void *)sendBuffer, recorder->save_len);
|
||||
//record_msg_send(recorder, RECORD_MSG_DATA, recorder->save_len);
|
||||
|
||||
uint64_t queue_started = hal_os_up_time_ms();
|
||||
int send_ret = talkingq_ws_send_bin((uint8_t*)sendBuffer, totalSize);
|
||||
if (send_ret == 0) {
|
||||
voice_diag.tx_packets++;
|
||||
voice_diag.tx_bytes += (unsigned int)packet->dataSize;
|
||||
} else {
|
||||
voice_diag.tx_failed++;
|
||||
}
|
||||
if (packet->packetType == 1 || packet->packetType == 2 || send_ret != 0 ||
|
||||
voice_diag.tx_packets % 50 == 0) {
|
||||
printf("[VOICE-TX] queue round=%u session=%s seq=%u type=%u bytes=%u ret=%d elapsed_ms=%u\n",
|
||||
conversation_round, sessionId, packet->sequenceNumber, packet->packetType,
|
||||
(unsigned int)packet->dataSize, send_ret,
|
||||
(unsigned int)(hal_os_up_time_ms() - queue_started));
|
||||
}
|
||||
free(sendBuffer);
|
||||
return send_ret;
|
||||
// rt_kprintf("sendRecordingAudioPacket - Device ID: %s, Session ID: %s\n", deviceId, sessionId);
|
||||
@@ -1570,7 +1493,6 @@ static void conversation_check_timeout(void)
|
||||
(conversation_wait == WAIT_NO_SPEECH ||
|
||||
conversation_wait == WAIT_SEND_FAILURE ||
|
||||
hal_os_up_time_ms() - reply_wait_start_ms > 5000)) {
|
||||
voice_diag_snapshot("timeout-before-disconnect");
|
||||
expired = conversation_wait;
|
||||
expired_round = wait_round;
|
||||
conversation_wait = WAIT_NONE;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,181 +0,0 @@
|
||||
"""Host behavioral tests of the actual APP module; no modem traffic is sent."""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
APP = ROOT / 'ql-application/threadx/interface/custom_app'
|
||||
with tempfile.TemporaryDirectory(prefix='app-nitz-test-') as directory:
|
||||
work = Path(directory)
|
||||
headers = {
|
||||
'ql_rtos.h': '''#include <stddef.h>
|
||||
typedef void *ql_task_t;
|
||||
unsigned int ql_rtos_get_systicks(void);
|
||||
void ql_rtos_task_sleep_ms(unsigned int);
|
||||
int ql_rtos_task_create(ql_task_t *, unsigned int, unsigned char, char *, void (*)(void *), void *, ...);
|
||||
int ql_rtos_task_delete(ql_task_t);
|
||||
''',
|
||||
'ql_func.h': '#include <stdint.h>\nextern intptr_t (*m_get_api_ptr)(char *);\n',
|
||||
'ql_nw.h': '''enum { QL_NW_SUCCESS=0 };
|
||||
typedef struct {char nitz_time[32]; unsigned long abs_time; unsigned char leap_sec;} QL_NW_NITZ_TIME_INFO_T;
|
||||
int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *);
|
||||
''',
|
||||
'ql_rtc.h': '''typedef struct {int tm_sec,tm_min,tm_hour,tm_mday,tm_mon,tm_year,tm_wday;} ql_rtc_time_t;
|
||||
int ql_rtc_set_time(ql_rtc_time_t *);
|
||||
int ql_rtc_get_time(ql_rtc_time_t *);
|
||||
''',
|
||||
}
|
||||
for name, content in headers.items():
|
||||
(work / name).write_text(content)
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include "APP_SOURCE"
|
||||
static int mode, creates, queries, sets, sleeps;
|
||||
static unsigned int ticks=100;
|
||||
static time_t app_epoch=100;
|
||||
static int zone_minutes=480;
|
||||
static ql_rtc_time_t saved;
|
||||
unsigned int ql_rtos_get_systicks(void) {return ticks;}
|
||||
void ql_rtos_task_sleep_ms(unsigned int ms) {assert(ms==5000); ++sleeps; ticks+=ms/5;}
|
||||
static intptr_t lookup(char *name) {
|
||||
assert(!strcmp(name,"ql_nw_get_nitz_time_info") || !strcmp(name,"ql_localtime_r") ||
|
||||
!strcmp(name,"ql_rtc_set_time") || !strcmp(name,"ql_rtc_get_time"));
|
||||
return mode==1 ? 0 : 1;
|
||||
}
|
||||
intptr_t (*m_get_api_ptr)(char *)=lookup;
|
||||
int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *info) {
|
||||
time_t value=1793019600; /* 2026-10-26 13:00:00 UTC */
|
||||
struct tm *utc;
|
||||
++queries;
|
||||
if (mode==2) return 1;
|
||||
if (mode==3) return 0;
|
||||
if (mode==4) {memset(info->nitz_time,'X',32); return 0;}
|
||||
if (mode==5) {strcpy(info->nitz_time,"26/02/30 13:00:00 +32 00"); return 0;}
|
||||
if (mode==6 || mode==22) {
|
||||
strcpy(info->nitz_time,mode==22 ? "26/10/26 08:00:10 32 0" : "26/10/26 13:00:00 +32 00");
|
||||
return 0;
|
||||
}
|
||||
if (mode==16 && queries==1) return 1;
|
||||
if (mode==23 && queries<3) return 1;
|
||||
if (mode==17) ticks+=600; /* slow query must be rejected */
|
||||
if (mode==14) value=1793059198; /* near midnight UTC */
|
||||
if (mode==15) value=2147483646; /* PMIC +8 local calendar exceeds signed32 range */
|
||||
if (mode==18) value=1704067200; /* Old but valid snapshot: age cannot be proven. */
|
||||
else value+=(ticks-100U)/200U;
|
||||
/* Compute relative elapsed correctly in wrap scenario. */
|
||||
if (mode==13) value=1793019600+(unsigned int)(ticks-(UINT32_MAX-199U))/200U;
|
||||
utc=gmtime(&value); assert(utc);
|
||||
snprintf(info->nitz_time,32,mode==21 ? "%02d/%02d/%02d %02d:%02d:%02d 32 0" : "%02d/%02d/%02d %02d:%02d:%02d +32 00",
|
||||
utc->tm_year%100,utc->tm_mon+1,utc->tm_mday,utc->tm_hour,utc->tm_min,utc->tm_sec);
|
||||
return 0;
|
||||
}
|
||||
struct tm *ql_localtime_r(const time_t *epoch,struct tm *out) {
|
||||
time_t local=*epoch+zone_minutes*60;
|
||||
struct tm *value;
|
||||
if (mode==7) return NULL;
|
||||
if (mode==25) local+=28800; /* Invalid PMIC conversion (+16 hours total). */
|
||||
value=gmtime(&local); assert(value); *out=*value; return out;
|
||||
}
|
||||
time_t time(time_t *out) {(void)out; return app_epoch;}
|
||||
int ql_rtc_set_time(ql_rtc_time_t *calendar) {
|
||||
long long value;
|
||||
++sets; saved=*calendar;
|
||||
if (mode==8) return -1;
|
||||
assert(calendar_epoch(calendar->tm_year,calendar->tm_mon,calendar->tm_mday,
|
||||
calendar->tm_hour,calendar->tm_min,calendar->tm_sec,&value)==0);
|
||||
if (mode!=9) app_epoch=(time_t)(value-zone_minutes*60);
|
||||
if (mode==10) app_epoch+=28800; /* catches double timezone conversion */
|
||||
return 0;
|
||||
}
|
||||
int ql_rtc_get_time(ql_rtc_time_t *out) {*out=saved; if (mode==20) out->tm_year=1970; return mode==11 ? -1 : 0;}
|
||||
int ql_rtos_task_create(ql_task_t *task,unsigned int size,unsigned char priority,char *name,
|
||||
void (*entry)(void *),void *arg,...) {
|
||||
assert(size>=4096 && priority==110 && !strcmp(name,"app_time_calibrate"));
|
||||
++creates;
|
||||
if (mode==19) return -1;
|
||||
*task=(void *)1; entry(arg); return 0;
|
||||
}
|
||||
int ql_rtos_task_delete(ql_task_t task) {assert(task==NULL); return 0;}
|
||||
static void check_parser(void) {
|
||||
time_t epoch; int zone; long long value;
|
||||
assert(parse_nitz("26/10/26 13:00:00 +32 00",&epoch,&zone)==0 &&
|
||||
epoch==1793019600 && zone==32);
|
||||
assert(parse_nitz("26/10/26,13:00:00-22",&epoch,&zone)==0 &&
|
||||
epoch==1793019600 && zone==-22);
|
||||
assert(parse_nitz("26/10/26 13:00:00 +56 02",&epoch,&zone)==0);
|
||||
assert(parse_nitz("26/10/26 08:00:10 32 0",&epoch,&zone)==0 && epoch==1793001610 && zone==32);
|
||||
assert(parse_nitz("26/10/26 13:00:00 -22 0",&epoch,&zone)==0 && zone==-22);
|
||||
assert(parse_nitz("26/10/26 13:00:00 0 0",&epoch,&zone)==0 && zone==0);
|
||||
assert(parse_nitz("26/10/26 13:00:00 0",&epoch,&zone)!=0);
|
||||
assert(parse_nitz("26/10/26 13:00:00 320 0",&epoch,&zone)!=0);
|
||||
assert(parse_nitz("24/02/29 23:59:59 +00 00",&epoch,&zone)==0);
|
||||
assert(parse_nitz("26/02/29 00:00:00 +00 00",&epoch,&zone)!=0);
|
||||
assert(parse_nitz("38/01/19 03:14:07 +00 00",&epoch,&zone)==0 && epoch==2147483647);
|
||||
assert(parse_nitz("38/01/19 03:14:08 +00 00",&epoch,&zone)!=0);
|
||||
const char *bad[]={"", "26/10/26 13:00:00", "23/12/31 23:59:59 +00 00",
|
||||
"26/00/26 13:00:00 +00 00", "26/13/26 13:00:00 +00 00",
|
||||
"26/10/00 13:00:00 +00 00", "26/04/31 13:00:00 +00 00",
|
||||
"26/10/26 24:00:00 +00 00", "26/10/26 13:60:00 +00 00",
|
||||
"26/10/26 13:00:60 +00 00", "26/10/26 13:00:00 +57 00",
|
||||
"26/10/26 13:00:00 -49 00", "26/10/26 13:00:00 +32 03",
|
||||
"26/10/26 13:00:00 +32 junk",
|
||||
"26/10/26 13:00:00 +32 00junk", "x6/10/26 13:00:00 +32 00"};
|
||||
for (unsigned int i=0;i<sizeof(bad)/sizeof(bad[0]);++i)
|
||||
assert(parse_nitz(bad[i],&epoch,&zone)!=0);
|
||||
assert(calendar_epoch(2024,2,29,23,59,59,&value)==0);
|
||||
long long next;
|
||||
assert(calendar_epoch(2024,3,1,0,0,0,&next)==0 && next-value==1);
|
||||
}
|
||||
int main(int argc,char **argv) {
|
||||
assert(argc==2); mode=atoi(argv[1]); check_parser();
|
||||
if (mode==12) zone_minutes=-330;
|
||||
if (mode==24) zone_minutes=0;
|
||||
if (mode==13) ticks=UINT32_MAX-199U;
|
||||
app_time_calibrate_start(); app_time_calibrate_start();
|
||||
assert(creates==(mode==19 ? 2 : 1));
|
||||
if ((mode>=1 && mode<=5) || mode==7 || mode==15 || mode==17 || mode==19 || mode==25)
|
||||
assert(sets==0);
|
||||
else assert(sets==1);
|
||||
if (mode==1 || mode==19) {assert(queries==0 && sleeps==0);}
|
||||
else if ((mode>=2 && mode<=5) || mode==17 || mode==23) {assert(queries==3 && sleeps==2);}
|
||||
else if (mode==16) {assert(queries==2 && sleeps==1);}
|
||||
else {assert(queries==1 && sleeps==0);}
|
||||
if (mode==22) {
|
||||
assert(saved.tm_year==2026 && saved.tm_mon==10 && saved.tm_mday==26);
|
||||
assert(saved.tm_hour==16 && saved.tm_min==0 && saved.tm_sec==10);
|
||||
assert(app_epoch==1793001610); /* Local RTC +8, UTC remains unshifted. */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''.replace('APP_SOURCE', str(APP / 'app_time_calibrate.c'))
|
||||
(work / 'test.c').write_text(harness)
|
||||
compiler = shutil.which('clang') or shutil.which('cc')
|
||||
if not compiler:
|
||||
raise SystemExit('A native C compiler is required')
|
||||
subprocess.run([compiler, '-std=c99', '-Wall', '-Wextra', '-Werror',
|
||||
'-fsanitize=address,undefined', '-I', str(work), '-I', str(APP / 'inc'),
|
||||
str(work / 'test.c'), '-o', str(work / 'test')], check=True)
|
||||
cases = {0: 'PASS source=nitz', 1: 'FAIL stage=sdk_api_unavailable',
|
||||
2: 'FAIL stage=no_valid_nitz', 3: 'FAIL stage=no_valid_nitz',
|
||||
4: 'FAIL stage=no_valid_nitz', 5: 'FAIL stage=no_valid_nitz',
|
||||
6: 'PASS source=nitz', 7: 'FAIL stage=local_calendar',
|
||||
8: 'FAIL stage=app_clock_verification', 9: 'FAIL stage=app_clock_verification',
|
||||
10: 'FAIL stage=app_clock_verification', 11: 'FAIL stage=app_clock_verification',
|
||||
12: 'PASS source=nitz', 13: 'PASS source=nitz', 14: 'PASS source=nitz',
|
||||
15: 'FAIL stage=local_calendar', 16: 'PASS source=nitz',
|
||||
17: 'FAIL stage=no_valid_nitz', 18: 'PASS source=nitz',
|
||||
19: 'FAIL stage=create_task', 20: 'FAIL stage=app_clock_verification',
|
||||
21: 'PASS source=nitz', 22: 'PASS source=nitz', 23: 'PASS source=nitz',
|
||||
24: 'PASS source=nitz', 25: 'FAIL stage=local_calendar'}
|
||||
for mode, expected in cases.items():
|
||||
log = subprocess.check_output([str(work / 'test'), str(mode)], text=True)
|
||||
assert expected in log, log
|
||||
assert '[APP-SYS-PROBE]' not in log, log
|
||||
assert 'stage=freshness' not in log, log
|
||||
if expected.startswith('PASS'):
|
||||
assert 'age=unknown first_valid_after_pdp=1 max_rtc_writes=1' in log, log
|
||||
else:
|
||||
assert '[APP-TIME] PASS ' not in log, log
|
||||
print('PASS: parser validation and 26 first-snapshot/retry/timezone/RTC scenarios (ASan/UBSan)')
|
||||
@@ -38,7 +38,6 @@ static int queued, disconnected, flags_reset, mutex_locked, player_mq_couter, va
|
||||
static uint64_t now;
|
||||
static unsigned int gSequenceNumber;
|
||||
static int end_result;
|
||||
static char sessionId[33] = "test-session";
|
||||
typedef struct { unsigned int sequenceNumber; int packetType, dataSize; } AudioPacket;
|
||||
static int ql_rtos_mutex_lock(ql_mutex_t m, unsigned int t) {
|
||||
(void)m; (void)t; assert(!mutex_locked); mutex_locked = 1; return 0;
|
||||
@@ -67,7 +66,7 @@ static void talkingq_ws_disconnect(void) {
|
||||
static int sendRecordingAudioPacket(AudioPacket *p) { assert(p->packetType == 2); return end_result; }
|
||||
'''
|
||||
harness += source[state_start:state_end]
|
||||
for name in ['voice_diag_snapshot', 'conversation_try_begin', 'sendEndPacket', 'conversation_check_timeout', 'conversation_accept_voice_message', 'conversation_finish_startup_prompt', 'conversation_find_session_id', 'role_notice_is_url', 'role_notice_start_locked', 'role_notice_poll_locked', 'app_role_notice_handle_url', 'app_role_notice_before_finish', 'role_notice_finished']:
|
||||
for name in ['conversation_try_begin', 'sendEndPacket', 'conversation_check_timeout', 'conversation_accept_voice_message', 'conversation_finish_startup_prompt', 'conversation_find_session_id', 'role_notice_is_url', 'role_notice_start_locked', 'role_notice_poll_locked', 'app_role_notice_handle_url', 'app_role_notice_before_finish', 'role_notice_finished']:
|
||||
harness += function(name)
|
||||
harness += r'''
|
||||
static void reset(void) {
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
"""Run the actual GNSS parser and power-state paths with logging off and on."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
app = root / 'ql-application/threadx/interface/custom_app'
|
||||
source = (app / 'gnss.c').read_text()
|
||||
source = re.sub(r'^#include "(?!app_gnss_log.h")[^"]+"\n', '', source, flags=re.M)
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned char u8;
|
||||
typedef void *ql_task_t;
|
||||
typedef enum { GNSS_MODE_FAST, GNSS_MODE_SLOW } gnss_mode_t;
|
||||
typedef struct { double latitude, longitude; int is_valid; char raw_nmea[512]; } gps_location_info_t;
|
||||
enum { AGPS_GNSS_ENABLE, APFLASH_GNSS_ENABLE, PACKET_MODE };
|
||||
static unsigned int now;
|
||||
static int scenario, opened, closed, printed;
|
||||
static jmp_buf stop;
|
||||
static unsigned int hal_os_up_time_ms(void) { return now; }
|
||||
static int test_printf(const char *format, ...) { (void)format; printed++; return 0; }
|
||||
#define printf test_printf
|
||||
static int ql_gnss_close(void) { closed++; return 0; }
|
||||
static int ql_gnss_open(void (*cb)(char *, int)) { (void)cb; opened++; return 0; }
|
||||
static void app_set_app_operation(int value) { assert(value == 1); }
|
||||
static void ql_gnss_cfgsys_set(int value) { (void)value; }
|
||||
static void ql_gnss_cfgmsg_set(int value) { (void)value; }
|
||||
static void ql_gnss_agps_cfg(int value, int extra) { (void)value; (void)extra; }
|
||||
static void ql_gnss_apflash_cfg(int value) { (void)value; }
|
||||
static void ql_set_gnss_nmea_mode(int value) { (void)value; }
|
||||
static void ql_rtos_task_sleep_s(int seconds);
|
||||
static void ql_rtos_task_create(ql_task_t *task, int stack, int priority, char *name, void (*entry)(void *), void *arg) {
|
||||
(void)task; (void)stack; (void)priority; (void)name; (void)entry; (void)arg;
|
||||
}
|
||||
'''
|
||||
harness += source
|
||||
harness += r'''
|
||||
#undef printf
|
||||
static void ql_rtos_task_sleep_s(int seconds) {
|
||||
if (closed) longjmp(stop, 1);
|
||||
now += (unsigned int)seconds * 1000;
|
||||
if (scenario == 1) g_gnss_sat_count = 2;
|
||||
if (scenario == 2) { g_gnss_sat_count = 4; g_gps_location.is_valid = 1; }
|
||||
assert(now < 1000000);
|
||||
}
|
||||
int main(void) {
|
||||
(void)test_printf;
|
||||
char data[] = "$GPGGA,120000,2311.71298,N,11328.63118,E,1,08,0.9,10,M,0,M,0,0*00\r\n"
|
||||
"$GPGSV,1,1,08,01,10,10,30*00\r\n";
|
||||
quec_gnss_callback(data, (int)strlen(data));
|
||||
assert(g_gps_location.is_valid && g_gnss_sat_count == 8);
|
||||
assert(fabs(g_gps_location.latitude - 23.1952163333) < 0.000001);
|
||||
assert(fabs(g_gps_location.longitude - 113.4771863333) < 0.000001);
|
||||
assert(strncmp(g_gps_location.raw_nmea, "$GPGGA,", 7) == 0);
|
||||
print_gps_location_info();
|
||||
set_gnss_mode(GNSS_MODE_FAST);
|
||||
assert(get_gnss_mode() == GNSS_MODE_FAST && get_gnss_interval() == 180);
|
||||
for (scenario = 0; scenario < 4; scenario++) {
|
||||
now = 1; opened = closed = 0; gnss_is_open = 0;
|
||||
g_gnss_mode = scenario == 3 ? GNSS_MODE_FAST : GNSS_MODE_SLOW;
|
||||
if (!setjmp(stop)) quec_gnss_test(NULL);
|
||||
assert(opened == 1 && closed == 1 && get_gnss_open_status() == 0);
|
||||
}
|
||||
#if APP_GNSS_LOG_ENABLED
|
||||
assert(printed > 0);
|
||||
#else
|
||||
assert(printed == 0);
|
||||
#endif
|
||||
puts("PASS: GNSS parsing and all four hardware-close paths preserved; expected log visibility");
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix='banban-gnss-log-') as directory:
|
||||
cfile = Path(directory) / 'gnss-test.c'
|
||||
cfile.write_text(harness)
|
||||
for enabled in (0, 1):
|
||||
exe = Path(directory) / f'gnss-test-{enabled}'
|
||||
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror',
|
||||
'-Wno-unused-parameter', '-Wno-unused-variable', '-Wno-sign-compare',
|
||||
'-fsanitize=address,undefined', f'-DAPP_GNSS_LOG_ENABLED={enabled}',
|
||||
'-I' + str(app / 'inc'), str(cfile), '-o', str(exe)], check=True)
|
||||
subprocess.run([str(exe)], check=True)
|
||||
@@ -1,112 +0,0 @@
|
||||
"""Compile actual microphone, queue and receive diagnostics with scripted inputs."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
app = root / 'ql-application/threadx/interface/custom_app'
|
||||
voice = (app / 'ws_talkingq.c').read_text()
|
||||
mic = (app / 'vad_det.c').read_text()
|
||||
|
||||
def function(source, signature):
|
||||
start = source.index(signature)
|
||||
opening = source.index('{', start)
|
||||
depth, end = 1, opening + 1
|
||||
while depth:
|
||||
depth += (source[end] == '{') - (source[end] == '}')
|
||||
end += 1
|
||||
return source[start:end] + '\n'
|
||||
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define VAD_FRAME_BYTES 640
|
||||
#define DEVICE_ID_SIZE 32
|
||||
#define SESSION_ID_SIZE 33
|
||||
#define RLOGE(...) ((void)0)
|
||||
typedef int rws_socket;
|
||||
static int g_mic_working;
|
||||
static int g_state, ring_result = 640, fail_alloc, queue_result, dispatched;
|
||||
static unsigned int conversation_round = 7;
|
||||
static int active_session_valid = 1;
|
||||
static char sessionId[33] = "session", deviceId[32] = "device";
|
||||
static void *ring_sender;
|
||||
static uint64_t now = 1000;
|
||||
static int mutex_locked;
|
||||
static uint64_t hal_os_up_time_ms(void) { return now; }
|
||||
static void conversation_lock(void) { assert(!mutex_locked); mutex_locked = 1; }
|
||||
static void conversation_unlock(void) { assert(mutex_locked); mutex_locked = 0; }
|
||||
static int hal_os_rb_write(void *ring, const void *data, int len, unsigned int timeout) {
|
||||
(void)ring; (void)data; (void)len; assert(timeout == 25); return ring_result;
|
||||
}
|
||||
static void *test_malloc(size_t size) { return fail_alloc ? NULL : malloc(size); }
|
||||
static int talkingq_ws_send_bin(uint8_t *data, int len) {
|
||||
uint32_t seq;
|
||||
assert(len >= DEVICE_ID_SIZE + SESSION_ID_SIZE + 12);
|
||||
memcpy(&seq, data + DEVICE_ID_SIZE + SESSION_ID_SIZE, 4);
|
||||
assert(seq == 9);
|
||||
return queue_result;
|
||||
}
|
||||
typedef struct { uint32_t sequenceNumber, packetType, dataSize; uint8_t *data; } AudioPacket;
|
||||
static int conversation_accept_voice_message(const char *text) { return strstr(text, "old") == NULL; }
|
||||
static void conversation_log_error_message(const char *text, unsigned int len) { (void)text; (void)len; }
|
||||
static void onmessage_text_locked(rws_socket socket, const char *text, unsigned int len) {
|
||||
(void)socket; (void)text; (void)len; assert(mutex_locked); dispatched++;
|
||||
}
|
||||
'''
|
||||
start = voice.index('static struct {')
|
||||
end = voice.index('} voice_diag;', start) + len('} voice_diag;')
|
||||
harness += voice[start:end] + '\n'
|
||||
start = mic.index('static struct {')
|
||||
end = mic.index('extern int audio_packet_type;', start)
|
||||
harness += mic[start:end]
|
||||
harness += '#define malloc test_malloc\n'
|
||||
harness += function(voice, 'int sendRecordingAudioPacket(AudioPacket *packet) {')
|
||||
harness += '#undef malloc\n'
|
||||
harness += function(voice, 'static void onmessage_text(rws_socket socket,')
|
||||
harness += function(voice, 'static void onmessage_bin(rws_socket socket,')
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
uint8_t pcm[640] = {0};
|
||||
AudioPacket packet = {9, 0, 4, pcm};
|
||||
mic_diag.started_ms = now;
|
||||
mic_diag_read(pcm, 640, 0);
|
||||
assert(mic_diag.reads == 1 && mic_diag.zero_frames == 1 && mic_diag.peak == 0);
|
||||
/* Full-scale negative input must avoid int16 absolute-value overflow. */
|
||||
pcm[0] = 0; pcm[1] = 128;
|
||||
mic_diag_read(pcm, 640, 0);
|
||||
assert(mic_diag.peak == 32768 && mic_diag.abs_sum == 32768 && mic_diag.samples == 640);
|
||||
mic_diag_read(pcm, 0, -1);
|
||||
assert(mic_diag.short_reads == 1 && mic_diag.read_errors == 1 && mic_diag.zero_frames == 1);
|
||||
mic_diag_read(pcm, 10000, 0); /* Diagnostic sampling stays inside the buffer. */
|
||||
assert(mic_diag.samples == 960 && mic_diag.short_reads == 2);
|
||||
ring_result = 640; assert(mic_diag_write(pcm, 640) == 640 && !mic_diag.ring_drops);
|
||||
ring_result = -1; assert(mic_diag_write(pcm, 640) == -1);
|
||||
ring_result = 320; assert(mic_diag_write(pcm, 640) == 320);
|
||||
assert(mic_diag.ring_drops == 2 && mic_diag.ring_drop_bytes == 960);
|
||||
mic_diag_log("test");
|
||||
queue_result = 0; assert(sendRecordingAudioPacket(&packet) == 0);
|
||||
assert(voice_diag.tx_packets == 1 && voice_diag.tx_bytes == 4 && !voice_diag.tx_failed);
|
||||
queue_result = -1; assert(sendRecordingAudioPacket(&packet) == -1);
|
||||
fail_alloc = 1; assert(sendRecordingAudioPacket(&packet) == -1);
|
||||
assert(voice_diag.tx_packets == 1 && voice_diag.tx_bytes == 4 && voice_diag.tx_failed == 2);
|
||||
onmessage_text(0, "START_ACK", 9);
|
||||
onmessage_text(0, "END_ACK", 7);
|
||||
onmessage_text(0, "unknown backend response", 24);
|
||||
onmessage_text(0, "old TTS_START", 13);
|
||||
assert(voice_diag.start_ack == 1 && voice_diag.end_ack == 1 && voice_diag.rx_text == 4);
|
||||
assert(dispatched == 3 && !mutex_locked);
|
||||
now = 2000; onmessage_bin(0, pcm, 640);
|
||||
assert(voice_diag.rx_bin == 1 && voice_diag.last_rx_ms == now && !mutex_locked);
|
||||
puts("PASS: microphone silence/level/short reads/errors, PCM drops, queue failure/allocation failure, ACK visibility, unknown/rejected replies, ignored binary replies");
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix='banban-voice-diag-') as directory:
|
||||
cfile = Path(directory) / 'diag.c'
|
||||
exe = Path(directory) / 'diag-test'
|
||||
cfile.write_text(harness)
|
||||
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', '-fsanitize=address,undefined', str(cfile), '-o', str(exe)], check=True)
|
||||
subprocess.run([str(exe)], check=True)
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Test the actual timed handshake loop with scripted SDK steps and 5 ms ticks."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
source = (root / 'ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c').read_bytes().decode('latin1')
|
||||
@@ -16,12 +15,7 @@ typedef struct { int ret, next_state; unsigned int duration; } step;
|
||||
static step steps[16];
|
||||
static int count, calls, sleeps;
|
||||
static unsigned int ticks;
|
||||
static char diagnostic[1024];
|
||||
static void test_log(const char *fmt, ...) {
|
||||
va_list args;
|
||||
if (!strstr(fmt, "[WSS-DIAG]")) return;
|
||||
va_start(args, fmt); vsnprintf(diagnostic, sizeof(diagnostic), fmt, args); va_end(args);
|
||||
}
|
||||
static void test_log(const char *fmt, ...) { (void)fmt; }
|
||||
#define LOG_I test_log
|
||||
static unsigned int ql_rtos_get_systicks(void) { return ticks; }
|
||||
static void rws_thread_sleep(unsigned int ms) { ticks += ms / 5; ++sleeps; }
|
||||
@@ -58,11 +52,8 @@ int main(void) {
|
||||
assert(rws_tls_handshake_timed(&ctx) == 0 && calls == 2 && sleeps == 0);
|
||||
reset(&ctx); steps[0] = (step){-99, MBEDTLS_SSL_SERVER_CERTIFICATE, 1}; count = 1;
|
||||
assert(rws_tls_handshake_timed(&ctx) == -99 && calls == 1 && sleeps == 0);
|
||||
assert(strstr(diagnostic, "result=failed phase=server certificate") && strstr(diagnostic, "ret=-99"));
|
||||
reset(&ctx); ctx.state = MBEDTLS_SSL_SERVER_HELLO;
|
||||
reset(&ctx);
|
||||
assert(rws_tls_handshake_timed(&ctx) == MBEDTLS_ERR_SSL_TIMEOUT && ticks == 4000 && calls < 2100);
|
||||
assert(strstr(diagnostic, "result=timeout phase=server hello") && strstr(diagnostic, "elapsed_ms=20000"));
|
||||
assert(strstr(diagnostic, "last_step=-26880") && strstr(diagnostic, "want_write=0"));
|
||||
reset(&ctx); ticks = (unsigned int)-1000;
|
||||
assert(rws_tls_handshake_timed(&ctx) == MBEDTLS_ERR_SSL_TIMEOUT && calls < 2100);
|
||||
reset(&ctx); steps[0] = (step){0, MBEDTLS_SSL_SERVER_CERTIFICATE, 4001}; count = 1;
|
||||
@@ -78,12 +69,7 @@ output.mkdir(parents=True, exist_ok=True)
|
||||
cfile = output / 'test_wss_handshake.c'
|
||||
cfile.write_text(harness, encoding='utf-8')
|
||||
exe = output / 'test_wss_handshake.exe'
|
||||
if os.name == "nt":
|
||||
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
|
||||
'/I' + str(root / 'ql-application/threadx/common/include'),
|
||||
'/Fo' + str(output / 'test_wss_handshake.obj'), '/Fe' + str(exe)], check=True)
|
||||
else:
|
||||
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror',
|
||||
'-I' + str(root / 'ql-application/threadx/common/include'),
|
||||
str(cfile), '-o', str(exe)], check=True)
|
||||
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
|
||||
'/I' + str(root / 'ql-application/threadx/common/include'),
|
||||
'/Fo' + str(output / 'test_wss_handshake.obj'), '/Fe' + str(exe)], check=True)
|
||||
subprocess.run([str(exe)], check=True)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Compile actual SDK-independent function bodies with scripted TLS/RTOS stubs."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
lib = root / 'ql-application/threadx/interface/custom_app/lib/librws/src'
|
||||
@@ -76,9 +75,6 @@ output.mkdir(parents=True, exist_ok=True)
|
||||
cfile = output / 'test_wss_io.c'
|
||||
cfile.write_text(harness, encoding='utf-8')
|
||||
exe = output / 'test_wss_io.exe'
|
||||
if os.name == "nt":
|
||||
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
|
||||
'/Fo' + str(output / 'test_wss_io.obj'), '/Fe' + str(exe)], check=True)
|
||||
else:
|
||||
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', str(cfile), '-o', str(exe)], check=True)
|
||||
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
|
||||
'/Fo' + str(output / 'test_wss_io.obj'), '/Fe' + str(exe)], check=True)
|
||||
subprocess.run([str(exe)], check=True)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Exercise the real receive/upgrade loop with delayed TLS records."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
lib = root / 'ql-application/threadx/interface/custom_app/lib/librws'
|
||||
@@ -18,8 +17,6 @@ harness = r'''
|
||||
#define MBEDTLS_ERR_SSL_WANT_READ -2
|
||||
#define MBEDTLS_ERR_SSL_WANT_WRITE -3
|
||||
#define MBEDTLS_ERR_NET_RECV_FAILED -4
|
||||
#define MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY -30848
|
||||
#define MBEDTLS_ERR_SSL_TIMEOUT -26624
|
||||
#define WSAEWOULDBLOCK 11
|
||||
#define WSAEINPROGRESS 115
|
||||
#define RWS_RECEIVE_BUFF_SIZE 1024
|
||||
@@ -40,16 +37,14 @@ typedef struct {
|
||||
char *payload_buff, *received;
|
||||
size_t received_size, received_len;
|
||||
int socket, command, is_connected;
|
||||
unsigned int diag_rx_calls, diag_rx_bytes;
|
||||
const char *error;
|
||||
} _rws_socket;
|
||||
static int replies[16], reply_count, calls, closes;
|
||||
static size_t offset;
|
||||
static unsigned int ticks;
|
||||
static const char good_response[] =
|
||||
static const char response[] =
|
||||
"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\nDATA";
|
||||
static const char *response = good_response;
|
||||
static unsigned int ql_rtos_get_systicks(void) { return ticks; }
|
||||
static void rws_thread_sleep(int ms) { ticks += (unsigned int)ms; }
|
||||
static void rws_error_delete_clean(const char **e) { *e = NULL; }
|
||||
@@ -67,7 +62,7 @@ static int mbedtls_ssl_read(int *ctx, unsigned char *buf, size_t size) {
|
||||
int ret = calls < reply_count ? replies[calls] : MBEDTLS_ERR_SSL_WANT_READ;
|
||||
(void)ctx; ++calls;
|
||||
if (ret > 0) {
|
||||
assert((size_t)ret <= size && offset + (size_t)ret <= strlen(response));
|
||||
assert((size_t)ret <= size && offset + (size_t)ret <= sizeof(response) - 1);
|
||||
memcpy(buf, response + offset, (size_t)ret); offset += (size_t)ret;
|
||||
}
|
||||
return ret;
|
||||
@@ -81,7 +76,7 @@ static void reset(_rws_socket *s, tls *ctx) {
|
||||
s->expected_accept = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
|
||||
s->payload_buff = (char *)malloc(RWS_RECEIVE_BUFF_SIZE); assert(s->payload_buff);
|
||||
s->command = COMMAND_WAIT_HANDSHAKE_RESPONCE;
|
||||
calls = closes = reply_count = 0; offset = 0; ticks = 0; response = good_response;
|
||||
calls = closes = reply_count = 0; offset = 0; ticks = 0;
|
||||
}
|
||||
int main(void) {
|
||||
tls ctx = {0}; _rws_socket s = {0}; int want;
|
||||
@@ -89,7 +84,7 @@ int main(void) {
|
||||
reset(&s, &ctx); replies[0] = want; reply_count = 1;
|
||||
assert(!rws_socket_recv(&s));
|
||||
assert(!s.error && s.command == COMMAND_WAIT_HANDSHAKE_RESPONCE && closes == 0);
|
||||
replies[1] = 17; replies[2] = want; replies[3] = (int)strlen(response) - 17;
|
||||
replies[1] = 17; replies[2] = want; replies[3] = (int)sizeof(response) - 1 - 17;
|
||||
reply_count = 4;
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
assert(s.is_connected && !s.error && s.command == COMMAND_INFORM_CONNECTED);
|
||||
@@ -99,7 +94,6 @@ int main(void) {
|
||||
reset(&s, &ctx);
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
assert(!s.is_connected && s.error && closes == 1 && ticks >= 2000 && calls < 300);
|
||||
assert(!strcmp(s.error, "no_response_timeout"));
|
||||
free(s.payload_buff);
|
||||
reset(&s, &ctx); ticks = (unsigned int)-1000;
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
@@ -113,25 +107,11 @@ int main(void) {
|
||||
reset(&s, NULL); rws_socket_wait_handshake_responce(&s);
|
||||
assert(s.error && closes == 1 && calls == 0); free(s.payload_buff);
|
||||
reset(&s, &ctx); s.expected_accept = "wrong";
|
||||
replies[0] = (int)strlen(response); reply_count = 1;
|
||||
replies[0] = (int)sizeof(response) - 1; reply_count = 1;
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
assert(!s.is_connected && s.error && closes == 1); free(s.payload_buff);
|
||||
assert(!strcmp(s.error, "invalid_upgrade_response"));
|
||||
reset(&s, &ctx); response = "HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n";
|
||||
replies[0] = (int)strlen(response); reply_count = 1;
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
assert(!s.is_connected && closes == 1 && !strcmp(s.error, "http_rejected")); free(s.payload_buff);
|
||||
reset(&s, &ctx); replies[0] = 17; reply_count = 1;
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
assert(!s.is_connected && closes == 1 && !strcmp(s.error, "incomplete_headers_timeout")); free(s.payload_buff);
|
||||
reset(&s, &ctx); replies[0] = MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; reply_count = 1;
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
assert(closes == 1 && !strcmp(s.error, "peer_close_notify")); free(s.payload_buff);
|
||||
reset(&s, &ctx); replies[0] = MBEDTLS_ERR_SSL_TIMEOUT; reply_count = 1;
|
||||
rws_socket_wait_handshake_responce(&s);
|
||||
assert(closes == 1 && !strcmp(s.error, "tls_read_timeout")); free(s.payload_buff);
|
||||
free(s.received);
|
||||
puts("PASS: actual TLS receive/upgrade loop: WANT_READ/WRITE, split HTTP 101, trailing frame bytes, timeout/wrap, EOF/fatal, null TLS, invalid Accept, HTTP rejection, partial-header timeout, close_notify, TLS timeout");
|
||||
puts("PASS: actual TLS receive/upgrade loop: WANT_READ/WRITE, split HTTP 101, trailing frame bytes, timeout/wrap, EOF/fatal, null TLS, invalid Accept");
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
@@ -140,10 +120,7 @@ output.mkdir(parents=True, exist_ok=True)
|
||||
cfile = output / 'test_wss_receive.c'
|
||||
cfile.write_text(harness, encoding='utf-8')
|
||||
exe = output / 'test_wss_receive.exe'
|
||||
if os.name == "nt":
|
||||
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/wd4018', '/TC',
|
||||
'/I' + str(lib / 'inc'), str(cfile), str(lib / 'src/rws_handshake.c'),
|
||||
'/Fo' + str(output) + '/', '/Fe' + str(exe)], check=True)
|
||||
else:
|
||||
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', '-Wno-sign-compare', '-I' + str(lib / 'inc'), str(cfile), str(lib / 'src/rws_handshake.c'), '-o', str(exe)], check=True)
|
||||
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/wd4018', '/TC',
|
||||
'/I' + str(lib / 'inc'), str(cfile), str(lib / 'src/rws_handshake.c'),
|
||||
'/Fo' + str(output) + '/', '/Fe' + str(exe)], check=True)
|
||||
subprocess.run([str(exe)], check=True)
|
||||
|
||||
Reference in New Issue
Block a user