BrightSu

EasePi R1 HDMI监控屏幕

众所周知,EasePi R1 背后是有个 HDMI 接口的。正好有个大学时出门带的、现在早就吃灰的2K便携屏,于是就粘冰箱上当监控屏用了。

然而官方只给了一个的监控程序lcdsimple,完全不支持自定义,似乎有点simple过头了。 遂决定自己折腾。

最开始用的TTY方案,直接装了个btop,改了改配置文件完事。启动脚本相当简单:

#!/bin/sh
export HOME=/root
# 等 tty1 可写
for i in $(seq 1 40); do
    [ -w /dev/tty1 ] && break
    sleep 0.5
done
exec </dev/tty1 >/dev/tty1 2>&1
exec /usr/bin/btop --force-utf

然而2K下默认的8x16字体太小了,而且btop终究没法加自己想要的各种功能,最后还是决定自己从头写一个。

(此处省略114514字)

总之最后自己拿 Python 搓了一个监控脚本,实现了对服务器节点状态(基于ServerStatus-Hotaru修改,读取API返回的JSON格式数据)、网络吞吐状态、软路由系统状态的监控。

效果如图:


懒狗不想写了,丢给大鲸鱼解释了

核心原理

这个监控屏脚本本质上做了一件事:绕过 Linux 的终端文本层,直接向 HDMI 输出设备(framebuffer)写入像素数据。理解这一点,就理解了整个方案的关键。

为什么 TTY 方案不够用

在嵌入式 Linux(包括 OpenWrt)上,HDMI 输出默认走的是 framebuffer console(fbcon)路径。内核把 HDMI 显示器抽象成一个“字符终端”,你往/dev/tty1写文字,内核会用内置字体把字符渲染成像素,再刷到屏幕上。

这套机制的问题在于:字体是内核编译时固定的,分辨率越高,字越小。2K 屏下默认 8×16 字体几乎不可读,而 OpenWrt 的精简环境又不一定带setfont或大字体包,换字体这条路经常走不通。

直接操作 framebuffer 可以绕过字体限制:

Linux 的 framebuffer 设备/dev/fb0是一块映射到用户空间的显存。**你往这块内存的某个偏移量写入像素的颜色值,屏幕对应位置就会立刻显示那个颜色。**整个过程不经过内核字体渲染,字体大小完全由你的程序控制。

核心操作只有三步:

  • 打开设备:open("/dev/fb0", O_RDWR)
  • 获取屏幕参数:通过ioctl拿到分辨率(xres × yres)和每像素字节数(bits_per_pixel)
  • 内存映射:用 mmap 把显存映射到进程地址空间,然后像操作普通字节数组一样写入像素数据

像素在内存中的位置计算公式是:偏移 = y × 行字节数 + x × 每像素字节数。知道公式后,画矩形、画线、写文字,都只是往正确的位置填颜色。

字体渲染:把 TTF 文字变成像素

直接用 framebuffer 画像素解决了“画在哪里”的问题,但“画什么字”还没解决。脚本的方案是:用 Pillow 把 TTF 字体渲染成一张 RGBA 图像,再把这图像的像素数据按坐标刷进 framebuffer。

Pillow 的ImageFont.truetype()加载 TTF 字体文件,ImageDraw.text()把文字画到内存中的画布上,得到的是一块像素矩阵。这块像素矩阵可以按任意尺寸渲染——你想要 84 号字就渲染 84 号字,不受内核字体限制。

关键障碍:fbcon 会覆盖你的画面

直接写 framebuffer 有一个“敌人”:内核的 fbcon 仍然绑定着/dev/fb0。即使你写入了像素,内核的光标闪烁、控制台滚动、shell 输出都可能随时重绘屏幕,把你的画面覆盖掉。

解决办法是解绑 fbcon。Linux 通过/sys/class/vtconsole/下的 bind 文件控制虚拟终端控制台与 framebuffer 的绑定关系。执行:

echo 0 > /sys/class/vtconsole/vtcon1/bind

就告诉内核:不要再往这块 framebuffer 上画终端文字了。解绑之后,/dev/tty1的文本输出不再显示到屏幕上,你的程序独占整个显示区域。

数据采集:OpenWrt 的 ubus 机制

屏幕内容来自系统数据。脚本通过 OpenWrt 的 ubus 进程间通信总线获取网络接口状态。ubus call network.interface.wan status返回 JSON 格式的接口信息,包含 IP 地址、L3 设备名等。拿到 L3 设备名(如pppoe-wan)后,再从 /sys/class/net/<dev>/statistics/读取累计收发字节数,两次采样求差值就得到实时速率。

整体数据流

flowchart LR
    A1["ubus<br/>接口状态"] --> B1["ubus_status<br/>IP + L3 设备名"]
    A2["/proc/stat"] --> B2["refresh_cpu<br/>两次采样求差值"]
    A3["/sys/class/net/*/statistics"] --> B3["sample<br/>两次采样求速率"]
    A4["ip neigh + dhcp.leases"] --> B4["get_connected_devices<br/>合并去重"]
    A5["/sys/class/net/*/carrier"] --> B5["get_net_ports<br/>网口状态"]
    A6["/proc/meminfo + /proc/mounts"] --> B6["get_mem_usage<br/>get_disk_usage"]
    A7["/proc/loadavg + /proc/uptime"] --> B7["get_loadavg<br/>get_uptime"]
    A8["/proc/[pid]/stat"] --> B8["get_process_counts<br/>进程 / 线程"]

    B1 --> C["Monitor 状态缓存<br/>sys_* / net_data / hist_* / devices"]
    B2 --> C
    B3 --> C
    B4 --> C
    B5 --> C
    B6 --> C
    B7 --> C
    B8 --> C

    C --> D1["Pillow 渲染<br/>文字 / 矩形 / 折线"]
    D1 --> D2["RGBA → BGRA<br/>通道重组"]
    D2 --> D3["mmap 写入<br/>/dev/fb0"]
    D3 --> D4["HDMI 屏幕"]

整条链路的关键设计决策是:放弃终端,直接控制像素。代价是需要自己处理字体渲染、布局排版、颜色格式转换;收益是完全自由的控制权——任意字体、任意字号、任意布局,不受内核编译配置的限制。

上一篇 上海电信SDN猫自行更换XG-140G-TF光猫
下一篇 已经是最后一篇了

暂无评论

Ctrl + Enter 发送

还没有评论,来说点什么吧

© 2015~2026 BrightSu
沪ICP备2020036506号-1
Theme Robes