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:
+58
-2
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user