7.9 KiB
Pi Dashboard — ESP32-S3 RLCD 4.2" Project Plan
Goal
WebSocket client on the ESP32-S3 RLCD board that receives system status from a Raspberry Pi home server over LAN and renders a monitoring dashboard via LVGL.
New project location
Create a new ESP-IDF project directory outside the Examples folder, e.g.:
J:\dev\arduino\ESP32-S3-RLCD-4.2-main\Projects\pi_dashboard\
Files to copy from the factory example
Source root: Example\ESP-IDF\10_FactoryProgram\
Must copy (build skeleton)
| File / Dir | Why |
|---|---|
CMakeLists.txt |
Project root cmake — edit project name, strip EXTRA_COMPONENT_DIRS if not using ExternLib |
partitions.csv |
Partition table (8MB app partition, NVS, PHY) |
sdkconfig.defaults |
Base config — strip BT lines, keep SPIRAM/flash/LVGL settings |
main/CMakeLists.txt |
Main component registration |
main/idf_component.yml |
Managed component deps — keep lvgl/lvgl: ^8.4.0, drop avi_player and esp_new_jpeg, add espressif/esp_websocket_client |
Must copy (display driver)
| File | From | Why |
|---|---|---|
display_bsp.h |
components/port_bsp/ |
DisplayPort class — RLCD SPI driver with LUT pixel mapping |
display_bsp.cpp |
components/port_bsp/ |
Full implementation: SPI init, reset, command sequences, SetPixel, Display |
These two files are the entire RLCD hardware abstraction. No other display code
exists. Copy them into a components/display_bsp/ component in the new project.
Must copy (LVGL port)
| File | From | Why |
|---|---|---|
lvgl_bsp.h |
components/app_bsp/ |
Lvgl_PortInit, Lvgl_lock/unlock declarations |
lvgl_bsp.cpp |
components/app_bsp/ |
LVGL display driver registration, double-buffered PSRAM, tick timer, port task on Core 0 |
Copy into a components/lvgl_port/ component. This file is self-contained — its
only dependency is lvgl.h and FreeRTOS.
Copy as reference (WiFi STA)
| File | From | Why |
|---|---|---|
esp_wifi_bsp.h |
Example/ESP-IDF/02_WIFI_STA/components/esp_wifi_bsp/ |
Cleaner than the factory version — no scan-and-destroy, no BLE entanglement |
esp_wifi_bsp.c |
same | Simple STA init + connect + reconnect skeleton |
The 02_WIFI_STA example is a better starting point than the factory program's
wifi code. The factory version tears down WiFi for BLE scanning — useless for a
persistent connection. You will need to modify the STA code to:
- Make SSID/password configurable (NVS or Kconfig)
- Add auto-reconnect on disconnect
- Signal connection readiness via event group so the WebSocket task knows when to start
Do NOT copy
| Component | Why not |
|---|---|
components/port_bsp/ (everything except display_bsp) |
I2C bus, buttons, SD card, ADC, codec — none needed for a dashboard |
components/ExternLib/ (SensorLib, codec_board) |
Sensor drivers and audio codec — irrelevant |
components/app_bsp/ble_scan_bsp.c |
BLE scanning — not needed, and conflicts with persistent WiFi |
components/app_bsp/esp_wifi_bsp.c |
Factory version destroys WiFi for BLE — use 02_WIFI_STA instead |
components/ui_bsp/ |
NXP GUI Guider generated UI — you will design your own dashboard layout |
components/user_app/ |
Factory test logic — all replaced by your WebSocket + dashboard code |
managed_components/ |
Auto-downloaded by ESP-IDF component manager from idf_component.yml — never copy these |
build/ |
Build artifacts — never copy |
New project structure
pi_dashboard/
CMakeLists.txt # from factory, edited
partitions.csv # from factory, as-is
sdkconfig.defaults # from factory, stripped (no BT)
main/
CMakeLists.txt # register main.cpp
idf_component.yml # lvgl ^8.4.0, esp_websocket_client
main.cpp # app_main: wifi init, display init, lvgl init, ws connect, task spawn
components/
display_bsp/
CMakeLists.txt # new: register display_bsp.cpp, REQUIRES driver esp_lcd
display_bsp.h # from port_bsp, as-is
display_bsp.cpp # from port_bsp, as-is
lvgl_port/
CMakeLists.txt # new: register lvgl_bsp.cpp, REQUIRES lvgl__lvgl esp_timer
lvgl_bsp.h # from app_bsp, as-is
lvgl_bsp.cpp # from app_bsp, as-is
wifi_sta/
CMakeLists.txt # new: register wifi_sta.c, REQUIRES esp_wifi esp_event nvs_flash
wifi_sta.h # based on 02_WIFI_STA, extended with reconnect + event group
wifi_sta.c # based on 02_WIFI_STA, extended
ws_client/
CMakeLists.txt # new: register ws_client.cpp, REQUIRES esp_websocket_client
ws_client.h # WebSocket connect/disconnect, message callback registration
ws_client.cpp # esp_websocket_client wrapper, reconnect logic, JSON parse
dashboard_ui/
CMakeLists.txt # new: register dashboard_ui.cpp, REQUIRES lvgl__lvgl
dashboard_ui.h # UI layout: create/update functions for dashboard widgets
dashboard_ui.cpp # LVGL widget creation, label/bar/table updates from parsed data
sdkconfig.defaults (modified for this project)
Strip from the factory version:
CONFIG_BT_ENABLED=yand all BT lines (saves ~300KB flash + significant RAM)CONFIG_BT_BLE_*lines
Keep:
CONFIG_IDF_TARGET="esp32s3"CONFIG_ESPTOOLPY_FLASHMODE_QIO=yCONFIG_ESPTOOLPY_FLASHSIZE_16MB=yCONFIG_PARTITION_TABLE_CUSTOM=yCONFIG_SPIRAM=y/CONFIG_SPIRAM_MODE_OCT=y/CONFIG_SPIRAM_SPEED_80M=yCONFIG_FREERTOS_HZ=1000CONFIG_LV_MEM_SIZE_KILOBYTES=64CONFIG_LV_DISP_DEF_REFR_PERIOD=1
Add:
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
Runtime architecture
Core 0 Core 1
------ ------
WiFi driver (system) main task (app_main, exits after init)
LVGL port task (priority 5) WebSocket event task (priority 3)
- lv_timer_handler loop - receives WS messages
- holds/releases lvgl_mux - parses JSON
- acquires lvgl_mux
- updates dashboard widgets
- releases lvgl_mux
Boot sequence (app_main)
wifi_sta_init()— start WiFi STA, wait for IPRlcdPort.RLCD_Init()— init display over SPILvgl_PortInit(400, 300, flush_cb)— init LVGL with double-buffered PSRAM- Create initial dashboard UI (under LVGL lock)
ws_client_start("ws://<pi_ip>:<port>")— connect WebSocket, register message handler- app_main returns, FreeRTOS tasks take over
Data flow
Pi server --[WebSocket JSON]--> ESP32 ws_client
--> parse message
--> Lvgl_lock()
--> update lv_label / lv_bar / lv_table widgets
--> Lvgl_unlock()
--> LVGL port task flushes on next cycle
--> flush_cb converts 16-bit to 1-bit, pushes to RLCD
Pi side (not part of this ESP-IDF project, but for context)
A simple Python WebSocket server that:
- Collects system stats (psutil or /proc reads)
- Serializes to JSON
- Pushes to connected clients every N seconds
Example payload:
{
"cpu_pct": 23,
"mem_pct": 61,
"mem_used_mb": 1952,
"disk_pct": 44,
"cpu_temp": 52,
"uptime_hrs": 342,
"services": [
{"name": "docker", "status": "running"},
{"name": "pihole", "status": "running"},
{"name": "nginx", "status": "stopped"}
],
"net_rx_kbps": 1240,
"net_tx_kbps": 320
}
Key decisions still open
- WiFi credentials — hardcode for now, or Kconfig menuconfig, or NVS provisioning?
- Dashboard layout — what stats matter most? Single screen or multiple pages?
- Update frequency — how often should the Pi push? 2s? 5s? On-change?
- Pi server location — fixed IP or mDNS discovery?
- Error states — what to show when WiFi drops or Pi goes offline?