Firmware for ESP32-C3/S3 that connects via USB CDC to the hl-hostmon host script,
polls JSON at fixed intervals, and renders a compact dashboard on a 1.54″ e-ink
(SSD1681, 200×200) using GxEPD2. It supports touch/auto page modes, Dallas (case)
temperature, optional Wi-Fi/OTA, and leaves room for a future PC fan PWM controller.
Current firmware: see
FW_VERSION(set inplatformio.ini→build_flags)
- USB CDC client: sends
INFOonce, thenGETat a configurable interval - Robust JSON framing (string/escape-aware) + ArduinoJson parsing
- Consistent UI with centered headers and right-aligned values
- Pages: Overview · Network · VMs/LXCs · Disks · Debug
- Touch / Auto page rotation, configurable poll interval
- Dallas 1-Wire temperature (“Case”)
- Wi-Fi / OTA optional (ArduinoOTA from Arduino IDE)
- Future: PC fan PWM on GPIO9 with tacho on GPIO5 (stubbed)
-
MCU: ESP32-C3 (native USB) or ESP32-S3 (works similarly)
-
Display: 1.54″ e-ink, SSD1681, 200×200
GxEPD2 model:GxEPD2_154_D67 -
Display wiring (as used in code):
Signal ESP32 pin CS 7 DC 1 RST 2 BUSY 3 SPI uses the board defaults (no explicit remap needed).
-
Touch: GPIO10 (active-low button or touch input)
-
Dallas: 1-Wire pin configurable (see
config.h) -
Fan (planned): PWM on GPIO9, Tacho on GPIO5
This project is organized for PlatformIO. Example environment for ESP32-C3 USB:
; platformio.ini (excerpt)
[env:esp32c3-usb]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
build_flags =
-D FW_VERSION="0.1.4"
-D POLL_INTERVAL_MS=2000 ; request interval (ms)
-D LINK_TIMEOUT_S=30 ; "Online/Timeout" threshold on Overview (s)
-D RX_LINEBUF_BYTES=8192 ; serial framing buffer (one JSON object)
-D JSON_DOC_CAP=9216 ; ArduinoJson document capacity
; -D USE_WIFI=1 ; optional Wi-Fi
; -D USE_OTA=1 ; optional OTA (Arduino IDE)
lib_deps =
bblanchon/ArduinoJson
ZinggJM/GxEPD2
adafruit/Adafruit GFX Library
milesburton/DallasTemperatureBuild / Upload
- USB upload (default):
PlatformIO: Upload(envesp32c3-usb) - OTA (optional): enable
USE_WIFIandUSE_OTA, set credentials inconfig.h, then use Arduino IDE → Network Ports to upload.
Most knobs are compile-time flags (see build_flags) with config.h fallbacks:
POLL_INTERVAL_MS— how often to sendGETto the hostLINK_TIMEOUT_S— Overview shows Online/Timeout based on last JSON ageRX_LINEBUF_BYTES— serial framing buffer (increase for larger payloads)JSON_DOC_CAP— ArduinoJson capacity (payload-dependent)USE_WIFI,USE_OTA— enable optional Wi-Fi/OTA
Runtime toggles live on the Debug page (e.g., debug flag) or via touch/auto mode.
All pages share a centered, bold header with an underline and right-aligned values.
- IP (primary IPv4)
- CPU — percent if available; fallback
L1 <load1>whencpu.percentisnull - RAM —
used/total GiB(binary GiB) - Case — local Dallas temperature
- Uptime —
Xd Yh - Link —
Online (Xs)orTimeout (Xs)based on last JSON age
- IP
- Status (e.g.,
ok) - Down / Up — rates from
nettotals (kbps/Mbps), split across two lines
- VMs:
running/total - LXCs:
running/total - Short lists (up to 2 each):
- nameon the left, status icon on the right
(filled square = running, outline = stopped). Names are ellipsis-truncated to fit.
- nameon the left- Temp right-aligned (10 px left of the icon),
-if unknown - Activity icon on the far right (filled square = active, outline = idle)
- FW version
- RX age
- OK/ERR
- JSON len (bytes)
- Poll interval (s)
- Mode (TOUCH/AUTO)
- Debug flag
The device writes:
INFOonce on bootGETeveryPOLL_INTERVAL_MS
The host replies with a single JSON object per line (or with newlines—we frame by braces).
Fields used (examples, not exhaustive):
{
"uptime_s": 181284,
"hostname": "bkg-LPS",
"cpu": { "percent": null, "load1": 0.49, "load5": 0.17, "load15": 0.11 },
"ram": { "total_bytes": 16513433600, "used_bytes": 3926523904 },
"filesystems": [{ "mount": "/", "total_bytes": 100861726720, "used_bytes": 14402846720 }],
"proxmox": {
"vm_running": 1, "vm_total": 4,
"lxc_running": 0, "lxc_total": 0,
"vms": [{ "id": 100, "name": "UbuntuLTS24.04", "status": "running" }],
"lxcs": [{ "id": 201, "name": "dns", "status": "stopped" }]
},
"disks": [{ "name": "nvme0n1", "state": "active", "temp_C": null }],
"ip": {
"primary_ifname": "vmbr2",
"primary_ipv4": "192.168.100.103",
"gateway_ipv4": "192.168.100.1",
"ip_status": "ok"
},
"net": {
"window_s": 1,
"total_rx_bps": 83216, "total_tx_bps": 83240
}
}Notes:
cpu.percentmay benull; we fall back tocpu.load1.ram.*_bytesare parsed as 64-bit and shown as GiB.disks[*].state:"active"/"active/idle"→ active icon; tempnull→ “-”.net.total_rx_bps/total_tx_bpspreferred; we also accept*_Bpsand per-interface fallback.
- Structure: modular
src/pages/*andsrc/modules/*(Wi-Fi, Dallas, etc.) - UI helpers:
ui_theme.hprovidesheader(...),content_top(), andui::printRight(...) - No serial spam: Serial is the host link; all debug info goes to the Debug page
- Extending pages: Prefer right-aligned values; measure and fit text to avoid collisions
- CPU shows “-”: host sent
"cpu":{"percent":null}and no loads; addload1or compute percent on host. - No Disks: ensure
disksarray present; temp may benull(we show “-”). - Time-outs: Overview
LinkusesLINK_TIMEOUT_S; bump if host polls slower. - JSON too large: check Debug → JSON len vs
RX_LINEBUF_BYTES. Increase buffers if needed.
We use semver-ish tags (vX.Y.Z). See CHANGELOG.md for details.
This project is licensed under GPL-3.0-or-later. See LICENSE and
THIRD_PARTY_LICENSES.md.
Libraries:
- GxEPD2 (GPL-3.0), Adafruit GFX (BSD), ArduinoJson (MIT), DallasTemperature (MIT).
- ZinggJM for GxEPD2
- Adafruit for GFX
- Benoît Blanchon for ArduinoJson
- Miles Burton et al. for DallasTemperature