Files
DCU_DesktopController/js/ota/console-mode.js
T
cbb e98950cde2 chore: 解决 stash pop 与 device-link 合并冲突并收回本地 WIP
保留 ConnectionHub(deviceLink 导入/ECAT 自启/pmHold 幂等)与主线
WIP(断线清指标、AUTO_POLL=false、boot_ecat 等)。
2026-09-16 12:31:39 +08:00

367 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* console-mode.js - 共享模式/目标切换胶水代码(从 index.html 内联脚本抽取)
*
* 职责:
* - 覆盖 app.js 中的 switchStageFlow,支持 BLE 阶段流程
* - 提供 setConsoleMode / selectFlashTarget 等供 HTML onclick 调用
* - 维护共享固件文件状态 (sharedFirmwareFiles) 并在 WiFi/BLE 间同步
* - 页面刷新后恢复上次升级目标与固件选择
*
* 依赖:app.js (通过 window.* 或 bare 引用) 提供 switchStageFlow、
* handleFileSelect、clearFile、pickFileViaFSAPI、addLog、showToast、setActivePhase 等。
*/
import {
restorePersistedFirmwareSelection,
persistFlashTarget,
loadPersistedFlashTarget
} from './file-handle.js';
window.connectionMode = window.connectionMode || 'wifi';
/* 刷新后优先用上次目标,避免默认回到 ESP32 丢掉 GD32 选择上下文 */
window.flashTarget = loadPersistedFlashTarget() || window.flashTarget || 'esp32';
window.sharedFirmwareFiles = window.sharedFirmwareFiles || {
esp32: { file: null, handle: null },
gd32: { file: null, handle: null }
};
(function () {
var originalSwitchStageFlow = window.switchStageFlow;
window.switchStageFlow = function (target) {
if (target === 'ble') {
window.currentOtaTarget = 'ble';
var esp32El = document.getElementById('esp32Stages');
var gd32El = document.getElementById('gd32Stages');
var bleEl = document.getElementById('bleStages');
var ecatEl = document.getElementById('ecatStages');
if (esp32El) esp32El.style.display = 'none';
if (gd32El) gd32El.style.display = 'none';
if (ecatEl) ecatEl.style.display = 'none';
if (bleEl) bleEl.style.display = '';
return;
}
if (target === 'ecat') {
window.currentOtaTarget = 'ecat';
var esp32E = document.getElementById('esp32Stages');
var gd32E = document.getElementById('gd32Stages');
var bleE = document.getElementById('bleStages');
var ecatE = document.getElementById('ecatStages');
if (esp32E) esp32E.style.display = 'none';
if (gd32E) gd32E.style.display = 'none';
if (bleE) bleE.style.display = 'none';
if (ecatE) ecatE.style.display = '';
return;
}
if (typeof originalSwitchStageFlow === 'function') {
originalSwitchStageFlow(target);
}
var bleEl2 = document.getElementById('bleStages');
if (bleEl2) bleEl2.style.display = 'none';
var ecatEl2 = document.getElementById('ecatStages');
if (ecatEl2) ecatEl2.style.display = 'none';
};
window.setConsoleMode = function (mode) {
// ★ 刷写进行中禁止切换连接模式:setConsoleMode → syncBleFileFromSelectedTarget →
// notifyFileChange → updateFileInfo → finalizeFileInfo → setProgress(0) 会覆盖
// 进行中的 WiFi/BLE 上传进度(与 selectFlashTarget 同样的守卫策略)。
var bleBusy = window.bleOta && window.bleOta.isUploading && window.bleOta.isUploading();
if (window.isUploading || bleBusy || window.bootEcatBusy) {
var active = window.bootEcatBusy ? 'ECAT' : (bleBusy ? 'BLE' : ((window.activeUploadChip || 'OTA').toUpperCase()));
if (typeof addLog === 'function') addLog('W', 'OTA', active + ' 刷写进行中,无法切换连接模式');
if (typeof showToast === 'function') showToast('当前有 ' + active + ' 任务在运行,请等待结束后再切换', 'error');
return;
}
var m = (mode === 'ble' || mode === 'ecat') ? mode : 'wifi';
var isBle = m === 'ble';
window.connectionMode = m;
var wifiCards = document.getElementById('wifiModeCards');
var bleCards = document.getElementById('bleModeCards');
var ecatCards = document.getElementById('ecatModeCards');
var wifiBtn = document.getElementById('modeWifiBtn');
var bleBtn = document.getElementById('modeBleBtn');
var ecatBtn = document.getElementById('modeEcatBtn');
if (wifiCards) wifiCards.style.display = m === 'wifi' ? '' : 'none';
if (bleCards) bleCards.style.display = m === 'ble' ? '' : 'none';
if (ecatCards) ecatCards.style.display = m === 'ecat' ? '' : 'none';
if (wifiBtn) wifiBtn.className = 'btn target-tab-btn ' + (m === 'wifi' ? 'btn-primary' : 'btn-outline');
if (bleBtn) bleBtn.className = 'btn target-tab-btn ' + (m === 'ble' ? 'btn-primary' : 'btn-outline');
if (ecatBtn) ecatBtn.className = 'btn target-tab-btn ' + (m === 'ecat' ? 'btn-primary' : 'btn-outline');
applySharedModeUi();
syncBleFileFromSelectedTarget();
/* 切到 EtherCAT 时自动探测一次,和 WiFi「测试」一样不用手点才知道通没通 */
if (m === 'ecat' && typeof window.bootEcatProbe === 'function') {
Promise.resolve(window.bootEcatProbe(false)).catch(function () { /* 后端未起时忽略 */ });
}
if (isBle) {
window.switchStageFlow('ble');
if (typeof setActivePhase === 'function') {
setActivePhase(window.flashTarget === 'gd32' ? 'gd32Ota' : 'esp32Ota');
}
if (window.bleOta && typeof window.bleOta.refreshButtons === 'function') {
window.bleOta.refreshButtons();
}
} else if (m === 'ecat') {
window.switchStageFlow('ecat');
if (typeof setActivePhase === 'function') setActivePhase('ecatOta');
if (typeof window.resetEcatStages === 'function') window.resetEcatStages();
if (typeof window.setProgress === 'function') window.setProgress(0, '等待刷写...');
var sb = document.getElementById('stageStatusBar');
var st = document.getElementById('stageStatusText');
if (sb) sb.className = 'stage-status-bar';
if (st) st.textContent = '待机';
} else {
window.switchStageFlow(window.flashTarget || (window.activePhase === 'esp32Ota' ? 'esp32' : 'gd32'));
}
};
window.selectFlashTarget = function (target) {
// OTA 上传进行中禁止切换目标:避免前端状态与后端 relay/upload 不同步导致卡死
// ★ 兼容 BLE 模式:BLE 上传用 bleOta.state.uploading,window.isUploading 仅 WiFi 通道使用。
var bleBusy = window.bleOta && window.bleOta.isUploading && window.bleOta.isUploading();
if (window.isUploading || bleBusy || window.bootEcatBusy) {
var active = window.bootEcatBusy ? 'ECAT' : (bleBusy ? 'BLE' : ((window.activeUploadChip || 'OTA').toUpperCase()));
if (typeof addLog === 'function') addLog('W', 'OTA', active + ' 刷写进行中,无法切换目标');
if (typeof showToast === 'function') showToast('当前有 ' + active + ' 任务在运行,请等待结束后再切换', 'error');
return;
}
window.flashTarget = target === 'gd32' ? 'gd32' : 'esp32';
persistFlashTarget(window.flashTarget);
applySharedModeUi();
/* 与 WiFi 一致:两路文件独立;切换只换「当前目标」面板 + 绑定该路文件到会话 */
if (window.connectionMode === 'ble') {
window.switchStageFlow('ble');
if (typeof setActivePhase === 'function') {
setActivePhase(window.flashTarget === 'gd32' ? 'gd32Ota' : 'esp32Ota');
}
if (window.bleOta && typeof window.bleOta.refreshTargetUi === 'function') {
window.bleOta.refreshTargetUi();
}
/* 把当前目标对应的 esp32/gd32 固件绑到 BLE 会话(无文件则清空,避免误刷另一芯片) */
syncBleFileFromSelectedTarget();
} else {
window.switchStageFlow(window.flashTarget);
}
};
window.handleSharedFileSelect = function (event, chip) {
window.flashTarget = chip === 'gd32' ? 'gd32' : 'esp32';
persistFlashTarget(window.flashTarget);
applySharedModeUi();
/* 等 processFile(含 HEX→BIN)完成后再写入 shared 并 sync */
var done = (typeof window.handleFileSelect === 'function')
? Promise.resolve(window.handleFileSelect(event, chip))
: Promise.resolve();
done.then(function () {
var prepared = chip === 'gd32' ? window.gd32File : window.esp32File;
if (prepared) {
window.sharedFirmwareFiles[chip] = { file: prepared, handle: null };
}
syncBleFileFromSelectedTarget();
}).catch(function (err) {
if (typeof window.addLog === 'function') {
window.addLog('W', '文件', chip.toUpperCase() + ' 选择失败: ' + (err && err.message ? err.message : err));
}
});
};
window.pickSharedFirmware = function (chip) {
window.flashTarget = chip === 'gd32' ? 'gd32' : 'esp32';
persistFlashTarget(window.flashTarget);
applySharedModeUi();
if (window.showOpenFilePicker && typeof window.pickFileViaFSAPI === 'function') {
var nativePicker = window.showOpenFilePicker;
var capturedHandle = null;
window.showOpenFilePicker = function () {
return nativePicker.apply(this, arguments).then(function (handles) {
capturedHandle = handles && handles.length ? handles[0] : null;
return handles;
});
};
Promise.resolve(window.pickFileViaFSAPI(chip)).then(function () {
/* 用 processFile 后的结果(GD32 HEX→BIN 后的 prepared),不要再塞回原始 .hex */
var prepared = chip === 'gd32' ? window.gd32File : window.esp32File;
var handle = (chip === 'gd32' ? window.gd32FileHandle : window.esp32FileHandle) || capturedHandle;
if (prepared) {
window.sharedFirmwareFiles[chip] = { file: prepared, handle: handle || null };
} else if (capturedHandle) {
window.sharedFirmwareFiles[chip] = {
file: window.sharedFirmwareFiles[chip] && window.sharedFirmwareFiles[chip].file,
handle: capturedHandle
};
}
syncBleFileFromSelectedTarget();
}).catch(function (err) {
if (err && err.name !== 'AbortError' && typeof window.addLog === 'function') {
window.addLog('W', '文件', 'FSAPI 选择失败: ' + (err.message || err));
}
}).then(function () {
/* 无论成败都还原原生 picker,避免二次选择失效 */
window.showOpenFilePicker = nativePicker;
});
} else {
var input = document.getElementById(chip === 'gd32' ? 'fileInputGd32' : 'fileInputEsp32');
if (input) input.click();
}
};
window.clearSharedFirmware = function (chip) {
window.sharedFirmwareFiles[chip] = { file: null, handle: null };
if (typeof window.clearFile === 'function') {
window.clearFile(chip);
}
if (window.connectionMode === 'ble' && window.flashTarget === chip && window.bleOta && typeof window.bleOta.clearFile === 'function') {
window.bleOta.clearFile();
}
};
window.setBleFirmwareInputFile = function (file) {
var input = document.getElementById('firmwareFile');
if (!input || !file || typeof window.DataTransfer !== 'function') {
return;
}
try {
var dt = new DataTransfer();
dt.items.add(file);
input.files = dt.files;
} catch (err) {
// Some browsers restrict assignment to file inputs; BLE state is still synced via notifyFileChange.
}
};
/**
* 按 flashTarget 把「该芯片一路」固件绑到 BLE 会话(复刻 WiFi:两路独立、切换不串文件)。
* 优先 window.esp32File/gd32File(含 HEX→BIN 结果),其次 sharedFirmwareFiles / input。
* 当前目标无文件时清空 BLE 会话,禁止继续显示/上传另一芯片固件。
*/
window.syncBleFileFromSelectedTarget = function () {
if (window.connectionMode !== 'ble' || !window.bleOta) {
return;
}
if (window.bleOta.isUploading && window.bleOta.isUploading()) {
return;
}
var chip = window.flashTarget === 'gd32' ? 'gd32' : 'esp32';
var file = chip === 'gd32' ? window.gd32File : window.esp32File;
var handle = chip === 'gd32' ? window.gd32FileHandle : window.esp32FileHandle;
var stored = window.sharedFirmwareFiles && window.sharedFirmwareFiles[chip]
? window.sharedFirmwareFiles[chip]
: null;
if (!handle && stored && stored.handle) handle = stored.handle;
if (!file && stored && stored.file) file = stored.file;
function bindBle(f, h) {
setBleFirmwareInputFile(f);
if (typeof window.bleOta.notifyFileChange === 'function') {
window.bleOta.notifyFileChange(f, h || null);
}
/* 会话 SHA 与对应芯片槽对齐(不跨芯片) */
var slot = chip === 'gd32' ? window._gd32FileSha256 : window._esp32FileSha256;
if (window.bleOta.state) {
if (slot) window.bleOta.state.fileSha256 = slot;
}
}
function clearBleSession() {
if (typeof window.bleOta.clearFile === 'function') {
window.bleOta.clearFile();
} else if (typeof window.bleOta.notifyFileChange === 'function') {
window.bleOta.notifyFileChange(null, null);
}
}
if (file) {
bindBle(file, handle);
return;
}
if (handle && typeof handle.getFile === 'function') {
handle.getFile().then(function (fresh) {
if (chip === 'gd32' && fresh.name.toLowerCase().endsWith('.hex') &&
typeof window.processFile === 'function') {
return Promise.resolve(window.processFile(fresh, 'gd32', {
silent: true,
skipHandlePersist: true
})).then(function () {
if (window.gd32File) bindBle(window.gd32File, handle);
else bindBle(fresh, handle);
});
}
if (chip === 'esp32') window.esp32File = fresh;
else window.gd32File = fresh;
if (stored) stored.file = chip === 'gd32' ? window.gd32File : window.esp32File;
bindBle(chip === 'gd32' ? window.gd32File : window.esp32File, handle);
}).catch(function () {
if (stored && stored.file) bindBle(stored.file, handle);
else clearBleSession();
});
return;
}
var suffix = chip === 'gd32' ? 'Gd32' : 'Esp32';
var input = document.getElementById('fileInput' + suffix);
var inputFile = input && input.files && input.files[0] ? input.files[0] : null;
if (inputFile) {
if (window.sharedFirmwareFiles) {
window.sharedFirmwareFiles[chip] = { file: inputFile, handle: null };
}
bindBle(inputFile, null);
return;
}
clearBleSession();
};
window.applySharedModeUi = function () {
var m = (window.connectionMode === 'ble' || window.connectionMode === 'ecat')
? window.connectionMode : 'wifi';
var isBle = m === 'ble';
var isEcat = m === 'ecat';
var isGd32 = window.flashTarget === 'gd32';
var targetEsp32 = document.getElementById('targetEsp32Tab');
var targetGd32 = document.getElementById('targetGd32Tab');
if (targetEsp32) targetEsp32.className = 'btn target-tab-btn ' + (isGd32 ? 'btn-outline' : 'btn-primary');
if (targetGd32) targetGd32.className = 'btn target-tab-btn ' + (isGd32 ? 'btn-primary' : 'btn-outline');
/* EtherCAT 只能刷 GD32(引导程序写应用区),目标切换行留着会以为还能选 ESP32 */
var targetRow = document.getElementById('targetTabRow');
if (targetRow) targetRow.style.display = isEcat ? 'none' : '';
var ids = {
/* EtherCAT 复用 GD32 上传区(同一 fileInputGd32 / processFile),选文件 UI 与 WiFi/BLE 一致 */
wifiEsp32UploadPane: !isEcat && !isGd32,
wifiGd32UploadPane: isGd32 || isEcat,
bleUploadPane: false,
wifiEsp32ActionPane: !isBle && !isEcat && !isGd32,
wifiGd32ActionPane: !isBle && !isEcat && isGd32,
bleActionPane: isBle,
bootEcatPane: isEcat
};
Object.keys(ids).forEach(function (id) {
var el = document.getElementById(id);
if (el) el.style.display = ids[id] ? '' : 'none';
});
/* EtherCAT 与 WiFi/BLE 共用同一张进度卡,只是切到 ecatStages + ppEcatOta */
var hint = document.getElementById('readyHint');
var autoBtn = document.getElementById('autoFlashBtn');
if (autoBtn && isBle) {
autoBtn.textContent = isGd32 ? '一键刷写 GD32' : '一键刷写 ESP32';
}
if (hint) {
if (isEcat) {
hint.innerHTML = 'EtherCAT 模式:选择 GD32 固件 → 探测对面是谁 → 刷写 APP。';
} else if (isBle) {
hint.innerHTML = isGd32
? 'BLE 模式 · 目标 GD32:连接设备 → 选择 GD32 固件 → 一键刷写。'
: 'BLE 模式 · 目标 ESP32:连接设备 → 选择 ESP32 固件 → 一键刷写。';
} else if (isGd32) {
hint.innerHTML = 'WiFi 模式:上传 GD32 固件后自动执行 SPI OTA。';
} else {
hint.innerHTML = 'WiFi 模式:上传 ESP32 固件后自动执行 OTA 并重启。';
}
}
if (typeof window.refreshBootEcatUi === 'function') window.refreshBootEcatUi();
};
applySharedModeUi();
/* 刷新后恢复上次固件与目标(FSAPI handle + localStorage 目标) */
restorePersistedFirmwareSelection().then(function () {
if (typeof window.switchStageFlow === 'function' && window.connectionMode !== 'ble') {
window.switchStageFlow(window.flashTarget || 'esp32');
}
}).catch(function () {});
})();
/* HTML onclick 用的 API 已在 IIFE 内挂到 window.*,无需再二次赋值 */