chore: archive diagnostics and project documentation
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
# OTA 周期检查与 HTTPS 下载稳定性修复说明
|
||||
|
||||
## 1. 提交信息
|
||||
|
||||
```text
|
||||
451d989 fix(ota): stabilize periodic checks and HTTPS downloads
|
||||
```
|
||||
|
||||
本提交解决三个相关问题:
|
||||
|
||||
1. 首次 OTA 检查完成后,后续周期检查可能永远不再执行。
|
||||
2. 下载腾讯云 COS 固件时,TLS 接收缓冲区可能不足,导致 HTTPS 响应头读取失败。
|
||||
3. 固件版本仍为 `1.0.0`,无法作为新的 OTA 测试目标与旧固件区分。
|
||||
|
||||
## 2. 问题原因
|
||||
|
||||
### 2.1 后续 OTA 检查被常驻云端 task 永久阻塞
|
||||
|
||||
多模态语音使用的是常驻双工 task。一次对话结束后,WebSocket 和云端 task 不会立即销毁,而是保持在 `Listening` 状态等待下一轮对话。
|
||||
|
||||
旧的 OTA 空闲判断要求同时满足:
|
||||
|
||||
```c
|
||||
return !state.has_active_task &&
|
||||
!state.client_speech_active &&
|
||||
!state.remote_responding &&
|
||||
!state.local_responding_started;
|
||||
```
|
||||
|
||||
其中 `has_active_task` 的含义是“云端 task 是否存在”,不是“用户是否正在对话”。
|
||||
|
||||
正常多轮对话空闲时的状态可能是:
|
||||
|
||||
```text
|
||||
has_active_task=1
|
||||
server_listening=1
|
||||
client_speech_active=0
|
||||
remote_responding=0
|
||||
local_responding_started=0
|
||||
```
|
||||
|
||||
此时用户没有说话,设备也没有播放回答,实际上可以安全进行 OTA 检查。但因为 `has_active_task=1`,旧逻辑始终认为设备忙碌。
|
||||
|
||||
结果是:
|
||||
|
||||
```text
|
||||
首次开机检查成功
|
||||
-> 云端 task 建立并常驻
|
||||
-> OTA 周期到期
|
||||
-> 空闲判断持续失败
|
||||
-> 每秒继续等待资源
|
||||
-> 后续检查永远无法开始
|
||||
```
|
||||
|
||||
所以即使把检查间隔改成 5 分钟,也不会在 5 分钟后看到第二次请求。这不是计时器没有到,而是计时器到期后一直卡在资源等待阶段。
|
||||
|
||||
### 2.2 等待资源时缺少明确诊断信息
|
||||
|
||||
旧代码进入 `WAITING_FOR_RESOURCES` 后只更新内部状态,没有打印具体阻塞原因。从串口日志看起来像 OTA worker 没有继续运行,很难区分以下情况:
|
||||
|
||||
- 正在录音;
|
||||
- 正在播放云端回复;
|
||||
- 本地回复事件尚未结束;
|
||||
- 仅仅存在一个空闲的常驻 task;
|
||||
- 获取多模态状态失败。
|
||||
|
||||
如果每秒都打印状态又会产生大量重复日志,因此需要“一次等待周期只打印一次”的诊断机制。
|
||||
|
||||
### 2.3 OTA HTTPS 响应读取失败
|
||||
|
||||
固件下载地址使用腾讯云 COS 的 HTTPS 链接。日志中出现过:
|
||||
|
||||
```text
|
||||
esp-tls-mbedtls: read error :-0x7100
|
||||
platform_ota: OTA response header fetch failed
|
||||
cp_ota: OTA install failed: ESP_ERR_INVALID_RESPONSE
|
||||
```
|
||||
|
||||
TLS 握手和 HTTP 响应读取需要一个足够大的入站缓冲区。原配置为:
|
||||
|
||||
```text
|
||||
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=8192
|
||||
```
|
||||
|
||||
当服务端证书链、TLS 记录或响应头超过当前可接收范围时,mbedTLS 无法完整读取记录,最终表现为 HTTPS 响应无效,而不是固件 SHA-256 错误。
|
||||
|
||||
### 2.4 固件版本无法区分
|
||||
|
||||
设备向 OTA 服务端上报 `version.txt` 中的版本。旧值是:
|
||||
|
||||
```text
|
||||
1.0.0
|
||||
```
|
||||
|
||||
如果新编译固件仍使用旧版本号,服务端无法可靠判断设备是否已经升级,也可能重复下发同一版本。
|
||||
|
||||
## 3. 解决方式
|
||||
|
||||
### 3.1 用“真实交互状态”判断 OTA 是否可以运行
|
||||
|
||||
修复后的判断不再把空闲常驻 task 当作繁忙条件:
|
||||
|
||||
```c
|
||||
static bool control_plane_ota_runtime_is_idle(void)
|
||||
{
|
||||
if (!control_plane_voice_turn_is_idle()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
control_plane_multimodal_runtime_state_t state = {0};
|
||||
if (control_plane_multimodal_get_runtime_state(&state) != ESP_OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A duplex cloud task may remain in Listening between voice turns. OTA
|
||||
* can retire and later restore that task once all real interaction has
|
||||
* stopped. */
|
||||
return !state.client_speech_active &&
|
||||
!state.remote_responding &&
|
||||
!state.local_responding_started;
|
||||
}
|
||||
```
|
||||
|
||||
这里仍然保留了四层保护:
|
||||
|
||||
1. `control_plane_voice_turn_is_idle()`:设备按键/本地唤醒控制的整轮语音流程必须处于空闲。
|
||||
2. `client_speech_active`:设备不能正在向云端发送用户语音。
|
||||
3. `remote_responding`:云端不能正在下发回答。
|
||||
4. `local_responding_started`:本地播放链路不能仍处于回复阶段。
|
||||
|
||||
只移除了 `!state.has_active_task`。OTA 获取资源窗口后会主动停止这个空闲 task,检查完成后再恢复 WebSocket 和本地唤醒,因此不会同时使用同一套 TLS、音频和内存资源。
|
||||
|
||||
### 3.2 增加一次性资源等待日志
|
||||
|
||||
worker 增加一个局部标志:
|
||||
|
||||
```c
|
||||
bool initial_check_pending = true;
|
||||
bool resource_wait_logged = false;
|
||||
```
|
||||
|
||||
当检查已经到期但语音仍忙时,只打印一次当前状态:
|
||||
|
||||
```c
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
control_plane_ota_set_state(
|
||||
CONTROL_PLANE_OTA_STATE_WAITING_FOR_RESOURCES,
|
||||
ESP_OK,
|
||||
-1);
|
||||
|
||||
if (!resource_wait_logged) {
|
||||
resource_wait_logged = true;
|
||||
control_plane_multimodal_runtime_state_t state = {0};
|
||||
|
||||
if (control_plane_multimodal_get_runtime_state(&state) == ESP_OK) {
|
||||
ESP_LOGI(TAG,
|
||||
"OTA check due; waiting for voice interaction: "
|
||||
"task=%d listening=%d speech=%d responding=%d "
|
||||
"local_responding=%d",
|
||||
state.has_active_task ? 1 : 0,
|
||||
state.server_listening ? 1 : 0,
|
||||
state.client_speech_active ? 1 : 0,
|
||||
state.remote_responding ? 1 : 0,
|
||||
state.local_responding_started ? 1 : 0);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "OTA check due; waiting for runtime resources");
|
||||
}
|
||||
}
|
||||
|
||||
control_plane_ota_wait_interruptible(1000U);
|
||||
continue;
|
||||
}
|
||||
|
||||
resource_wait_logged = false;
|
||||
```
|
||||
|
||||
网络断开或资源重新可用后会清除标志。下一次独立的等待周期仍会打印一次,不会丢失后续诊断信息。
|
||||
|
||||
### 3.3 将 TLS 入站缓冲区提高到 16 KB
|
||||
|
||||
配置修改为:
|
||||
|
||||
```text
|
||||
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
|
||||
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
|
||||
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
|
||||
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096
|
||||
```
|
||||
|
||||
含义如下:
|
||||
|
||||
- 入站缓冲区从 8 KB 增加到 16 KB,用于接收更大的证书链、TLS 记录和 HTTP 响应。
|
||||
- 出站缓冲区仍为 4 KB,因为设备请求体较小,不需要同步扩大。
|
||||
- 动态缓冲保持启用,TLS 会话结束后相关内存可以释放,不是永久占用 16 KB。
|
||||
- 入站和出站使用非对称大小,避免不必要地同时扩大两个方向。
|
||||
|
||||
### 3.4 更新固件版本
|
||||
|
||||
```text
|
||||
1.0.0 -> 1.0.2
|
||||
```
|
||||
|
||||
设备上报版本变为 `1.0.2`。后端如果要让该设备继续升级,目标版本必须按照后端版本比较规则高于当前版本,并且固件文件内部版本也应与发布信息一致。
|
||||
|
||||
## 4. 解决成果
|
||||
|
||||
1. 周期到期后,即使云端 task 常驻在 `Listening`,OTA 仍能在真实语音空闲时执行。
|
||||
2. OTA 不会在用户录音、云端回复或本地回复期间强行抢占资源。
|
||||
3. 资源暂时不可用时,日志会明确显示是哪种语音状态阻塞检查,同时不会每秒刷屏。
|
||||
4. COS HTTPS 下载能够接收更大的 TLS 入站记录,降低响应头读取失败和 `ESP_ERR_INVALID_RESPONSE` 的概率。
|
||||
5. 新固件以 `1.0.2` 上报,服务端能够区分升级前后的设备版本。
|
||||
6. 使用 ESP-IDF 5.5.4 完整编译通过。
|
||||
|
||||
## 5. 后续影响
|
||||
|
||||
### 5.1 正向影响
|
||||
|
||||
- 5 分钟调试间隔和正式 6 小时间隔都能正常触发后续检查。
|
||||
- 常驻双工会话不再错误阻止 OTA。
|
||||
- OTA 等待语音结束后才运行,对正常对话的影响更可控。
|
||||
- HTTPS 下载兼容更大的证书链和 TLS 数据记录。
|
||||
|
||||
### 5.2 资源影响
|
||||
|
||||
TLS 入站缓冲上限增加了 8 KB。由于启用了动态缓冲,这部分主要体现在 HTTPS 会话期间的峰值内存,而不是设备全程常驻占用。
|
||||
|
||||
OTA 已经通过 voice window 暂停本地唤醒和多模态网络服务,以释放 internal RAM。后续如果新增其他会同时运行的 HTTPS 服务,仍应遵守同样的资源互斥策略。
|
||||
|
||||
### 5.3 行为影响
|
||||
|
||||
- 周期检查到期时如果用户正在对话,实际请求时间会顺延,直到语音链路空闲。
|
||||
- OTA 可以停止空闲的常驻云端 task,检查结束后会重新建立 WebSocket;这是预期行为。
|
||||
- 版本改为 `1.0.2` 后,服务端不应再向设备下发 `1.0.1` 或相同版本,除非后端明确支持强制降级。
|
||||
|
||||
### 5.4 仍需实机验证的项目
|
||||
|
||||
1. 将检查间隔设为 5 分钟,确认首次检查后还能出现第二次和第三次请求。
|
||||
2. 在检查到期前主动开始对话,确认先打印等待语音日志,对话结束后再执行 OTA。
|
||||
3. 使用 COS 固件链接进行完整下载,确认不再出现 TLS 响应头读取失败。
|
||||
4. 确认下载后的 size 和 SHA-256 校验仍然有效。
|
||||
5. 确认检查结束后本地唤醒和 WebSocket 均能恢复。
|
||||
|
||||
Reference in New Issue
Block a user