Files
DCU_DesktopController/js/ota/ecat_load.js
T
cbb f9502579ef feat(link): 外壳启动强制 open+motor/start,不必等电机页切 EtherCAT
DeviceLink Owner 在控制台加载后自动 bootstrapEcatBus:status→open→
motor/start,沿用 hub 网卡与 period/spin 持久化。成功后不重复 start;
掉线 10s 退避重开。有线 ECAT 与 WiFi 不冲突。
2026-09-16 12:24:45 +08:00

97 lines
3.9 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 的自动拉起逻辑。
*
* 请求统一走 ConnectionHub(device-link.ecatPost),与 Owner 健康轮询同源。
*/
import { deviceLink } from '../common/device-link.js';
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 + ';当前不可用:控制台外壳启动时会自动 open+motor/start;'
+ '若仍为 --,检查网线/从站供电,或手动打开电机控制页选 EtherCAT。';
/** @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 {
ok = render(await deviceLink.ecatPost('app/load', {}, 3000));
} 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);
}