Files
pi-dashboard/PLAN.md
Mikkeli Matlock 19db125619 initial commit
2026-02-15 02:48:59 +09:00

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=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://<pi_ip>:<port>") — 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:

{
  "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?