From 1910a2fb9f589073b4400b554869c6b30f43095f Mon Sep 17 00:00:00 2001 From: stswangzhiping <59632378+stswangzhiping@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:15:48 +0800 Subject: [PATCH] fix(led,provision): AP forces WiFi(play) off; LAN uses carrier+operstate - Monitor: every tick in ap state led.off() so play never stays on with hotspot - hasLanCableCarrier: _ifacePhysicalLinkUp (carrier=1 and operstate not down) - When end0/eth0 exist but link down, return false (no fallback to hasWiredCarrier) - LAN poll 500ms Made-with: Cursor --- lib/led.js | 2 +- lib/network.js | 39 +++++++++++++++++++++++++++++---------- lib/provisioning.js | 9 +++++---- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/led.js b/lib/led.js index ea71fbb..520adef 100644 --- a/lib/led.js +++ b/lib/led.js @@ -19,7 +19,7 @@ const { hasLanCableCarrier } = require('./network'); */ const BLINK_INTERVAL_MS = 500; -const LAN_POLL_MS = 1000; +const LAN_POLL_MS = 500; const VFD_BASE = process.env.CLAWD_OPENVFD_PATH || '/sys/class/leds/openvfd'; diff --git a/lib/network.js b/lib/network.js index 7213c6e..0dcd454 100644 --- a/lib/network.js +++ b/lib/network.js @@ -63,28 +63,47 @@ function hasWiredCarrier() { } /** - * LAN 面板灯专用:只反映「板载以太网插线」,与 WiFi 无关。 - * - CLAWD_ETH_IFACE:只用该口 carrier - * - 否则每次优先读 end0、eth0(RK 等板型固定名),避免用「扫描到的第一个 carrier 口」 - * (该口会随 WiFi 断开/连接、NM 重排而变化,导致 LAN 灯跟 WiFi 联动) - * - 若无 end0/eth0 节点再退回 hasWiredCarrier() + * 物理链路是否 up:carrier + operstate 双读,避免单读 carrier 拔线不更新、或与 WiFi 状态不同步的误判。 + */ +function _ifacePhysicalLinkUp(iface) { + try { + const base = `/sys/class/net/${iface}`; + const carrier = fs.readFileSync(`${base}/carrier`, 'utf8').trim(); + if (carrier !== '1') return false; + let oper = 'unknown'; + try { + oper = fs.readFileSync(`${base}/operstate`, 'utf8').trim(); + } catch (_) {} + if (oper === 'down' || oper === 'lowerlayerdown') return false; + return true; + } catch (_) { + return false; + } +} + +/** + * LAN 面板灯:只反映指定/板载以太网 RJ45 链路,与 WiFi 无关。 + * - CLAWD_ETH_IFACE:只读该口 + * - 否则只认 end0、eth0(存在则仅用其 sysfs,链路 down 即灭,不再退回「任一带 carrier 口」以免跟 WiFi 耦合) + * - 无 end0/eth0 节点时(开发机)退回 hasWiredCarrier() */ function hasLanCableCarrier() { const explicit = process.env.CLAWD_ETH_IFACE; if (explicit) { try { - return fs.readFileSync(`/sys/class/net/${explicit}/carrier`, 'utf8').trim() === '1'; + return fs.existsSync(`/sys/class/net/${explicit}`) && _ifacePhysicalLinkUp(explicit); } catch (_) { return false; } } + let hasBoardIface = false; for (const n of ['end0', 'eth0']) { - try { - if (!fs.existsSync(`/sys/class/net/${n}`)) continue; - return fs.readFileSync(`/sys/class/net/${n}/carrier`, 'utf8').trim() === '1'; - } catch (_) {} + if (!fs.existsSync(`/sys/class/net/${n}`)) continue; + hasBoardIface = true; + if (_ifacePhysicalLinkUp(n)) return true; } + if (hasBoardIface) return false; return hasWiredCarrier(); } diff --git a/lib/provisioning.js b/lib/provisioning.js index 7aa6e2d..e191565 100644 --- a/lib/provisioning.js +++ b/lib/provisioning.js @@ -199,15 +199,16 @@ class ProvisionManager extends EventEmitter { this.emit('network-ready'); } - // WiFi 灯:只在 STA 模式下反映互联网连通性;AP 模式下始终熄灭 - if (this._state === 'sta') { + // 产品 WiFi 灯(OpenVFD play):AP 全程强制熄灭,避免与其它逻辑竞态导致误亮 + if (this._state === 'ap') { + led.off(); + } else if (this._state === 'sta') { if (hasInternet()) { led.on(); } else { - led.off(); // WiFi 已连接但无互联网 + led.off(); // STA 已连热点但无互联网 } } - // AP 模式下 led 已在 _enterAP() 中熄灭,无需重复操作 }, MONITOR_INTERVAL_MS); }