Files
DCU_DesktopController/js/common/app-lease.js
T
cbb 99fb9bcb4a feat(server): app 窗口租约, 全部窗口关闭自动停 EtherCAT 循环
PDO 循环起来后 native 侧一直跑, 其 --parent-pid 自清理只覆盖"本服务拉起
的实例死掉"; 浏览器模式关窗后本服务还在、窗口模式复用现成 ecat_native
(parent 是别人)这两种常见离开方式都够不着 —— 从站卡在 OP、自旋节拍白占
一个核。现由每个 app 页面与页面服务器的 WebSocket 租约兜底:

- esp32_server.py: 新增 GET /ws/app-lease(手写 RFC6455 握手 + 帧泵)。
  连接计数归零后隔 3s GRACE 调后端 /api/op/stop 停循环退回 INIT; 断开
  感知靠 TCP FIN / Close 帧回显而非心跳(后台标签页节流不误停); 代理
  透传时 _note_ecat_loop 记账"循环是否本服务跑起来的", 只停自己确认过
  的; /api/shutdown 与 serve_forever 退出路径同步补停(定时器随进程死亡)。
- js/common/app-lease.js: 租约客户端, 3~30s 退避重连, file:// 静默跳过。
- 接入 tabs.html(外壳持有, 内部切标签不断连)与 pages/motor.html(单独
  打开时页面自持)。
- 测试: scripts/test_app_lease.py(裸 TCP 全路径, 假 _ecat_post) +
  scripts/test_app_lease_e2e.mjs(真 Edge headless + 假后端 8091, 不碰
  真总线)。README 补设计动机与要点。
2026-09-05 19:58:25 +08:00

44 lines
1.9 KiB
JavaScript

/**
* app-lease.js — "app 窗口还开着"的凭证。
*
* 与 esp32_server.py 的 /ws/app-lease 保持一条 WebSocket。服务端在
* **所有** 租约连接断开后的宽限期结束时, 自动把 EtherCAT PDO 循环停掉、
* 从站退回 INIT —— 这是"关掉 app 从站必须停"的兜底, 无论 ecat_native
* 是谁拉起的(本服务复用现成实例时, 它的 --parent-pid 看门狗够不着)。
*
* 为什么用 WebSocket 而不是心跳轮询: 已建立的连接不受浏览器后台标签页
* 定时器节流影响(那会把"挂在后台的 app"误判成已关闭), 断开靠 TCP FIN
* 即时感知, 不依赖任何计时器。服务端另有 3s 断连吸收窗口, 页面刷新不会误停。
*
* 接入位置: tabs.html(外壳, 覆盖整个 app 窗口生命周期) 和 pages/motor.html
* (单独打开电机页时它自己就是窗口)。同一窗口里两条连接并存没问题,
* 服务端判据是"连接数归零"。断线自动重连; file:// 直开页面时静默跳过。
*/
(function () {
"use strict";
if (location.protocol !== "http:" && location.protocol !== "https:") return;
var url = (location.protocol === "https:" ? "wss://" : "ws://") +
location.host + "/ws/app-lease";
var retryTimer = null;
var delay = 3000;
function scheduleReconnect() {
if (retryTimer) return;
// 3~30s 递增退避: 服务没起/刚重启时不去刷屏, 起来后最多等半分钟接上。
retryTimer = setTimeout(function () {
retryTimer = null;
delay = Math.min(delay * 2, 30000);
connect();
}, delay);
}
function connect() {
var ws;
try { ws = new WebSocket(url); } catch (e) { scheduleReconnect(); return; }
ws.onopen = function () { delay = 3000; };
ws.onclose = function () { scheduleReconnect(); };
ws.onerror = function () { try { ws.close(); } catch (e) {} };
}
connect();
})();