feat(link): DeviceLink 外壳单例统一 ESP32/ECAT/BLE 健康态

各页只共用 IP、各自 fetch 导致同屏「已连接/连接失败」分裂。
外壳 Owner 轮询 /system/status 与 EtherCAT 状态,BroadcastChannel
广播快照;连接灯只认共享层,SPI 失败单独文案不覆盖设备离线。
This commit is contained in:
cbb
2026-09-16 02:50:12 +08:00
parent be26f4daa2
commit 670b833dc6
6 changed files with 422 additions and 10 deletions
+37 -10
View File
@@ -186,6 +186,31 @@
var $ = function (id) { return document.getElementById(id); };
var logBox = $("log");
/* DeviceLink 统一健康态(外壳 Owner 经 BroadcastChannel 广播)。
SPI 0xA2 失败 ≠ 设备离线:灯只认共享层;命令错误单独文案。 */
window.__pmSetConn = null;
function setConn(ok, text) {
var p = $("pillConn");
p.className = "pill " + (ok ? "online" : "offline");
$("pillConnTxt").textContent = text;
}
window.__pmSetConn = setConn;
/** SPI/业务失败:不得把「设备离线」打红(除非共享层已判离线)。 */
function setSpiFail(msg) {
log(msg, "err");
var dl = window.__pmDeviceLink;
var s = dl && dl.getSnapshot ? dl.getSnapshot() : null;
if (s && s.links && s.links.esp32 && s.links.esp32.online) {
setConn(true, "设备在线 · SPI 失败");
} else if (!s) {
/* 无外壳/尚未收到快照:退回旧语义 */
setConn(false, "连接失败 · 5s 后重试");
}
/* 有快照且 offline:留给 DeviceLink subscribe 刷新灯 */
}
/* 与 js/common/base-url.js 同 key 同默认值:全局唯一设备 IP,不设独立连接区 */
var LS_IP = "dcu_esp_ip", DEFAULT_IP = "192.168.31.127";
function deviceIp() {
@@ -302,11 +327,6 @@
* 事务本身会唤醒睡眠中的芯片,轮询等于不停叫醒它);连败 3 次标红停轮。
* pollGen:丢弃 enterMode/唤醒 之后仍在途的过期 1E 响应,避免竞态覆盖 UI。 ---- */
var pollFail = 0, pollTimer = null, pollPaused = false, pollGen = 0;
function setConn(ok, text) {
var p = $("pillConn");
p.className = "pill " + (ok ? "online" : "offline");
$("pillConnTxt").textContent = text;
}
function bumpPollGen() { return ++pollGen; }
/* 仅在确认芯片已离开睡眠(唤醒成功 / 读到 SLEEP_EN=0 / NRST)时清 flag */
function markAwake() {
@@ -336,7 +356,7 @@
if (gen !== pollGen || pollPaused) return;
pollFail++;
if (pollFail >= 3) {
setConn(false, "连接失败 · 5s 后重试");
setSpiFail("SPI 轮询失败 ×3(IP " + deviceIp() + ")· 5s 后重试");
pollTimer = setTimeout(probe, 5000);
} else {
pollTimer = setTimeout(pollOnce, 2000);
@@ -359,8 +379,7 @@
log("ESP32 可达: " + deviceIp() + "(未读 LAN9252,保持睡眠)", "ok");
})
.catch(function (e) {
setConn(false, "连接失败 · 5s 后重试");
log("连接失败: " + e.message + "(IP 取自 OTA 页的设备地址 " + deviceIp() + ")", "err");
setSpiFail("ESP32 探测失败: " + e.message + "(IP " + deviceIp() + ")");
pollTimer = setTimeout(probeEspOnly, 5000);
});
}
@@ -374,8 +393,7 @@
pollOnce();
})
.catch(function (e) {
setConn(false, "连接失败 · 5s 后重试");
log("连接失败: " + e.message + "(IP 取自 OTA 页的设备地址 " + deviceIp() + ")", "err");
setSpiFail("ESP32 探测失败: " + e.message + "(IP " + deviceIp() + ")");
pollTimer = setTimeout(probe, 5000);
});
}
@@ -604,5 +622,14 @@
}
})();
</script>
<script type="module">
import { deviceLink } from '../js/common/device-link.js?v=20260916a';
window.__pmDeviceLink = deviceLink;
deviceLink.subscribe(function (s) {
if (!s || !s.links || !window.__pmSetConn) return;
var e = s.links.esp32;
window.__pmSetConn(!!e.online, e.online ? '已连接' : '离线');
});
</script>
</body>
</html>