3.9 KiB
Device Screens Management
Authored by Antigravity Date: 2026-03-15
1. What (Goal)
The goal is to enable the Calendink Provider to act as a backend screen generator for dump clients (like e-ink devices) connected to the network.
We need to implement a system that:
- Allows devices to register themselves via their MAC address.
- Provides a Frontend "Device Manager" interface where a user can upload and assign a custom LVGL XML string to each registered device.
- Generates a custom PNG image on the fly when a device requests its screen, by parsing its assigned XML string using the LVGL XML runtime.
- Temporarily stores this data in RAM (as per the current project architecture), deferring persistent storage (like SQLite) to a later phase.
2. Why (Reasoning)
Dumb e-ink clients (like the TRMNL) typically cannot run complex UI frameworks or parse rich data formats like JSON to render screens themselves. They simply download and display a static image buffer.
By having the ESP32-S3 Provider generate these images using LVGL's headless rendering capabilities:
- Centralized Configuration: The user can design and assign screens for all their devices from a single web dashboard.
- Dynamic UI: Using the new
LV_USE_XMLfeature in LVGL 9.4+, the layout is completely decoupled from the C++ firmware. Users can radically change what a display looks like by simply uploading a new XML string via the web interface, without needing to recompile or flash the ESP32. - Payload Efficiency: Returning a URL
{"image_url": "/api/devices/screen.png"}in the JSON response instead of a base64 encoded binary prevents massive memory spikes and reduces transmission time for the constrained devices. - Consistency: Storing user settings in BSS static arrays aligns with the existing non-persistent data models (like Tasks). It avoids heap fragmentation risks on the ESP32 until a proper SQLite database is integrated.
3. How (Implementation Details)
Backend Storage & State
Similar to the Todo app from tdd/todo_list.md, we use static arrays in the BSS segment to manage devices. The structure holds the device MAC, an active flag, and a statically allocated string buffer (2048 bytes) to store the uploaded LVGL XML.
struct Device {
char mac[18];
bool active;
char xml_layout[2048];
};
extern Device g_Devices[8];
API Endpoints
The following REST endpoints handle the device lifecycle and image generation:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/devices |
Returns a JSON array of all active devices, including whether they have a custom XML layout set. |
POST |
/api/devices/register |
Accepts {"mac": "..."}. Claims a slot in g_Devices if not already registered. |
POST |
/api/devices/layout |
Accepts {"mac": "...", "xml": "<lvgl xml>"}. Stores the XML string in the device's struct buffer. |
GET |
/api/devices/screen?mac=XX |
Returns {"image_url": "/api/devices/screen.png?mac=XX"} (TRMNL API pattern). |
GET |
/api/devices/screen.png?mac=XX |
Core rendering endpoint. Claims g_LvglMutex, clears the screen, parses the xml_layout buffer using lv_xml_create(), forces a refresh lv_refr_now(), encodes the buffer to PNG using lodepng, and streams the response. |
Subsystems Config
The ESP-IDF project configuration (sdkconfig.defaults) must be modified to enable the CONFIG_LV_USE_XML=y flag, which compiles the LVGL XML parser component into the firmware image.
Frontend
- DeviceManager.svelte: A new component accessible from the Sidebar. It fetches the device list on load.
- XML Uploading: For each device card, a text area allows the user to paste an LVGL XML string. Clicking "Save Layout" updates the device via
POST /api/devices/layout. - Integration: The
App.svelterouter will be updated to include the'devices'view state alongside Dashboard, Tasks, and Users.