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
This commit is contained in:
stswangzhiping
2026-03-28 22:15:48 +08:00
parent 9800e712e5
commit 1910a2fb9f
3 changed files with 35 additions and 15 deletions

View File

@@ -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';

View File

@@ -63,28 +63,47 @@ function hasWiredCarrier() {
}
/**
* LAN 面板灯专用:只反映「板载以太网插线」,与 WiFi 无关
* - CLAWD_ETH_IFACE只用该口 carrier
* - 否则每次优先读 end0、eth0RK 等板型固定名),避免用「扫描到的第一个 carrier 口」
* (该口会随 WiFi 断开/连接、NM 重排而变化,导致 LAN 灯跟 WiFi 联动)
* - 若无 end0/eth0 节点再退回 hasWiredCarrier()
* 物理链路是否 upcarrier + operstate 双读,避免单读 carrier 拔线不更新、或与 WiFi 状态不同步的误判
*/
function hasLanCableCarrier() {
const explicit = process.env.CLAWD_ETH_IFACE;
if (explicit) {
function _ifacePhysicalLinkUp(iface) {
try {
return fs.readFileSync(`/sys/class/net/${explicit}/carrier`, 'utf8').trim() === '1';
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;
}
}
for (const n of ['end0', 'eth0']) {
/**
* 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 {
if (!fs.existsSync(`/sys/class/net/${n}`)) continue;
return fs.readFileSync(`/sys/class/net/${n}/carrier`, 'utf8').trim() === '1';
} catch (_) {}
return fs.existsSync(`/sys/class/net/${explicit}`) && _ifacePhysicalLinkUp(explicit);
} catch (_) {
return false;
}
}
let hasBoardIface = false;
for (const n of ['end0', 'eth0']) {
if (!fs.existsSync(`/sys/class/net/${n}`)) continue;
hasBoardIface = true;
if (_ifacePhysicalLinkUp(n)) return true;
}
if (hasBoardIface) return false;
return hasWiredCarrier();
}

View File

@@ -199,15 +199,16 @@ class ProvisionManager extends EventEmitter {
this.emit('network-ready');
}
// WiFi 灯:只在 STA 模式下反映互联网连通性AP 模式下始终熄灭
if (this._state === 'sta') {
// 产品 WiFi 灯OpenVFD playAP 全程强制熄灭,避免与其它逻辑竞态导致误亮
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);
}