8.8 KiB
OTA 周期检查与 HTTPS 下载稳定性修复说明
1. 提交信息
451d989 fix(ota): stabilize periodic checks and HTTPS downloads
本提交解决三个相关问题:
- 首次 OTA 检查完成后,后续周期检查可能永远不再执行。
- 下载腾讯云 COS 固件时,TLS 接收缓冲区可能不足,导致 HTTPS 响应头读取失败。
- 固件版本仍为
1.0.0,无法作为新的 OTA 测试目标与旧固件区分。
2. 问题原因
2.1 后续 OTA 检查被常驻云端 task 永久阻塞
多模态语音使用的是常驻双工 task。一次对话结束后,WebSocket 和云端 task 不会立即销毁,而是保持在 Listening 状态等待下一轮对话。
旧的 OTA 空闲判断要求同时满足:
return !state.has_active_task &&
!state.client_speech_active &&
!state.remote_responding &&
!state.local_responding_started;
其中 has_active_task 的含义是“云端 task 是否存在”,不是“用户是否正在对话”。
正常多轮对话空闲时的状态可能是:
has_active_task=1
server_listening=1
client_speech_active=0
remote_responding=0
local_responding_started=0
此时用户没有说话,设备也没有播放回答,实际上可以安全进行 OTA 检查。但因为 has_active_task=1,旧逻辑始终认为设备忙碌。
结果是:
首次开机检查成功
-> 云端 task 建立并常驻
-> OTA 周期到期
-> 空闲判断持续失败
-> 每秒继续等待资源
-> 后续检查永远无法开始
所以即使把检查间隔改成 5 分钟,也不会在 5 分钟后看到第二次请求。这不是计时器没有到,而是计时器到期后一直卡在资源等待阶段。
2.2 等待资源时缺少明确诊断信息
旧代码进入 WAITING_FOR_RESOURCES 后只更新内部状态,没有打印具体阻塞原因。从串口日志看起来像 OTA worker 没有继续运行,很难区分以下情况:
- 正在录音;
- 正在播放云端回复;
- 本地回复事件尚未结束;
- 仅仅存在一个空闲的常驻 task;
- 获取多模态状态失败。
如果每秒都打印状态又会产生大量重复日志,因此需要“一次等待周期只打印一次”的诊断机制。
2.3 OTA HTTPS 响应读取失败
固件下载地址使用腾讯云 COS 的 HTTPS 链接。日志中出现过:
esp-tls-mbedtls: read error :-0x7100
platform_ota: OTA response header fetch failed
cp_ota: OTA install failed: ESP_ERR_INVALID_RESPONSE
TLS 握手和 HTTP 响应读取需要一个足够大的入站缓冲区。原配置为:
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=8192
当服务端证书链、TLS 记录或响应头超过当前可接收范围时,mbedTLS 无法完整读取记录,最终表现为 HTTPS 响应无效,而不是固件 SHA-256 错误。
2.4 固件版本无法区分
设备向 OTA 服务端上报 version.txt 中的版本。旧值是:
1.0.0
如果新编译固件仍使用旧版本号,服务端无法可靠判断设备是否已经升级,也可能重复下发同一版本。
3. 解决方式
3.1 用“真实交互状态”判断 OTA 是否可以运行
修复后的判断不再把空闲常驻 task 当作繁忙条件:
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;
}
这里仍然保留了四层保护:
control_plane_voice_turn_is_idle():设备按键/本地唤醒控制的整轮语音流程必须处于空闲。client_speech_active:设备不能正在向云端发送用户语音。remote_responding:云端不能正在下发回答。local_responding_started:本地播放链路不能仍处于回复阶段。
只移除了 !state.has_active_task。OTA 获取资源窗口后会主动停止这个空闲 task,检查完成后再恢复 WebSocket 和本地唤醒,因此不会同时使用同一套 TLS、音频和内存资源。
3.2 增加一次性资源等待日志
worker 增加一个局部标志:
bool initial_check_pending = true;
bool resource_wait_logged = false;
当检查已经到期但语音仍忙时,只打印一次当前状态:
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
配置修改为:
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 更新固件版本
1.0.0 -> 1.0.2
设备上报版本变为 1.0.2。后端如果要让该设备继续升级,目标版本必须按照后端版本比较规则高于当前版本,并且固件文件内部版本也应与发布信息一致。
4. 解决成果
- 周期到期后,即使云端 task 常驻在
Listening,OTA 仍能在真实语音空闲时执行。 - OTA 不会在用户录音、云端回复或本地回复期间强行抢占资源。
- 资源暂时不可用时,日志会明确显示是哪种语音状态阻塞检查,同时不会每秒刷屏。
- COS HTTPS 下载能够接收更大的 TLS 入站记录,降低响应头读取失败和
ESP_ERR_INVALID_RESPONSE的概率。 - 新固件以
1.0.2上报,服务端能够区分升级前后的设备版本。 - 使用 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 仍需实机验证的项目
- 将检查间隔设为 5 分钟,确认首次检查后还能出现第二次和第三次请求。
- 在检查到期前主动开始对话,确认先打印等待语音日志,对话结束后再执行 OTA。
- 使用 COS 固件链接进行完整下载,确认不再出现 TLS 响应头读取失败。
- 确认下载后的 size 和 SHA-256 校验仍然有效。
- 确认检查结束后本地唤醒和 WebSocket 均能恢复。