feat: BtMonitor 监控 bluetoothctl 状态驱动 BT 指示灯
新增 lib/bt-monitor.js: - 每 3 秒轮询 bluetoothctl show + hcitool con - connected(ACL 连接存在)→ 常亮 - scanning/connecting(Discovering: yes)→ 闪烁 - 无 adapter / Powered: no / 静止 → 熄灭 client.js:启动时开启 BtMonitor,stop() 时清理 provisioning.js:移除所有 led.bt 调用,BT 灯统一由 BtMonitor 管理 Made-with: Cursor
This commit is contained in:
107
lib/bt-monitor.js
Normal file
107
lib/bt-monitor.js
Normal file
@@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const log = require('./logger');
|
||||
const led = require('./led');
|
||||
|
||||
const POLL_INTERVAL_MS = 3000;
|
||||
|
||||
function findBin(name, candidates) {
|
||||
for (const p of candidates) {
|
||||
try { fs.accessSync(p); return p; } catch (_) {}
|
||||
}
|
||||
return name; // fallback: rely on PATH
|
||||
}
|
||||
|
||||
/**
|
||||
* 监控蓝牙状态,驱动 BT 指示灯(b6)。
|
||||
*
|
||||
* 状态优先级:
|
||||
* connected → 常亮 (hcitool con 检测到 ACL 连接)
|
||||
* scanning → 闪烁 (bluetoothctl show: Discovering: yes)
|
||||
* connecting → 闪烁 (bluetoothctl devices: 有 Connecting 状态)
|
||||
* off → 熄灭 (adapter 不存在 / Powered: no / 静止)
|
||||
*/
|
||||
class BtMonitor {
|
||||
constructor() {
|
||||
this._timer = null;
|
||||
this._btctl = findBin('bluetoothctl', [
|
||||
'/usr/bin/bluetoothctl', '/bin/bluetoothctl',
|
||||
]);
|
||||
this._hcitool = findBin('hcitool', [
|
||||
'/usr/bin/hcitool', '/usr/sbin/hcitool', '/bin/hcitool',
|
||||
]);
|
||||
}
|
||||
|
||||
start() {
|
||||
this._poll();
|
||||
this._timer = setInterval(() => this._poll(), POLL_INTERVAL_MS);
|
||||
log.info('bt', 'BT 状态监控已启动');
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer);
|
||||
this._timer = null;
|
||||
}
|
||||
led.bt.off();
|
||||
log.info('bt', 'BT 状态监控已停止');
|
||||
}
|
||||
|
||||
// ── 内部 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
_poll() {
|
||||
try {
|
||||
const state = this._getBtState();
|
||||
if (state === 'connected') {
|
||||
led.bt.on();
|
||||
} else if (state === 'scanning' || state === 'connecting') {
|
||||
led.bt.blink();
|
||||
} else {
|
||||
led.bt.off();
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn('bt', `状态检测异常: ${e.message}`);
|
||||
led.bt.off();
|
||||
}
|
||||
}
|
||||
|
||||
_exec(cmd, timeout = 3000) {
|
||||
return execSync(cmd, {
|
||||
timeout,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
encoding: 'utf8',
|
||||
});
|
||||
}
|
||||
|
||||
_getBtState() {
|
||||
// 1. 检查 adapter 是否存在且已开启
|
||||
let show;
|
||||
try {
|
||||
show = this._exec(`${this._btctl} show`);
|
||||
} catch (_) {
|
||||
return 'off'; // bluetoothctl 不可用 或 无 adapter
|
||||
}
|
||||
if (!show.includes('Powered: yes')) return 'off';
|
||||
|
||||
// 2. 检查是否有已连接的 ACL 设备(A2DP 连接)
|
||||
try {
|
||||
const con = this._exec(`${this._hcitool} con`);
|
||||
if (/ACL\s+[0-9A-Fa-f:]{17}/i.test(con)) return 'connected';
|
||||
} catch (_) {}
|
||||
|
||||
// 3. 部分版本支持 bluetoothctl devices Connected
|
||||
try {
|
||||
const devs = this._exec(`${this._btctl} devices Connected`);
|
||||
if (devs.trim()) return 'connected';
|
||||
} catch (_) {}
|
||||
|
||||
// 4. 检查是否正在扫描
|
||||
if (show.includes('Discovering: yes')) return 'scanning';
|
||||
|
||||
return 'off';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { BtMonitor };
|
||||
Reference in New Issue
Block a user