/** * 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(); })();