feat(led): drive VFD digits via vfdservice pipe (TITLE/CLOCK), keep display API

This commit is contained in:
stswangzhiping
2026-03-29 13:40:56 +08:00
parent a6e2f7ab8f
commit 23afd24f4d
2 changed files with 72 additions and 11 deletions

View File

@@ -13,15 +13,71 @@ const { hasLanCableCarrier } = require('./network');
* alarm → pwrSETUP=灭 / APPS=亮)
* BT → 无 sysfs仅日志
*
* 数码管AP/Conn/时间等):仍仅 debug 输出,不接 sysfs
* 数码管vfdservice 管道660 字节mode=4 TITLE / mode=1 CLOCK与板端 Demo 一致)
* CLAWD_VFD_PIPE 默认 /tmp/openvfd_service管道不存在时仅 debug不抛错。
*
* CLAWD_OPENVFD_PATH 默认 /sys/class/leds/openvfd
* CLAWD_OPENVFD_PATH 默认 /sys/class/leds/openvfd(图标灯)
*/
const BLINK_INTERVAL_MS = 500;
const LAN_POLL_MS = 500;
const VFD_BASE = process.env.CLAWD_OPENVFD_PATH || '/sys/class/leds/openvfd';
const VFD_PIPE = process.env.CLAWD_VFD_PIPE || '/tmp/openvfd_service';
const VFD_BUF_SIZE = 660;
const VFD_MODE_CLOCK = 1;
const VFD_MODE_TITLE = 4;
const VFD_TITLE_OFF = 20;
function vfdPipePresent() {
try {
return fs.existsSync(VFD_PIPE);
} catch (_) {
return false;
}
}
/** TITLE固定 4 字符ASCII供 vfdservice 解析为段码 */
function vfdTitleNormalize(raw) {
return String(raw || '')
.toUpperCase()
.replace(/[^A-Z0-9 \-]/g, ' ')
.slice(0, 4)
.padEnd(4, ' ');
}
function writeVfdPipeTitle(text4) {
if (!vfdPipePresent()) return;
const seg = vfdTitleNormalize(text4);
const data = Buffer.alloc(VFD_BUF_SIZE);
data.writeUInt16LE(VFD_MODE_TITLE, 0);
try {
data.write(`${seg}\0`, VFD_TITLE_OFF, 'ascii');
} catch (e) {
log.debug('display', `openvfd title encode: ${e.message}`);
return;
}
try {
const fd = fs.openSync(VFD_PIPE, 'w');
fs.writeSync(fd, data);
fs.closeSync(fd);
} catch (e) {
log.debug('display', `openvfd pipe title: ${e.message}`);
}
}
function writeVfdPipeClock() {
if (!vfdPipePresent()) return;
const data = Buffer.alloc(VFD_BUF_SIZE);
data.writeUInt16LE(VFD_MODE_CLOCK, 0);
try {
const fd = fs.openSync(VFD_PIPE, 'w');
fs.writeSync(fd, data);
fs.closeSync(fd);
} catch (e) {
log.debug('display', `openvfd pipe clock: ${e.message}`);
}
}
function vfdOn(icon) {
try {
@@ -166,18 +222,18 @@ class Display {
showAP() {
this._stopBlink();
this._write('#m3AP ');
this._pipeTitle('AP ', '#m3AP ');
log.info('display', '显示屏 → AP');
}
showConn() {
this._stopBlink();
this._write('#m3Conn');
this._pipeTitle('Conn', '#m3Conn');
log.info('display', '显示屏 → Conn闪烁');
let visible = true;
const blink = () => {
visible = !visible;
this._write(visible ? '#m3Conn' : '#c1');
this._pipeTitle(visible ? 'Conn' : ' ', visible ? '#m3Conn' : '#c1');
this._blinkTimer = setTimeout(blink, visible ? 1000 : 500);
};
this._blinkTimer = setTimeout(blink, 1000);
@@ -185,25 +241,26 @@ class Display {
showErr0() {
this._stopBlink();
this._write('#m3Err0');
this._pipeTitle('Err0', '#m3Err0');
log.info('display', '显示屏 → Err0');
}
showTime() {
this._stopBlink();
this._write('#s1');
log.debug('display', '[vfd] #s1');
writeVfdPipeClock();
log.info('display', '显示屏 → 时间');
}
showPin(pin) {
this._stopBlink();
const s = String(pin || '').padStart(4, '0').slice(-4);
this._write('#m2' + s);
this._pipeTitle(s, '#m2' + s);
log.info('display', `显示屏 → PIN: ${s}(闪烁)`);
let visible = true;
const blink = () => {
visible = !visible;
this._write(visible ? '#m2' + s : '#c1');
this._pipeTitle(visible ? s : ' ', visible ? '#m2' + s : '#c1');
this._blinkTimer = setTimeout(blink, visible ? 1000 : 500);
};
this._blinkTimer = setTimeout(blink, 1000);
@@ -217,8 +274,10 @@ class Display {
}
}
_write(val) {
log.debug('display', `[vfd] ${val}`);
/** 保留原 #m3/#m2/#c1 的 debug 语义,并写 vfdservice TITLE */
_pipeTitle(fourCharText, debugLegacy) {
log.debug('display', `[vfd] ${debugLegacy}`);
writeVfdPipeTitle(fourCharText);
}
}