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 补设计动机与要点。
44 lines
1.9 KiB
JavaScript
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();
|
|
})();
|