chore: 解决 stash pop 与 device-link 合并冲突并收回本地 WIP
保留 ConnectionHub(deviceLink 导入/ECAT 自启/pmHold 幂等)与主线 WIP(断线清指标、AUTO_POLL=false、boot_ecat 等)。
This commit is contained in:
+104
-6
@@ -10,8 +10,19 @@
|
||||
* 与 /system/status 轮询完全独立:ESP32 离线不影响本显示,反之亦然;
|
||||
* 失败只静默退避,绝不计入 ESP32 的连接失败计数。
|
||||
*
|
||||
* **自动拉起**(2026-09):读到 invalid/过期时,代替用户走完
|
||||
* 服务→网卡→OP→PDO 循环 四级自愈(与 motor/transport-ecat.js 同一套 API),
|
||||
* 不必再单独打开电机页选 EtherCAT。避让:
|
||||
* - ECAT 刷写中(window.bootEcatBusy)或其它 OTA 在跑时**绝不动总线**
|
||||
* —— open 会 loop_stop、motor/start 会抢 OP,会把正在灌写的会话掐死;
|
||||
* - 冷却 8s,避免无适配器/无从站时每秒刷 open;
|
||||
* - 首次成功只打一条日志,之后静默。
|
||||
* 首页需持有 app 租约(index.html 挂 app-lease.js),否则服务端在
|
||||
* 租约归零宽限期后会把刚拉起的循环又停掉。
|
||||
*
|
||||
* 失效语义(都显示 '--'):
|
||||
* - valid:false —— ECAT 后端没在跑 / 总线从未打开过 / 固件 < 1.5.61(段 5 恒 0);
|
||||
* - valid:false —— ECAT 后端没在跑 / 总线从未打开过 / 固件 < 1.5.61(段 5 恒 0)
|
||||
* / 循环刚拉起、首个 1s 窗口还没滚完(seq 仍 0);
|
||||
* - age_ms > 3000 —— PDO 循环已停(没有页面持有 app 租约时 ecat_native 会
|
||||
* 自动停循环退回 INIT),负载值不再滚动,视为过期;
|
||||
* - 代理 503 —— ecat_native 没在跑。退避轮询(1s→5s→30s),避免不用 EtherCAT
|
||||
@@ -23,9 +34,12 @@ import { deviceLink } from '../common/device-link.js';
|
||||
|
||||
var POLL_MS = 1000; /* 正常轮询节拍(负载窗口 1s 一滚,再快没意义) */
|
||||
var STALE_MS = 3000; /* 超过 3 个窗口没刷新即视为过期 */
|
||||
var START_COOLDOWN_MS = 8000; /* 自动 open/start 冷却,防无设备时刷屏 */
|
||||
|
||||
var failCount = 0; /* 连续失败/过期次数,驱动退避 */
|
||||
var timerId = null;
|
||||
var startAttemptAt = 0; /* 上次尝试自动拉起的时刻 */
|
||||
var startLogged = false; /* 成功拉起只记一条日志 */
|
||||
|
||||
function el() { return document.getElementById('mGd32Load'); }
|
||||
|
||||
@@ -41,8 +55,9 @@ function setTitle(node, title) {
|
||||
}
|
||||
|
||||
var TIP_BASE = 'DCU_APP CPU 负载率:中断+非延迟处理(任务派发/OTA中继)占墙钟比,不含常开轮询与空转;EtherCAT 输入 PDO 段5,1s 窗口';
|
||||
var TIP_OFF = TIP_BASE + ';当前不可用:控制台外壳启动时会自动 open+motor/start;'
|
||||
+ '若仍为 --,检查网线/从站供电,或手动打开电机控制页选 EtherCAT。';
|
||||
var TIP_OFF = TIP_BASE + ';当前不可用:外壳/本页会自动 open+motor/start;'
|
||||
+ '若持续为 --,检查网卡/总线供电,或手动打开电机控制页选 EtherCAT。'
|
||||
+ 'ECAT 刷写期间会暂停自动拉起。';
|
||||
|
||||
/** @description 把 /api/app/load 的 JSON 渲染到 #mGd32Load。返回值是否有效(驱动退避)。 */
|
||||
function render(d) {
|
||||
@@ -71,6 +86,81 @@ function render(d) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function ecatPost(path, body, timeoutMs) {
|
||||
return fetch('/ecat/api/' + path.replace(/^\/+/, ''), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body || {}),
|
||||
signal: AbortSignal.timeout(timeoutMs || 8000),
|
||||
cache: 'no-store',
|
||||
}).then(function (r) { return r.json(); });
|
||||
}
|
||||
|
||||
function adapterSubstr() {
|
||||
try {
|
||||
var s = localStorage.getItem('dcu_ecat_adapter');
|
||||
if (s && s.trim()) return s.trim();
|
||||
} catch (_) {}
|
||||
return 'I210';
|
||||
}
|
||||
|
||||
/** 刷写/上传进行中:绝不动总线(open=loop_stop,start=抢 OP)。 */
|
||||
function busBusy() {
|
||||
return !!window.bootEcatBusy || !!window.isUploading
|
||||
|| !!(window.bleOta && window.bleOta.isUploading && window.bleOta.isUploading());
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保网卡已开 + OP + 后台 PDO 循环在跑。
|
||||
* 与 motor/transport-ecat.js 的 ensureOpen/ensureStarted 同一套 API,
|
||||
* 只做布尔成败,不引入 transient/慢请求表(本页没有那套基础设施)。
|
||||
*/
|
||||
async function ensurePdoCycle() {
|
||||
var st = null;
|
||||
try {
|
||||
st = await ecatPost('status', {}, 6000);
|
||||
} catch (_) {
|
||||
/* 后端未起:下面 open 会触发代理 _ecat_autostart */
|
||||
}
|
||||
if (!st || st.ok !== true) {
|
||||
var o = await ecatPost('open', { substr: adapterSubstr() }, 25000);
|
||||
if (!o || o.ok !== true) return false;
|
||||
}
|
||||
/* 周期/自旋与电机页共用 localStorage,用户调过的档位不被本页覆盖。 */
|
||||
var body = {};
|
||||
try {
|
||||
var v = parseFloat(localStorage.getItem('dcu_ecat_period_ms'));
|
||||
if (v > 0) body.period_ms = v;
|
||||
var sp = localStorage.getItem('dcu_ecat_spin');
|
||||
if (sp === '0' || sp === '1') body.spin = (sp === '1');
|
||||
} catch (_) {}
|
||||
/* motor/start 内部未进 OP 时自己走 PREOP→SAFEOP→OP,最坏约 24s */
|
||||
var sr = await ecatPost('motor/start', body, 30000);
|
||||
return !!(sr && sr.ok === true);
|
||||
}
|
||||
|
||||
/** 读数无效时尝试自动拉起。冷却 + 刷写避让。返回是否本次调用成功启动。 */
|
||||
async function tryAutoStart() {
|
||||
if (busBusy()) return false;
|
||||
var now = Date.now();
|
||||
if (now - startAttemptAt < START_COOLDOWN_MS) return false;
|
||||
startAttemptAt = now;
|
||||
try {
|
||||
var ok = await ensurePdoCycle();
|
||||
if (ok) {
|
||||
if (!startLogged) {
|
||||
startLogged = true;
|
||||
if (window.addLog) {
|
||||
window.addLog('I', 'ECAT',
|
||||
'已自动拉起 EtherCAT PDO 循环(GD32 负载将在首个 1s 窗口后显示)');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (_) { /* 代理/后端离线、open/start 失败:走退避 */ }
|
||||
return false;
|
||||
}
|
||||
|
||||
async function tick() {
|
||||
var node = el();
|
||||
if (!node) return; /* 页面上没有该指标就不再调度 */
|
||||
@@ -78,10 +168,18 @@ async function tick() {
|
||||
try {
|
||||
ok = render(await deviceLink.ecatPost('app/load', {}, 3000));
|
||||
} catch (_) { /* 代理/后端离线,走退避 */ }
|
||||
if (!ok) {
|
||||
failCount++;
|
||||
} else {
|
||||
if (ok) {
|
||||
failCount = 0;
|
||||
} else {
|
||||
var started = false;
|
||||
try { started = await tryAutoStart(); } catch (_) {}
|
||||
if (started) {
|
||||
/* 刚 start 成功时 seq 仍可能是 0(首个 1s 窗口未滚完),
|
||||
* 温和计数:保持 1s 节拍等窗口,不要立刻 5s/30s 退避。 */
|
||||
failCount = Math.min(failCount, 1);
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
var wait = POLL_MS;
|
||||
if (failCount > 6) wait = 30000;
|
||||
|
||||
Reference in New Issue
Block a user