feat(led): OpenVFD sysfs (wifi+eth, alarm, play/lan), skip BT without hci
- Write /sys/class/leds/openvfd led_on/led_off (CLAWD_OPENVFD_PATH) - WiFi product LED: wifi+eth together; SETUP/APPS -> alarm off/on - LanLed polls hasWiredCarrier -> play; export led.lan, start/stop from client - BT monitor only if CLAWD_DISABLE_BT unset and hci* exists (RK3528) - Display strings remain debug-only (数码管暂不驱动) - install.sh env template: CLAWD_DISABLE_BT, CLAWD_OPENVFD_PATH comments Made-with: Cursor
This commit is contained in:
@@ -167,6 +167,10 @@ CLAWD_LOG_LEVEL=info
|
||||
CLAWD_LOG_FILE=1
|
||||
# 自定义服务器地址(留空则读 config.json)
|
||||
# CLAWD_SERVER=wss://claw.cutos.ai/ws
|
||||
# 无蓝牙机型(如部分 RK3528):强制不写 bluetoothctl
|
||||
# CLAWD_DISABLE_BT=1
|
||||
# OpenVFD sysfs 根路径(默认 /sys/class/leds/openvfd)
|
||||
# CLAWD_OPENVFD_PATH=/sys/class/leds/openvfd
|
||||
EOF
|
||||
info "环境变量文件已创建:$ENV_FILE ✓"
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const { getNotifySocket } = require('./systemd-env');
|
||||
const WebSocket = require('ws');
|
||||
const { execSync, execFileSync } = require('child_process');
|
||||
@@ -20,6 +21,15 @@ const PING_INTERVAL_MS = 15_000;
|
||||
const HEARTBEAT_INTERVAL_MS = 10_000; // 心跳间隔:10 秒,用于快速感知网络状态
|
||||
const METRICS_EVERY_N = 3; // 每 N 次心跳采集一次指标(= 30 秒)
|
||||
|
||||
/** CLAWD_DISABLE_BT=1 或系统无 hci* 时不启动 bluetoothctl 轮询(如 RK3528 无蓝牙) */
|
||||
function bluetoothAdapterPresent() {
|
||||
try {
|
||||
return fs.readdirSync('/sys/class/bluetooth').some((n) => n.startsWith('hci'));
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ClawClient {
|
||||
constructor() {
|
||||
this._cfg = config.load();
|
||||
@@ -165,6 +175,7 @@ class ClawClient {
|
||||
this._clearHeartbeat();
|
||||
this._clearPing();
|
||||
if (this._sdTimer) { clearInterval(this._sdTimer); this._sdTimer = null; }
|
||||
led.lan.stop();
|
||||
if (this._btMonitor) { this._btMonitor.stop(); this._btMonitor = null; }
|
||||
if (this._provisionMgr) { this._provisionMgr.stop(); this._provisionMgr = null; }
|
||||
this._frpc.stop();
|
||||
|
||||
109
lib/led.js
109
lib/led.js
@@ -1,19 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const log = require('./logger');
|
||||
const { hasWiredCarrier } = require('./network');
|
||||
|
||||
/**
|
||||
* 前面板指示灯 / VFD 逻辑(原 openvfd sysfs)。
|
||||
* 硬件路径因板型而异,当前不直接写 sysfs:仅在 debug 记录拟写入内容,业务状态仍以 info 输出。
|
||||
* OpenVFD 图标:/sys/class/leds/openvfd/led_on|led_off(写入图标名)。
|
||||
*
|
||||
* WiFi 灯 (b5): 1 = 亮, 0 = 灭(正逻辑)
|
||||
* BT 灯 (b6): 1 = 亮, 0 = 灭(正逻辑)
|
||||
* SETUP/APPS (b1/b2): 反逻辑,与 claw 激活状态互斥
|
||||
* 映射:
|
||||
* wifi + eth 同亮/同灭 → 产品 WiFi 灯
|
||||
* alarm → pwr(SETUP=灭 / APPS=亮)
|
||||
* play → lan(有线 carrier)
|
||||
* BT → 无 sysfs,仅日志
|
||||
*
|
||||
* 恢复真机显示时:可在此类 _write 中接新驱动或 CLAWD_* 路径。
|
||||
* 数码管(AP/Conn/时间等):仍仅 debug 输出,不接 sysfs。
|
||||
*
|
||||
* CLAWD_OPENVFD_PATH 默认 /sys/class/leds/openvfd
|
||||
*/
|
||||
|
||||
const BLINK_INTERVAL_MS = 500;
|
||||
const LAN_POLL_MS = 3000;
|
||||
|
||||
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() {
|
||||
@@ -65,7 +99,9 @@ class WifiLed {
|
||||
}
|
||||
|
||||
_write(val) {
|
||||
log.debug('led', `[vfd] WiFi LED (b5) <= ${val}`);
|
||||
const on = !!val;
|
||||
log.debug('led', `[vfd] WiFi(wifi+eth)<= ${on ? 1 : 0}`);
|
||||
vfdWifiPair(on);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +154,8 @@ class BtLed {
|
||||
}
|
||||
}
|
||||
|
||||
_write(val) {
|
||||
log.debug('led', `[vfd] BT LED (b6) <= ${val}`);
|
||||
_write(_val) {
|
||||
log.debug('led', '[vfd] BT 无 OpenVFD 映射,忽略');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,28 +224,63 @@ class Display {
|
||||
|
||||
class StatusLed {
|
||||
setSetup() {
|
||||
this._write('SETUP', 0);
|
||||
this._write('APPS', 1);
|
||||
vfdOff('alarm');
|
||||
log.debug('led', '[vfd] alarm(pwr)<= 0');
|
||||
log.info('led', '状态灯 → SETUP(未激活)');
|
||||
}
|
||||
|
||||
setApps() {
|
||||
this._write('SETUP', 1);
|
||||
this._write('APPS', 0);
|
||||
vfdOn('alarm');
|
||||
log.debug('led', '[vfd] alarm(pwr)<= 1');
|
||||
log.info('led', '状态灯 → APPS(已激活)');
|
||||
}
|
||||
|
||||
off() {
|
||||
this._write('SETUP', 1);
|
||||
this._write('APPS', 1);
|
||||
}
|
||||
|
||||
_write(which, val) {
|
||||
log.debug('led', `[vfd] 状态灯 ${which} (b1/b2 反逻辑) <= ${val}`);
|
||||
vfdOff('alarm');
|
||||
log.debug('led', '[vfd] alarm(pwr)<= 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;
|
||||
}
|
||||
vfdOff('play');
|
||||
this._current = null;
|
||||
}
|
||||
|
||||
_sync() {
|
||||
const up = hasWiredCarrier();
|
||||
if (up) {
|
||||
if (this._current !== 'on') {
|
||||
vfdOn('play');
|
||||
this._current = 'on';
|
||||
log.info('led', 'LAN(有线 carrier)→ 亮');
|
||||
}
|
||||
} else if (this._current !== 'off') {
|
||||
vfdOff('play');
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user