diff --git a/lib/led.js b/lib/led.js new file mode 100644 index 0000000..be8b734 --- /dev/null +++ b/lib/led.js @@ -0,0 +1,86 @@ +'use strict'; + +const fs = require('fs'); +const log = require('./logger'); + +/** + * WiFi 指示灯控制 + * + * 硬件路径: /sys/devices/platform/openvfd/attr/b5 + * 1 = 亮 0 = 灭 + * + * LED 状态与 WiFi 状态的对应关系: + * - WiFi 已连接且互联网畅通 → 常亮 + * - WiFi 连接中(正在尝试) → 闪烁 + * - WiFi 未连接 / 无互联网 → 熄灭 + */ + +const LED_PATH = process.env.CLAWD_LED_PATH || '/sys/devices/platform/openvfd/attr/b5'; +const BLINK_INTERVAL_MS = 500; // 闪烁间隔(ms) + +class WifiLed { + constructor() { + this._blinkTimer = null; + this._blinkState = false; + this._current = null; // 'on' | 'off' | 'blink' + } + + /** 常亮 */ + on() { + if (this._current === 'on') return; + this._stopBlink(); + this._write(1); + this._current = 'on'; + log.debug('led', 'WiFi 指示灯 → 常亮'); + } + + /** 熄灭 */ + off() { + if (this._current === 'off') return; + this._stopBlink(); + this._write(0); + this._current = 'off'; + log.debug('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.debug('led', 'WiFi 指示灯 → 闪烁'); + } + + /** 释放资源,关灯 */ + destroy() { + this._stopBlink(); + this._write(0); + this._current = 'off'; + } + + // ── 内部 ────────────────────────────────────────────────────────────────── + + _stopBlink() { + if (this._blinkTimer) { + clearInterval(this._blinkTimer); + this._blinkTimer = null; + } + } + + _write(val) { + try { + fs.writeFileSync(LED_PATH, String(val)); + } catch (_) { + // 设备不支持 openvfd 时静默忽略(开发机上不会报错) + } + } +} + +// 全局单例,整个进程共用 +module.exports = new WifiLed(); diff --git a/lib/provisioning.js b/lib/provisioning.js index be28301..2eb431f 100644 --- a/lib/provisioning.js +++ b/lib/provisioning.js @@ -5,6 +5,7 @@ const log = require('./logger'); const { hasInternet, hasSavedWifiConnection, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, AP_IP } = require('./network'); const { DnsHijack } = require('./dns-hijack'); const { CaptiveServer } = require('./captive-server'); +const led = require('./led'); const MONITOR_INTERVAL_MS = 30_000; const BOOT_WAIT_MAX_MS = 20_000; // 等待 NM 自动连接的最大时间 @@ -31,6 +32,8 @@ class ProvisionManager extends EventEmitter { } async start() { + led.off(); // 初始状态:灭灯 + // WiFi 已连接 → 直接进入 STA 模式 if (isWifiStaConnected()) { this._state = 'sta'; @@ -43,6 +46,7 @@ class ProvisionManager extends EventEmitter { // 有已保存的 WiFi 配置 → 等 NM 自动连接(重启场景) if (hasSavedWifiConnection()) { log.info('provision', '发现已保存的 WiFi 配置,等待 NetworkManager 自动连接...'); + led.blink(); // 等待自动重连期间闪烁 const connected = await this._waitForWifiConnect(); if (connected) { this._state = 'sta'; @@ -64,7 +68,10 @@ class ProvisionManager extends EventEmitter { } _emitNetworkReady() { - if (hasInternet()) this.emit('network-ready'); + if (hasInternet()) { + led.on(); // 网络畅通 → 常亮 + this.emit('network-ready'); + } } /** @@ -90,6 +97,7 @@ class ProvisionManager extends EventEmitter { this._stopMonitor(); this._stopAll(); this._state = 'idle'; + led.destroy(); // 停止时关灯、释放闪烁定时器 } // ── 进入 AP 模式 ───────────────────────────────────────────────────────── @@ -97,6 +105,8 @@ class ProvisionManager extends EventEmitter { _enterAP() { if (this._state === 'ap') return; + led.off(); // AP 模式:WiFi 未连接,灭灯 + try { // AP 模式下无法扫描 WiFi,必须在开 AP 之前扫描并缓存 log.info('provision', '扫描周边 WiFi...'); @@ -131,6 +141,7 @@ class ProvisionManager extends EventEmitter { this._state = 'connecting'; log.info('provision', `用户请求连接 WiFi: ${ssid}`); + led.blink(); // 正在连接 → 闪烁 this._stopAPServices(); @@ -139,12 +150,13 @@ class ProvisionManager extends EventEmitter { if (result.success) { this._state = 'sta'; log.info('provision', `WiFi 已连接: ${ssid}`); + led.on(); // 连接成功 → 常亮 this.emit('network-ready'); return result; } log.warn('provision', `WiFi 连接失败: ${result.error},重新启动 AP`); - this._enterAP(); + this._enterAP(); // _enterAP 内部会调用 led.off() return result; } @@ -158,7 +170,8 @@ class ProvisionManager extends EventEmitter { if (this._state === 'sta' && !wifiUp) { log.warn('provision', 'WiFi 连接已断开,重新启动 AP'); - this._enterAP(); + this._enterAP(); // 内部调用 led.off() + return; } if (this._state === 'ap' && wifiUp) { @@ -167,6 +180,15 @@ class ProvisionManager extends EventEmitter { this._state = 'sta'; this.emit('network-ready'); } + + // STA 模式下持续监测互联网连通性,实时更新 LED + if (this._state === 'sta') { + if (hasInternet()) { + led.on(); + } else { + led.off(); // WiFi 已连接但无互联网 + } + } }, MONITOR_INTERVAL_MS); }