Files
DCU_DesktopController/js/ota/ecat_load.js
T
cbb 9cbde690c6 feat(ota): 首页运行指标卡新增 GD32 CPU 负载率(EtherCAT 通道)
DCU_APP 固件 >=1.5.61 把"中断+非延迟处理段"占用率(1.5.63 收窄定义)
写进 EtherCAT 输入 PDO 段 5 诊断区(1s 滚动窗口, 0.1% 分辨率), native
经 /api/app/load 提供纯缓存读; 前端 js/ota/ecat_load.js 经同源代理
/ecat/api/app/* 轮询并渲染到 #mGd32Load。

- 与 /system/status 轮询完全独立: ESP32 离线互不影响, 失败不计入
  ESP32 连接失败计数
- 失败静默退避 1s→5s→30s, 不用 EtherCAT 的用户不按 1Hz 触发 esp32_server
  的自动拉起逻辑
- 无效显示 --: valid:false(总线未开/循环未跑/固件过旧)或 age_ms>3s
  (PDO 循环已停, 值不再滚动); >=85% 琥珀色提示余量吃紧
- 页面上无 #mGd32Load 时自动空转(其他页共用 app.js 不受影响)
2026-09-05 19:58:36 +08:00

95 lines
3.8 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.
/**
* ecat_load.js - GD32(DCU_APP) CPU 负载率显示(EtherCAT 通道,仅 OTA 中继页使用)
*
* 数据来源:DCU_APP 1.5.61+ 把「中断 + 非延迟处理段」的占用率(1.5.63 收窄定义)写进
* EtherCAT 输入 PDO 段 5 诊断区(1s 滚动窗口,0.1% 分辨率),主站
* tools/ecat/ecat_native.exe 解码后由 /api/app/load 提供纯缓存读。
* 本模块经本服务器的同源代理 /ecat/api/* 调它(代理只收 POST,见 esp32_server
* _ecat_proxy),显示在「运行指标」卡的 #mGd32Load。
*
* 与 /system/status 轮询完全独立:ESP32 离线不影响本显示,反之亦然;
* 失败只静默退避,绝不计入 ESP32 的连接失败计数。
*
* 失效语义(都显示 '--'):
* - valid:false —— ECAT 后端没在跑 / 总线从未打开过 / 固件 < 1.5.61(段 5 恒 0);
* - age_ms > 3000 —— PDO 循环已停(没有页面持有 app 租约时 ecat_native 会
* 自动停循环退回 INIT),负载值不再滚动,视为过期;
* - 代理 503 —— ecat_native 没在跑。退避轮询(1s→5s→30s),避免不用 EtherCAT
* 的用户按 1Hz 反复触发 esp32_server 的自动拉起逻辑。
*/
var POLL_MS = 1000; /* 正常轮询节拍(负载窗口 1s 一滚,再快没意义) */
var STALE_MS = 3000; /* 超过 3 个窗口没刷新即视为过期 */
var failCount = 0; /* 连续失败/过期次数,驱动退避 */
var timerId = null;
function el() { return document.getElementById('mGd32Load'); }
/* 写前比较:1Hz 轮询里大部分字段不变,无条件写会白白触发样式失效(同 stages.js) */
function setText(node, txt) {
if (node && node.textContent !== txt) node.textContent = txt;
}
function setCls(node, cls) {
if (node && node.className !== cls) node.className = cls;
}
function setTitle(node, title) {
if (node && node.title !== title) node.title = title;
}
var TIP_BASE = 'DCU_APP CPU 负载率:中断+非延迟处理(任务派发/OTA中继)占墙钟比,不含常开轮询与空转;EtherCAT 输入 PDO 段5,1s 窗口';
var TIP_OFF = TIP_BASE + ';当前不可用:需要 ECAT 总线已打开且过程数据循环在跑'
+ '(如打开电机控制页选 EtherCAT 传输),且 DCU_APP 固件 >= 1.5.61。';
/** @description 把 /api/app/load 的 JSON 渲染到 #mGd32Load。返回值是否有效(驱动退避)。 */
function render(d) {
var node = el();
if (!node) return false;
var fresh = !!d && d.valid === true
&& Number.isFinite(d.age_ms) && d.age_ms >= 0 && d.age_ms <= STALE_MS;
if (!fresh) {
setText(node, '--');
setCls(node, 'v');
setTitle(node, TIP_OFF);
return false;
}
var perm = Number(d.load_perm);
if (!Number.isFinite(perm)) {
setText(node, '--');
setCls(node, 'v');
setTitle(node, TIP_OFF);
return false;
}
var pct = perm / 10;
setText(node, pct.toFixed(1) + '%');
/* ≥85% 变琥珀提示余量吃紧;其余用正常强调色 */
setCls(node, pct >= 85 ? 'v warn' : 'v ok');
setTitle(node, TIP_BASE + ';窗口序号 ' + d.seq + ',' + d.age_ms + 'ms 前更新');
return true;
}
async function tick() {
var node = el();
if (!node) return; /* 页面上没有该指标就不再调度 */
var ok = false;
try {
var resp = await fetch('/ecat/api/app/load', { method: 'POST', body: '{}' });
if (resp.ok) ok = render(await resp.json());
} catch (_) { /* 代理/后端离线,走退避 */ }
if (!ok) {
failCount++;
} else {
failCount = 0;
}
var wait = POLL_MS;
if (failCount > 6) wait = 30000;
else if (failCount > 2) wait = 5000;
timerId = setTimeout(tick, wait);
}
/** @description 启动轮询。幂等;页面上没有 #mGd32Load 时不启动。 */
export function startEcatLoadPoller() {
if (timerId !== null || !el()) return;
timerId = setTimeout(tick, POLL_MS);
}