# 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=y` and all BT lines (saves ~300KB flash + significant RAM) - `CONFIG_BT_BLE_*` lines Keep: - `CONFIG_IDF_TARGET="esp32s3"` - `CONFIG_ESPTOOLPY_FLASHMODE_QIO=y` - `CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y` - `CONFIG_PARTITION_TABLE_CUSTOM=y` - `CONFIG_SPIRAM=y` / `CONFIG_SPIRAM_MODE_OCT=y` / `CONFIG_SPIRAM_SPEED_80M=y` - `CONFIG_FREERTOS_HZ=1000` - `CONFIG_LV_MEM_SIZE_KILOBYTES=64` - `CONFIG_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) 1. `wifi_sta_init()` — start WiFi STA, wait for IP 2. `RlcdPort.RLCD_Init()` — init display over SPI 3. `Lvgl_PortInit(400, 300, flush_cb)` — init LVGL with double-buffered PSRAM 4. Create initial dashboard UI (under LVGL lock) 5. `ws_client_start("ws://:")` — connect WebSocket, register message handler 6. 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: ```json { "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 1. **WiFi credentials** — hardcode for now, or Kconfig menuconfig, or NVS provisioning? 2. **Dashboard layout** — what stats matter most? Single screen or multiple pages? 3. **Update frequency** — how often should the Pi push? 2s? 5s? On-change? 4. **Pi server location** — fixed IP or mDNS discovery? 5. **Error states** — what to show when WiFi drops or Pi goes offline?