feat(ecat): 启动后把 ecat_native 提到 RealTime;跳拍优化实验脚本与报告
- esp32_server: Popen 后外部 SetPriorityClass(RT/High),native 自设会被钳成 High - 入库 skip_* 标定/实验脚本与 REPORT_ecat_skip_opt_20260917 - 前端 WIP:layout/app/polling/boot_ecat/index - 不含 lab/skip_meter 原始采样与 preview 截图
This commit is contained in:
+87
-29
@@ -124,22 +124,42 @@ function showLog(res) {
|
||||
/**
|
||||
* @description 按"有没有已选 GD32 镜像 + 有没有任务在跑"重算按钮可用性。
|
||||
* 幂等,供 file-handle / console-mode / upload 各处调用;不缓存判据、不复制 arm 判断。
|
||||
* 交接按钮文案由最后一次 probe/reboot 结果驱动(window.bootEcatIsBoot)。
|
||||
*/
|
||||
export function refreshBootEcatUi() {
|
||||
var probeBtn = el('bootEcatProbeBtn');
|
||||
var runBtn = el('bootEcatRunBtn');
|
||||
var abortBtn = el('bootEcatAbortBtn');
|
||||
var rebootBtn = el('bootEcatRebootBtn');
|
||||
/* 别的通道在刷就互斥:三条传输共用同一块 flash,没有并发可言 */
|
||||
var otherBusy = !!window.isUploading ||
|
||||
!!(window.bleOta && window.bleOta.isUploading && window.bleOta.isUploading());
|
||||
var hasImg = !!window.gd32File;
|
||||
if (probeBtn) probeBtn.disabled = window.bootEcatBusy;
|
||||
if (rebootBtn) rebootBtn.disabled = window.bootEcatBusy || otherBusy;
|
||||
if (rebootBtn) {
|
||||
rebootBtn.disabled = window.bootEcatBusy || otherBusy;
|
||||
if (!window.bootEcatBusy) {
|
||||
var isBoot = window.bootEcatIsBoot === true;
|
||||
rebootBtn.textContent = isBoot ? '跳回应用' : '让应用回引导程序';
|
||||
rebootBtn.title = isBoot
|
||||
? '写 BOOT 的 CoE 0x2503:00=1,跳回 APP(BOOT ≥1.1.0.20)'
|
||||
: '写 APP 的 CoE 0x2502:00=1,让它复位进引导程序';
|
||||
}
|
||||
}
|
||||
if (runBtn) runBtn.disabled = window.bootEcatBusy || otherBusy || !hasImg;
|
||||
if (abortBtn) abortBtn.disabled = window.bootEcatBusy;
|
||||
}
|
||||
|
||||
function setRoleBadge(role, sw) {
|
||||
var badge = el('bootEcatRoleBadge');
|
||||
var txt = el('bootEcatRoleTxt');
|
||||
var swEl = el('bootEcatRoleSw');
|
||||
if (!badge) return;
|
||||
var r = role === 'boot' ? 'boot' : role === 'app' ? 'app' : 'unknown';
|
||||
badge.setAttribute('data-role', r);
|
||||
if (txt) {
|
||||
txt.textContent = r === 'boot' ? 'BOOT' : r === 'app' ? 'APP' : '未探测';
|
||||
}
|
||||
if (swEl) swEl.textContent = sw || (r === 'unknown' ? '—' : '版本未知');
|
||||
}
|
||||
|
||||
/** @description 复用 GD32 那条路的文件控件:同一条 processFile 做 .hex→bin 与镜像校验。 */
|
||||
export function bootEcatPick() {
|
||||
var input = el('fileInputGd32');
|
||||
@@ -156,26 +176,23 @@ export async function bootEcatProbe(verbose) {
|
||||
try {
|
||||
r = await api({ op: 'probe' });
|
||||
} catch (e) {
|
||||
setTxt('bootEcatWho', '后端无响应');
|
||||
setEcatConn('err', '后端无响应');
|
||||
setRoleBadge('unknown', null);
|
||||
if (verbose) window.addLog('E', 'ECAT刷写', '探测请求异常:' + e);
|
||||
return null;
|
||||
}
|
||||
var who = r.boot ? '引导程序' : (r.name ? '应用' : '读不到');
|
||||
var ver = r.sw || '—';
|
||||
setTxt('bootEcatWho', who);
|
||||
setRoleBadge(r.boot ? 'boot' : (r.name ? 'app' : 'unknown'), r.sw || null);
|
||||
setTxt('bootEcatName', r.name || '—');
|
||||
setTxt('bootEcatSw', ver);
|
||||
setTxt('bootEcatArm', r.armed ? '允许' : '不允许');
|
||||
setTxt('bootEcatRecv', bytes(r.slave_recv || 0));
|
||||
if (r.boot) setEcatConn('ok', '引导程序 ' + ver + ' · 可以刷写');
|
||||
else if (r.name) setEcatConn('warn', '应用 ' + ver + ' · 刷写会自动交接回 BOOT');
|
||||
else setEcatConn('err', '邮箱无回应');
|
||||
setTxt('bootEcatRecv', (r.srv_recv != null ? bytes(r.srv_recv) : '—') + ' / ' + bytes(r.slave_recv || 0));
|
||||
window.bootEcatIsBoot = !!r.boot;
|
||||
refreshBootEcatUi();
|
||||
if (verbose) {
|
||||
window.addLog('I', 'ECAT刷写',
|
||||
'0x1008 = "' + (r.name || '—') + '" 0x100A = "' + (r.sw || '') + '" → ' +
|
||||
(r.boot ? '是引导程序,可以刷写'
|
||||
: (r.name ? '是应用,刷写链里第 3 步会写它的 0x2502 让它回 BOOT(1.6.34 以前的 APP 没这个对象,只能走 SPI 0xB0 / CAN 0x320)'
|
||||
(r.boot ? '是引导程序,点主按钮可跳回 APP(0x2503)'
|
||||
: (r.name ? '是应用,点主按钮写 0x2502 进 BOOT'
|
||||
: '邮箱没回应,多半是老 BOOT 或栈没起来')));
|
||||
}
|
||||
return r;
|
||||
@@ -243,42 +260,71 @@ async function waitBootOnBus(maxTries, gapMs) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 单独一次「让应用回引导程序」:写 APP 的 CoE 0x2502:00 = 1。
|
||||
* 刷写链里本来就带这一步,这个按钮是给"只想把板子停在 BOOT 上等人查"的场合 ——
|
||||
* 交接一个字节 flash 都不写,代价只是从站重启一次、停在 BOOT 的 10s 窗口里等。
|
||||
* 跑着老 APP(<1.6.34)的机器字典里没有 0x2502,主站会把从站的 SDO abort 码原样带回来,
|
||||
* 日志里直接就能看出该退回 SPI 0xB0 / CAN 0x320。
|
||||
* @description 交接按钮总入口:按对端身份自动选方向。
|
||||
* - 已在 BOOT → op=jump_app(写 0x2503)
|
||||
* - 在 APP / 未知 → op=reboot(写 0x2502)
|
||||
* 刷写链里本来就带 reboot 这一步;本按钮给"只想手动来回切换"的场合。
|
||||
*/
|
||||
export async function bootEcatReboot() {
|
||||
if (window.bootEcatBusy) return;
|
||||
window.bootEcatBusy = true;
|
||||
refreshBootEcatUi();
|
||||
var btn = el('bootEcatRebootBtn');
|
||||
if (btn) btn.textContent = '交接中…';
|
||||
setEcatMsg('正在让应用回引导程序…');
|
||||
var isBoot = window.bootEcatIsBoot === true;
|
||||
if (btn) btn.textContent = isBoot ? '跳转中…' : '交接中…';
|
||||
setEcatMsg(isBoot ? '正在让引导程序跳回应用…' : '正在让应用回引导程序…');
|
||||
try {
|
||||
if (!await ensureBusOpen()) {
|
||||
setEcatStatus('err', '总线没打开');
|
||||
setEcatMsg('主站没能打开总线(见日志)');
|
||||
return;
|
||||
}
|
||||
var r = await api({ op: 'reboot' });
|
||||
/* 先用本次动作的方向;若 probe 结果与缓存不符,会话内纠正 */
|
||||
var op = window.bootEcatIsBoot === true ? 'jump_app' : 'reboot';
|
||||
var r = await api({ op: op });
|
||||
showLog(r);
|
||||
if (!r || r.ok !== true) {
|
||||
setEcatStatus('err', '交接被拒');
|
||||
setEcatMsg('交接没被受理(见日志里从站的 abort 码)');
|
||||
setEcatStatus('err', op === 'jump_app' ? '跳回被拒' : '交接被拒');
|
||||
setEcatMsg(op === 'jump_app'
|
||||
? '跳回没被受理(BOOT 需 ≥1.1.0.20;见日志 abort 码)'
|
||||
: '交接没被受理(见日志里从站的 abort 码)');
|
||||
await bootEcatProbe(false);
|
||||
return;
|
||||
}
|
||||
if (r.already) {
|
||||
setEcatStatus('ok', '对面本来就是引导程序');
|
||||
setEcatMsg('从站已在 BOOT,无需交接,可直接刷写');
|
||||
if (op === 'jump_app') {
|
||||
window.bootEcatIsBoot = false;
|
||||
setEcatStatus('ok', '对面本来就是应用');
|
||||
setEcatMsg('从站已在 APP,无需跳回');
|
||||
} else {
|
||||
window.bootEcatIsBoot = true;
|
||||
setEcatStatus('ok', '对面本来就是引导程序');
|
||||
setEcatMsg('从站已在 BOOT,无需交接,可直接刷写');
|
||||
}
|
||||
refreshBootEcatUi();
|
||||
await bootEcatProbe(true);
|
||||
return;
|
||||
}
|
||||
if (op === 'jump_app') {
|
||||
setEcatMsg('跳回已受理,等 APP 上总线…');
|
||||
var a = await waitAppOnBus(10, 800);
|
||||
if (a && a.name && !a.boot) {
|
||||
window.bootEcatIsBoot = false;
|
||||
setEcatStatus('ok', '已回到应用');
|
||||
setEcatMsg('APP 已在总线应答(0x100A = ' + (a.sw || '—') + ')');
|
||||
} else {
|
||||
setEcatStatus('warn', '跳回后没等到 APP');
|
||||
setEcatMsg('写了 0x2503 但没等到应用 —— 看串口,或 A 区 CRC 不过');
|
||||
window.addLog('E', 'ECAT刷写',
|
||||
'跳回请求已受理却等不到 APP 应答 —— 确认 BOOT ≥1.1.0.20 且 A 区镜像完整');
|
||||
}
|
||||
refreshBootEcatUi();
|
||||
return;
|
||||
}
|
||||
setEcatMsg('交接已受理,等 BOOT 上总线…');
|
||||
var b = await waitBootOnBus(10, 800);
|
||||
if (b && b.boot) {
|
||||
window.bootEcatIsBoot = true;
|
||||
setEcatStatus('ok', '已进引导程序');
|
||||
setEcatMsg('BOOT 已在总线应答(0x100A = ' + (b.sw || '—') + '),窗口 10s:可以直接刷写');
|
||||
} else {
|
||||
@@ -293,15 +339,27 @@ export async function bootEcatReboot() {
|
||||
}
|
||||
} catch (e) {
|
||||
setEcatStatus('err', '请求异常');
|
||||
setEcatMsg('交接请求异常');
|
||||
window.addLog('E', 'ECAT刷写', '交接请求异常:' + e);
|
||||
setEcatMsg('交接/跳回请求异常');
|
||||
window.addLog('E', 'ECAT刷写', '交接/跳回请求异常:' + e);
|
||||
} finally {
|
||||
window.bootEcatBusy = false;
|
||||
if (btn) btn.textContent = '让应用回引导程序';
|
||||
refreshBootEcatUi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 跳回之后等 APP 真的回到总线(与 waitBootOnBus 对称)。
|
||||
*/
|
||||
async function waitAppOnBus(maxTries, gapMs) {
|
||||
var last = null;
|
||||
for (var k = 0; k < maxTries; k++) {
|
||||
last = await bootEcatProbe(false);
|
||||
if (last && last.name && !last.boot) return last;
|
||||
await new Promise(function (res) { setTimeout(res, gapMs); });
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
/** @description 放弃:丢弃服务端缓冲。总线此刻本来就没动过(推只到主站内存)。 */
|
||||
export async function bootEcatAbort() {
|
||||
if (window.bootEcatBusy) return;
|
||||
|
||||
Reference in New Issue
Block a user