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 不受影响)
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
||||
import {
|
||||
updateStagesFromRelayState, updateSystemStatusUi
|
||||
} from './ota/stages.js';
|
||||
import { startEcatLoadPoller } from './ota/ecat_load.js';
|
||||
import {
|
||||
handleFileSelect, pickFileViaFSAPI, setupDropZone,
|
||||
parseIntelHex, isIntelHexText, convertHexFileToBin,
|
||||
@@ -269,6 +270,9 @@ setupDropZone('uploadZoneGd32', 'gd32');
|
||||
// 否则 polling.js 的 scheduleStatusPoll 会因 window.OtaSessionState 未定义而抛 TypeError)
|
||||
startPolling();
|
||||
|
||||
// GD32 CPU 负载率(EtherCAT 通道,独立于 /system/status 轮询;页面上无该指标则自动空转)
|
||||
startEcatLoadPoller();
|
||||
|
||||
// IP 输入变化时清零失败计数并立即刷新状态
|
||||
var ipInput = document.getElementById('espIp');
|
||||
if (ipInput) {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user