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
This commit is contained in:
stswangzhiping
2026-03-28 21:50:56 +08:00
parent 347b19a0c9
commit 8f5a8ec896
5 changed files with 50 additions and 12 deletions

View File

@@ -171,6 +171,8 @@ CLAWD_LOG_FILE=1
# CLAWD_DISABLE_BT=1 # CLAWD_DISABLE_BT=1
# OpenVFD sysfs 根路径(默认 /sys/class/leds/openvfd # OpenVFD sysfs 根路径(默认 /sys/class/leds/openvfd
# CLAWD_OPENVFD_PATH=/sys/class/leds/openvfd # CLAWD_OPENVFD_PATH=/sys/class/leds/openvfd
# LAN 灯跟 carrier务必设为实际以太网口如 end0拔网线才能可靠熄灭
# CLAWD_ETH_IFACE=end0
EOF EOF
info "环境变量文件已创建:$ENV_FILE" info "环境变量文件已创建:$ENV_FILE"
fi fi

View File

@@ -83,8 +83,11 @@ class ClawClient {
async start() { async start() {
log.info('clawd', `启动中... 服务器 = ${this._cfg.server}`); log.info('clawd', `启动中... 服务器 = ${this._cfg.server}`);
// 启动时全灭WS 连接后由 _applyStatus() 按实际状态设置 // 启动时关 alarm若本地已记为已激活立即亮 pwr不等首包 connected
led.status.off(); led.status.off();
if (this._cfg.activated) {
led.status.setApps();
}
this._startSdNotify(); this._startSdNotify();
@@ -213,6 +216,9 @@ class ClawClient {
this._backoff = 1_000; this._backoff = 1_000;
this._wsFailCount = 0; // 连接成功,重置失败计数 this._wsFailCount = 0; // 连接成功,重置失败计数
this._hasEverConnected = true; // 标记已成功连接过 this._hasEverConnected = true; // 标记已成功连接过
if (this._cfg.activated) {
led.status.setApps();
}
this._sendConnect(); this._sendConnect();
this._startPing(); this._startPing();
// 显示由 _onConnected 根据 status 设置,不在此处提前 showTime // 显示由 _onConnected 根据 status 设置,不在此处提前 showTime
@@ -332,12 +338,16 @@ class ClawClient {
log.warn('clawd', '硬件指纹不符,清除凭证重新注册...'); log.warn('clawd', '硬件指纹不符,清除凭证重新注册...');
this._cfg.claw_id = null; this._cfg.claw_id = null;
this._cfg.token = null; this._cfg.token = null;
this._cfg.activated = false;
config.save(this._cfg); config.save(this._cfg);
led.status.setSetup();
} else if (msg.msg && msg.msg.includes('invalid')) { } else if (msg.msg && msg.msg.includes('invalid')) {
log.warn('clawd', '凭证无效,清除凭证重新注册...'); log.warn('clawd', '凭证无效,清除凭证重新注册...');
this._cfg.claw_id = null; this._cfg.claw_id = null;
this._cfg.token = null; this._cfg.token = null;
this._cfg.activated = false;
config.save(this._cfg); config.save(this._cfg);
led.status.setSetup();
} }
break; break;
default: default:
@@ -369,6 +379,8 @@ class ClawClient {
_applyStatus(msg) { _applyStatus(msg) {
if (msg.status === 'inactive') { if (msg.status === 'inactive') {
this._cfg.activated = false;
config.save(this._cfg);
led.status.setSetup(); led.status.setSetup();
led.display.showPin(msg.pin); led.display.showPin(msg.pin);
const id = String(this._cfg.claw_id || '').padEnd(6); const id = String(this._cfg.claw_id || '').padEnd(6);
@@ -383,6 +395,8 @@ class ClawClient {
log.info('clawd', '等待激活,心跳正常运行...'); log.info('clawd', '等待激活,心跳正常运行...');
this._updateOpenClawOrigin('0000'); this._updateOpenClawOrigin('0000');
} else { } else {
this._cfg.activated = true;
config.save(this._cfg);
led.status.setApps(); led.status.setApps();
led.display.showTime(); led.display.showTime();
log.info('clawd', `已激活 claw_id = ${this._cfg.claw_id}`); log.info('clawd', `已激活 claw_id = ${this._cfg.claw_id}`);

View File

@@ -17,6 +17,8 @@ const DEFAULTS = {
claw_id: null, claw_id: null,
token: null, token: null,
heartbeat_interval: 30, // 秒 heartbeat_interval: 30, // 秒
/** 云端已激活:用于启动/重连时立即点亮 alarmpwr不等首包 connected */
activated: false,
}; };
function load() { function load() {

View File

@@ -2,15 +2,15 @@
const fs = require('fs'); const fs = require('fs');
const log = require('./logger'); const log = require('./logger');
const { hasWiredCarrier } = require('./network'); const { hasLanCableCarrier } = require('./network');
/** /**
* OpenVFD 图标:/sys/class/leds/openvfd/led_on|led_off写入图标名 * OpenVFD 图标:/sys/class/leds/openvfd/led_on|led_off写入图标名
* *
* 映射: * 映射与面板丝印一致play=WiFi 状态wifi+eth=有线链路)
* wifi + eth 同亮/同灭 → 产品 WiFi 灯 * play → 产品 WiFi 灯(配网逻辑 on/off/blink
* wifi + eth 同亮/同灭 → LAN有线插拔见 hasLanCableCarrier / CLAWD_ETH_IFACE
* alarm → pwrSETUP=灭 / APPS=亮) * alarm → pwrSETUP=灭 / APPS=亮)
* play → lan有线 carrier
* BT → 无 sysfs仅日志 * BT → 无 sysfs仅日志
* *
* 数码管AP/Conn/时间等):仍仅 debug 输出,不接 sysfs。 * 数码管AP/Conn/时间等):仍仅 debug 输出,不接 sysfs。
@@ -19,7 +19,7 @@ const { hasWiredCarrier } = require('./network');
*/ */
const BLINK_INTERVAL_MS = 500; const BLINK_INTERVAL_MS = 500;
const LAN_POLL_MS = 3000; const LAN_POLL_MS = 1000;
const VFD_BASE = process.env.CLAWD_OPENVFD_PATH || '/sys/class/leds/openvfd'; const VFD_BASE = process.env.CLAWD_OPENVFD_PATH || '/sys/class/leds/openvfd';
@@ -100,8 +100,9 @@ class WifiLed {
_write(val) { _write(val) {
const on = !!val; const on = !!val;
log.debug('led', `[vfd] WiFiwifi+eth<= ${on ? 1 : 0}`); log.debug('led', `[vfd] WiFiplay<= ${on ? 1 : 0}`);
vfdWifiPair(on); if (on) vfdOn('play');
else vfdOff('play');
} }
} }
@@ -231,6 +232,8 @@ class StatusLed {
setApps() { setApps() {
vfdOn('alarm'); vfdOn('alarm');
// 部分 OpenVFD 驱动单次写入生效慢,短延迟再写一次
setTimeout(() => vfdOn('alarm'), 50);
log.debug('led', '[vfd] alarmpwr<= 1'); log.debug('led', '[vfd] alarmpwr<= 1');
log.info('led', '状态灯 → APPS已激活'); log.info('led', '状态灯 → APPS已激活');
} }
@@ -257,20 +260,20 @@ class LanLed {
clearInterval(this._timer); clearInterval(this._timer);
this._timer = null; this._timer = null;
} }
vfdOff('play'); vfdWifiPair(false);
this._current = null; this._current = null;
} }
_sync() { _sync() {
const up = hasWiredCarrier(); const up = hasLanCableCarrier();
if (up) { if (up) {
if (this._current !== 'on') { if (this._current !== 'on') {
vfdOn('play'); vfdWifiPair(true);
this._current = 'on'; this._current = 'on';
log.info('led', 'LAN有线 carrier→ 亮'); log.info('led', 'LAN有线 carrier→ 亮');
} }
} else if (this._current !== 'off') { } else if (this._current !== 'off') {
vfdOff('play'); vfdWifiPair(false);
this._current = 'off'; this._current = 'off';
log.info('led', 'LAN有线 carrier→ 灭'); log.info('led', 'LAN有线 carrier→ 灭');
} }

View File

@@ -62,6 +62,22 @@ function hasWiredCarrier() {
return getWiredIfaceWithCarrier() !== null; return getWiredIfaceWithCarrier() !== null;
} }
/**
* LAN 面板灯专用:若设置 CLAWD_ETH_IFACE只认该口 carrier拔网线必灭
* 未设置时退回 hasWiredCarrier()(可能受其它网口影响,建议在 box 上配置 end0 等)。
*/
function hasLanCableCarrier() {
const explicit = process.env.CLAWD_ETH_IFACE;
if (explicit) {
try {
return fs.readFileSync(`/sys/class/net/${explicit}/carrier`, 'utf8').trim() === '1';
} catch (_) {
return false;
}
}
return hasWiredCarrier();
}
function _tryPingInternet() { function _tryPingInternet() {
try { try {
run('ping -c 1 -W 3 8.8.8.8'); run('ping -c 1 -W 3 8.8.8.8');
@@ -312,6 +328,7 @@ function getLocalIps() {
module.exports = { module.exports = {
hasInternet, hasInternet,
hasWiredCarrier, hasWiredCarrier,
hasLanCableCarrier,
hasWiredInternetProbe, hasWiredInternetProbe,
getWiredIfaceWithCarrier, getWiredIfaceWithCarrier,
hasSavedWifiConnection, hasSavedWifiConnection,