feat(relay): OTA可用版本,添加OTA中继阶段健康检查和APP存活验证功能

- 新增health_check_before_ota_relay函数,用于OTA中继转发阶段的健康检查,
  跳过WiFi相关检查,只检查温度等本地状态
- 添加APP存活验证阶段,OTA完成后等待GD32 APP启动并通过ping确认存活
- 实现更精确的响应帧类型匹配和轮询机制,提高协议可靠性
- 添加多个阶段的响应匹配器(is_handshake_echo_ack、is_challenge_payload_ack等)
- 优化poll_until_expected_ack_shape函数,处理残留ACK和超时情况
- 将调试日志级别从INFO降级为DBG,减少日志输出
- 修复handshake阶段重试机制,添加最大重试次数限制
- 优化Flash擦除期间的WATI状态处理,使用短超时避免长阻塞
- 添加完整的会话数据清理,防止状态污染
- 增加详细的注释说明各阶段的处理逻辑
This commit is contained in:
小龙虾
2026-05-30 20:04:04 +08:00
parent 235b9c0420
commit 6f4be9189c
13 changed files with 923 additions and 111 deletions
+58 -2
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
@@ -514,6 +514,7 @@
selectedFile: null,
selectedFileBytes: null,
selectedFileKey: '',
selectedFileHandle: null, // FileSystemFileHandle for hot reload
abortRequested: false,
uploading: false,
autoFlashing: false,
@@ -895,10 +896,38 @@
updateFileInfo(file);
}
// 通过 File System Access API 选择文件(支持热重载)
async function pickFileViaFSAPI() {
if (!window.showOpenFilePicker) {
log('FILE', '当前浏览器不支持 File System Access API', 'W');
return false;
}
try {
const handles = await window.showOpenFilePicker({
types: [{ description: '固件文件', accept: { 'application/octet-stream': ['.bin', '.hex'] } }],
multiple: false
});
if (handles && handles.length > 0) {
const handle = handles[0];
const file = await handle.getFile();
state.selectedFileHandle = handle;
updateFileInfo(file);
log('FILE', '已通过 FSAPI 选择文件(支持热重载): ' + file.name);
return true;
}
} catch (e) {
if (e.name !== 'AbortError') {
log('FILE', 'FSAPI 选择失败: ' + e.message, 'W');
}
}
return false;
}
function clearSelectedFile() {
state.selectedFile = null;
state.selectedFileBytes = null;
state.selectedFileKey = '';
state.selectedFileHandle = null;
state.lastUploadStatus = 'idle';
if (els.firmwareFile) els.firmwareFile.value = '';
updateFileInfo(null);
@@ -940,6 +969,18 @@
}
async function uploadFirmware() {
// 每次刷写前尝试重新从本地读取文件
if (state.selectedFileHandle) {
try {
const freshFile = await state.selectedFileHandle.getFile();
updateFileInfo(freshFile);
log('FILE', '已重新读取本地文件: ' + freshFile.name + ' (' + formatBytes(freshFile.size) + ')');
} catch (e) {
log('FILE', '重新读取文件失败: ' + e.message + ',将使用缓存版本', 'W');
}
} else {
syncSelectedFileFromInput();
}
if (!state.selectedFile || !state.dataInChar) return false;
const cfg = currentTarget();
@@ -1040,6 +1081,18 @@
}
async function autoFlash() {
// 每次刷写前尝试重新从本地读取文件
if (state.selectedFileHandle) {
try {
const freshFile = await state.selectedFileHandle.getFile();
updateFileInfo(freshFile);
log('FILE', '已重新读取本地文件: ' + freshFile.name + ' (' + formatBytes(freshFile.size) + ')');
} catch (e) {
log('FILE', '重新读取文件失败: ' + e.message + ',将使用缓存版本', 'W');
}
} else {
syncSelectedFileFromInput();
}
if (!state.selectedFile || !state.server) return;
const cfg = currentTarget();
state.autoFlashing = true;
@@ -1077,7 +1130,10 @@
els.autoFlashBtn.addEventListener('click', autoFlash);
els.abortBtn.addEventListener('click', abortTask);
els.clearLogBtn.addEventListener('click', clearLogs);
els.uploadZone.addEventListener('click', () => els.firmwareFile.click());
els.uploadZone.addEventListener('click', () => {
if (window.showOpenFilePicker) { pickFileViaFSAPI(); }
else { els.firmwareFile.click(); }
});
els.uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.currentTarget.classList.add('dragover');