From 90bba5f34808eff3596af2599c3b274908066668 Mon Sep 17 00:00:00 2001 From: stswangzhiping <59632378+stswangzhiping@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:25:54 +0800 Subject: [PATCH] feat: add SETUP/APPS status LEDs (b2/b1), toggle on activation Made-with: Cursor --- lib/client.js | 7 +++++++ lib/led.js | 53 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/lib/client.js b/lib/client.js index c28f40f..85ceb23 100644 --- a/lib/client.js +++ b/lib/client.js @@ -9,6 +9,7 @@ const { collect } = require('./metrics'); const { getDashboardInfo, startTtyd, FrpcManager } = require('./frpc'); // getDashboardInfo 也用于心跳中定期刷新 const { ProvisionManager } = require('./provisioning'); const { hasInternet } = require('./network'); +const led = require('./led'); const MAX_BACKOFF_MS = 60_000; const PONG_TIMEOUT_MS = 15_000; @@ -61,6 +62,9 @@ class ClawClient { async start() { log.info('clawd', `启动中... 服务器 = ${this._cfg.server}`); + // 启动时默认 SETUP 亮(未激活状态),连接成功后按实际状态切换 + led.status.setSetup(); + this._startSdNotify(); // 启动 AP 配网管理器(等待已保存 WiFi 自动连接,超时再开 AP) @@ -105,6 +109,7 @@ class ClawClient { if (this._provisionMgr) { this._provisionMgr.stop(); this._provisionMgr = null; } this._frpc.stop(); if (this._ws) this._ws.terminate(); + led.status.off(); // 进程退出,两灯全灭 this._sdNotify('STOPPING=1'); log.info('clawd', '已停止'); log.close(); @@ -234,6 +239,7 @@ class ClawClient { } if (msg.status === 'inactive') { + led.status.setSetup(); // 未激活 → SETUP 亮 const id = String(msg.claw_id).padEnd(6); const pin = String(msg.pin); log.info('clawd', ''); @@ -245,6 +251,7 @@ class ClawClient { log.info('clawd', ''); log.info('clawd', '等待激活,心跳正常运行...'); } else { + led.status.setApps(); // 已激活 → APPS 亮 log.info('clawd', `已激活 claw_id = ${msg.claw_id}`); } diff --git a/lib/led.js b/lib/led.js index d94d789..47d268c 100644 --- a/lib/led.js +++ b/lib/led.js @@ -4,18 +4,22 @@ const fs = require('fs'); const log = require('./logger'); /** - * WiFi 指示灯控制 + * 前面板指示灯控制 * - * 硬件路径: /sys/devices/platform/openvfd/attr/b5 - * 1 = 亮 0 = 灭 - * - * LED 状态与 WiFi 状态的对应关系: + * WiFi 灯 (b5): 1 = 亮, 0 = 灭(正逻辑) * - WiFi 已连接且互联网畅通 → 常亮 * - WiFi 连接中(正在尝试) → 闪烁 * - WiFi 未连接 / 无互联网 → 熄灭 + * + * SETUP 灯 (b2): 0 = 亮, 1 = 灭(反逻辑,与 APPS 互斥) + * APPS 灯 (b1): 0 = 亮, 1 = 灭(反逻辑,与 SETUP 互斥) + * - claw 未激活 → SETUP 亮,APPS 灭 + * - claw 已激活 → APPS 亮,SETUP 灭 */ const LED_PATH = process.env.CLAWD_LED_PATH || '/sys/devices/platform/openvfd/attr/b5'; +const SETUP_LED_PATH = '/sys/devices/platform/openvfd/attr/b2'; +const APPS_LED_PATH = '/sys/devices/platform/openvfd/attr/b1'; const BLINK_INTERVAL_MS = 500; // 闪烁间隔(ms) class WifiLed { @@ -82,5 +86,42 @@ class WifiLed { } } +// ── SETUP / APPS 状态灯 ─────────────────────────────────────────────────────── + +/** + * SETUP 灯(b2)与 APPS 灯(b1)互斥控制。 + * 两灯均为反逻辑:写 0 = 亮,写 1 = 灭。 + */ +class StatusLed { + /** claw 未激活 → SETUP 亮,APPS 灭 */ + setSetup() { + this._write(SETUP_LED_PATH, 0); // SETUP 亮 + this._write(APPS_LED_PATH, 1); // APPS 灭 + log.info('led', '状态灯 → SETUP(未激活)'); + } + + /** claw 已激活 → APPS 亮,SETUP 灭 */ + setApps() { + this._write(SETUP_LED_PATH, 1); // SETUP 灭 + this._write(APPS_LED_PATH, 0); // APPS 亮 + log.info('led', '状态灯 → APPS(已激活)'); + } + + /** 两灯全灭(进程退出时调用) */ + off() { + this._write(SETUP_LED_PATH, 1); + this._write(APPS_LED_PATH, 1); + } + + _write(path, val) { + try { + fs.writeFileSync(path, String(val)); + } catch (e) { + log.warn('led', `写入失败 (${path}): ${e.message}`); + } + } +} + // 全局单例,整个进程共用 -module.exports = new WifiLed(); +module.exports = new WifiLed(); +module.exports.status = new StatusLed();