Files
clawd/lib/led.js
stswangzhiping 8f5a8ec896 fix(led): swap WiFi(play)/LAN(wifi+eth), LAN carrier via CLAWD_ETH_IFACE, faster alarm
- WifiLed drives play; LanLed drives wifi+eth pair (matches panel silkscreen)
- hasLanCableCarrier(): CLAWD_ETH_IFACE-only carrier when set (reliable unplug)
- LAN poll 1s; config.activated persisted for immediate setApps on boot/reconnect
- setApps double vfdOn alarm after 50ms for slow OpenVFD
- Clear activated + setSetup on credential errors
- install.sh env comment for CLAWD_ETH_IFACE

Made-with: Cursor
2026-03-28 21:50:56 +08:00

290 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const fs = require('fs');
const log = require('./logger');
const { hasLanCableCarrier } = require('./network');
/**
* OpenVFD 图标:/sys/class/leds/openvfd/led_on|led_off写入图标名
*
* 映射与面板丝印一致play=WiFi 状态wifi+eth=有线链路):
* play → 产品 WiFi 灯(配网逻辑 on/off/blink
* wifi + eth 同亮/同灭 → LAN有线插拔见 hasLanCableCarrier / CLAWD_ETH_IFACE
* alarm → pwrSETUP=灭 / APPS=亮)
* BT → 无 sysfs仅日志
*
* 数码管AP/Conn/时间等):仍仅 debug 输出,不接 sysfs。
*
* CLAWD_OPENVFD_PATH 默认 /sys/class/leds/openvfd
*/
const BLINK_INTERVAL_MS = 500;
const LAN_POLL_MS = 1000;
const VFD_BASE = process.env.CLAWD_OPENVFD_PATH || '/sys/class/leds/openvfd';
function vfdOn(icon) {
try {
fs.writeFileSync(`${VFD_BASE}/led_on`, icon);
} catch (e) {
log.debug('led', `openvfd led_on ${icon}: ${e.message}`);
}
}
function vfdOff(icon) {
try {
fs.writeFileSync(`${VFD_BASE}/led_off`, icon);
} catch (e) {
log.debug('led', `openvfd led_off ${icon}: ${e.message}`);
}
}
function vfdWifiPair(on) {
if (on) {
vfdOn('wifi');
vfdOn('eth');
} else {
vfdOff('wifi');
vfdOff('eth');
}
}
class WifiLed {
constructor() {
this._blinkTimer = null;
this._blinkState = false;
this._current = null;
}
on() {
if (this._current === 'on') return;
this._stopBlink();
this._write(1);
this._current = 'on';
log.info('led', 'WiFi 指示灯 → 常亮');
}
off() {
if (this._current === 'off') return;
this._stopBlink();
this._write(0);
this._current = 'off';
log.info('led', 'WiFi 指示灯 → 熄灭');
}
blink(intervalMs = BLINK_INTERVAL_MS) {
if (this._current === 'blink') return;
this._stopBlink();
this._blinkState = true;
this._write(1);
this._blinkTimer = setInterval(() => {
this._blinkState = !this._blinkState;
this._write(this._blinkState ? 1 : 0);
}, intervalMs);
this._current = 'blink';
log.info('led', 'WiFi 指示灯 → 闪烁');
}
destroy() {
this._stopBlink();
this._write(0);
this._current = 'off';
}
_stopBlink() {
if (this._blinkTimer) {
clearInterval(this._blinkTimer);
this._blinkTimer = null;
}
}
_write(val) {
const on = !!val;
log.debug('led', `[vfd] WiFiplay<= ${on ? 1 : 0}`);
if (on) vfdOn('play');
else vfdOff('play');
}
}
class BtLed {
constructor() {
this._blinkTimer = null;
this._blinkState = false;
this._current = null;
}
on() {
if (this._current === 'on') return;
this._stopBlink();
this._write(1);
this._current = 'on';
log.info('led', 'BT 指示灯 → 常亮');
}
off() {
if (this._current === 'off') return;
this._stopBlink();
this._write(0);
this._current = 'off';
log.info('led', 'BT 指示灯 → 熄灭');
}
blink(intervalMs = BLINK_INTERVAL_MS) {
if (this._current === 'blink') return;
this._stopBlink();
this._blinkState = true;
this._write(1);
this._blinkTimer = setInterval(() => {
this._blinkState = !this._blinkState;
this._write(this._blinkState ? 1 : 0);
}, intervalMs);
this._current = 'blink';
log.info('led', 'BT 指示灯 → 闪烁');
}
destroy() {
this._stopBlink();
this._write(0);
this._current = 'off';
}
_stopBlink() {
if (this._blinkTimer) {
clearInterval(this._blinkTimer);
this._blinkTimer = null;
}
}
_write(_val) {
log.debug('led', '[vfd] BT 无 OpenVFD 映射,忽略');
}
}
class Display {
constructor() {
this._blinkTimer = null;
}
showAP() {
this._stopBlink();
this._write('#m3AP ');
log.info('display', '显示屏 → AP');
}
showConn() {
this._stopBlink();
this._write('#m3Conn');
log.info('display', '显示屏 → Conn闪烁');
let visible = true;
const blink = () => {
visible = !visible;
this._write(visible ? '#m3Conn' : '#c1');
this._blinkTimer = setTimeout(blink, visible ? 1000 : 500);
};
this._blinkTimer = setTimeout(blink, 1000);
}
showErr0() {
this._stopBlink();
this._write('#m3Err0');
log.info('display', '显示屏 → Err0');
}
showTime() {
this._stopBlink();
this._write('#s1');
log.info('display', '显示屏 → 时间');
}
showPin(pin) {
this._stopBlink();
const s = String(pin || '').padStart(4, '0').slice(-4);
this._write('#m2' + s);
log.info('display', `显示屏 → PIN: ${s}(闪烁)`);
let visible = true;
const blink = () => {
visible = !visible;
this._write(visible ? '#m2' + s : '#c1');
this._blinkTimer = setTimeout(blink, visible ? 1000 : 500);
};
this._blinkTimer = setTimeout(blink, 1000);
}
_stopBlink() {
if (this._blinkTimer) {
clearTimeout(this._blinkTimer);
clearInterval(this._blinkTimer);
this._blinkTimer = null;
}
}
_write(val) {
log.debug('display', `[vfd] ${val}`);
}
}
class StatusLed {
setSetup() {
vfdOff('alarm');
log.debug('led', '[vfd] alarmpwr<= 0');
log.info('led', '状态灯 → SETUP未激活');
}
setApps() {
vfdOn('alarm');
// 部分 OpenVFD 驱动单次写入生效慢,短延迟再写一次
setTimeout(() => vfdOn('alarm'), 50);
log.debug('led', '[vfd] alarmpwr<= 1');
log.info('led', '状态灯 → APPS已激活');
}
off() {
vfdOff('alarm');
log.debug('led', '[vfd] alarmpwr<= 0 (off)');
}
}
class LanLed {
constructor() {
this._timer = null;
this._current = null;
}
start() {
this._sync();
this._timer = setInterval(() => this._sync(), LAN_POLL_MS);
}
stop() {
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
vfdWifiPair(false);
this._current = null;
}
_sync() {
const up = hasLanCableCarrier();
if (up) {
if (this._current !== 'on') {
vfdWifiPair(true);
this._current = 'on';
log.info('led', 'LAN有线 carrier→ 亮');
}
} else if (this._current !== 'off') {
vfdWifiPair(false);
this._current = 'off';
log.info('led', 'LAN有线 carrier→ 灭');
}
}
}
const lan = new LanLed();
module.exports = new WifiLed();
module.exports.bt = new BtLed();
module.exports.status = new StatusLed();
module.exports.display = new Display();
module.exports.lan = lan;