HomeLab 跑了一堆服务:OpenClaw 助手、Pi-hole 广告拦截、Uptime Kuma 监控……但每次想看系统状态,得掏出手机打开一堆管理页面。有没有办法把这些信息集中到一个屏幕上,扫一眼就了然?
答案是:一块 40 块钱的 ESP32 开发板 + 一块 2.8 寸 LCD 屏幕 + 树莓派上的 REST API,总成本不到 100 块,做出来的东西比想象中实用得多。
整个系统的设计思路很直接:
架构原则:数据计算放在树莓派,ESP32 只负责显示和交互。这样即使以后加更多监控指标,也不用动 ESP32 的代码。
用的是 ESP32-2432S024R(又称 CYD - Cheap Yellow Display),这块板子大概 40 块人民币,板载 2.8 寸 320×240 IPS 屏幕和触控,使用 SPI 接口。性价比极高,缺点是文档比较碎片化,不同批次的屏幕驱动芯片可能不同。
这块屏幕用的是 ST7789 驱动芯片(不是更常见的 ILI9341),需要用专门的驱动库。颜色排序是 BGR 而不是 RGB,这些细节如果不注意屏幕会显示奇怪的偏色。
屏幕使用 VSPI 总线,触屏使用独立的 XPT2046 芯片(另占一组 SPI 引脚)。关键引脚如下(具体 GPIO 编号根据实际接线略有差异):
使用 PlatformIO 管理 ESP32 项目,主要依赖两个库:
TFT_eSPI:驱动 ST7789 屏幕,支持自定义引脚配置XPT2046_Touchscreen:驱动触屏芯片项目的核心配置文件 platformio.ini 中需要指定正确的驱动参数:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = bodmer/TFT_eSPI@^2.5.43
build_flags =
-DST7789_DRIVER=1
-DTFT_RGB_ORDER=TFT_BGR
-DTFT_WIDTH=240
-DTFT_HEIGHT=320
-DTFT_RST=12
-DSPI_FREQUENCY=55000000
ESP32 连接 WiFi 后,通过 NTP 服务器同步时间。连接时使用静态 IP 分配,指定一个同网段的固定地址,避免 DHCP 每次拿到的 IP 不一致导致通信失败。
WiFi.begin("SSID", "PASSWORD");
WiFi.config(
IPAddress(192, 168, 51, 240),
IPAddress(192, 168, 51, 1),
IPAddress(255, 255, 255, 0)
);
configTime(8 * 3600, 0, "pool.ntp.org");
深色系主题,参考工业仪表盘的风格:
ST7789 的颜色显示有一定的绿色偏移(绿色通道偏亮),在选色时需要刻意降低绿色的比重,否则看起来会偏青。
屏幕 320×240 像素,横向布局:
ESP32 的 HTTP 客户端对 TLS 和复杂路由支持有限,且不适合做数据聚合。把数据获取逻辑放在树莓派上,ESP32 只需要定期 GET 一个 JSON,复杂度降低很多。
树莓派上用 Python Flask 跑一个极简 API,监听在 8099 端口:
@app.route("/api/dashboard")
def dashboard():
return jsonify({
"time": get_local_time(),
"uptime": get_uptime(),
"system": {
"cpu": get_cpu_usage(),
"ram": get_ram_usage(),
"temp": get_cpu_temp()
},
"devices": {
"pi5": True,
"nas": check_nas(),
"jellyfin": check_jellyfin()
},
"services": {
"openclaw": check_service("openclaw-gateway"),
"hermes": check_service("hermes-agent"),
"pihole": check_service("pihole")
},
"controls": {
"light": state["light"],
"fan": state["fan"]
}
})
触摸开关灯或风扇时,ESP32 发送 POST 请求:
POST /api/control/light
Body: {"action": "toggle"} # 或 "on" / "off"
树莓派把控制状态写入本地文件,持久化保存,重启后不丢失。
开局屏幕一片白,代码确认没问题但就是没有显示。最后发现这块板子用的是 ST7789 驱动,不是更常见的 ILI9341。换对驱动后立刻正常。
ESP32 的 WiFi 模块只支持 2.4GHz,而我家路由器有两个 SSID:一个带 _5G 后缀的 5GHz 网络,一个普通 2.4GHz 网络。连错了 SSID 就会一直显示连接失败。
WiFi 连接成功,HTTP 请求却一直超时。用 Python 模拟 TCP 连接发现:ESP32 发送的 SYN 能到达树莓派,但树莓派的 SYN-ACK 永远回不来——路由器开启了客户端隔离(Client Isolation),阻止 WiFi 客户端之间直接通信。
解决方案:关闭路由器 AP 隔离,或者把 ESP32 和树莓派放在同一网络平面。实测关掉隔离后一切正常。
在电脑上配的颜色,部署到真实屏幕上发现偏绿。这是 ST7789 驱动特有的颜色映射问题,需要在选色时主动降低绿色通道的比重。例如原本的深蓝色 (24, 4, 32) 实际显示效果不错,但高饱和度的颜色偏移明显。
ESP32 内置 RTC 精度有限,长时间会漂移。改用从树莓派 API 获取基准时间,本地每秒递增,30 秒同步一次。这样即使 WiFi 偶尔抖动,时钟也依然流畅不卡顿。
┌─────────────────┐ WiFi (2.4GHz) ┌─────────────────┐
│ ESP32 + LCD │ ◄──── HTTP GET ----─�► │ 树莓派 (Pi5) │
│ Dashboard UI │ 192.168.51.x │ Flask API │
│ Touch Input │ │ 端口: 8099 │
└─────────────────┘ └────────┬────────┘
│
CPU/RAM/温度/服务
systemd 服务状态
控制状态持久化
第一版做完了基本功能,接下来还有几个想加的东西:
整个项目从头到尾花了两天晚上,大部分时间花在网络调试上,屏幕和 UI 其实相对顺利。这个方案的性价比很高:硬件成本极低,树莓派提供数据,ESP32 只管显示,职责分离得清晰。
对于有 HomeLab 的朋友,这块 40 块的屏幕放在桌上或者服务器旁边,随时看到系统状态,比打开手机 App 方便多了。