Fixed code to use <screen> tags as base for the xml. Confirmed layout works now.

This commit is contained in:
2026-03-15 15:08:23 -04:00
parent ebb0ccecf4
commit f64860125c
5 changed files with 49 additions and 53 deletions

View File

@@ -10,7 +10,7 @@
let savingMac = $state(''); let savingMac = $state('');
let saveResult = $state(''); let saveResult = $state('');
const DEFAULT_XML = `<lv_obj width="100%" height="100%" flex_flow="column" align="center" style_pad_row="10">\n <lv_label text="Hello World" />\n <lv_label bind_text="device_mac" />\n</lv_obj>`; let defaultXml = $state('');
// Debug states // Debug states
let debugRegistering = $state(false); let debugRegistering = $state(false);
@@ -19,7 +19,9 @@
loading = true; loading = true;
error = ''; error = '';
try { try {
devices = await getDevices(); const data = await getDevices();
devices = data.devices;
defaultXml = data.default_layout;
} catch (e) { } catch (e) {
error = e.message || 'Failed to load devices'; error = e.message || 'Failed to load devices';
} finally { } finally {
@@ -107,7 +109,7 @@
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{#if device.has_layout} {#if device.has_layout}
{#if device.xml_layout === DEFAULT_XML} {#if device.xml_layout === defaultXml}
<span class="text-[10px] bg-accent/20 text-accent px-2 py-0.5 rounded-full uppercase tracking-wider font-semibold"> <span class="text-[10px] bg-accent/20 text-accent px-2 py-0.5 rounded-full uppercase tracking-wider font-semibold">
Default Layout Default Layout
</span> </span>
@@ -133,9 +135,9 @@
<textarea <textarea
id="xml-{device.mac}" id="xml-{device.mac}"
class="w-full h-32 bg-bg-primary border border-border rounded-lg p-3 text-xs font-mono text-text-primary resize-y focus:border-accent focus:outline-none transition-colors placeholder:text-text-secondary/50" class="w-full h-32 bg-bg-primary border border-border rounded-lg p-3 text-xs font-mono text-text-primary resize-y focus:border-accent focus:outline-none transition-colors placeholder:text-text-secondary/50"
placeholder={DEFAULT_XML} placeholder={defaultXml}
value={editingXml[device.mac] ?? device.xml_layout} value={editingXml[device.mac] ?? device.xml_layout}
oninput={(e) => editingXml[device.mac] = e.target.value} oninput={(e) => editingXml[device.mac] = e.currentTarget.value}
></textarea> ></textarea>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">

View File

@@ -278,8 +278,8 @@ export async function deleteTask(id) {
// ─── Device Management ─────────────────────────────────────────────────────── // ─── Device Management ───────────────────────────────────────────────────────
/** /**
* Fetch all registered devices. * Fetch all registered devices and the default layout.
* @returns {Promise<Array<{mac: string, has_layout: boolean}>>} * @returns {Promise<{default_layout: string, devices: Array<{mac: string, has_layout: boolean, xml_layout: string}>}>}
*/ */
export async function getDevices() { export async function getDevices() {
const res = await trackedFetch(`${API_BASE}/api/devices`); const res = await trackedFetch(`${API_BASE}/api/devices`);

View File

@@ -11,7 +11,11 @@ internal esp_err_t api_devices_get_handler(httpd_req_t *req)
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "default_layout", kDefaultLayoutXml);
cJSON *arr = cJSON_CreateArray(); cJSON *arr = cJSON_CreateArray();
cJSON_AddItemToObject(root, "devices", arr);
for (int i = 0; i < MAX_DEVICES; i++) for (int i = 0; i < MAX_DEVICES; i++)
{ {
@@ -25,11 +29,11 @@ internal esp_err_t api_devices_get_handler(httpd_req_t *req)
} }
} }
const char *json = cJSON_PrintUnformatted(arr); const char *json = cJSON_PrintUnformatted(root);
httpd_resp_sendstr(req, json); httpd_resp_sendstr(req, json);
free((void *)json); free((void *)json);
cJSON_Delete(arr); cJSON_Delete(root);
return ESP_OK; return ESP_OK;
} }

View File

@@ -89,21 +89,32 @@ internal esp_err_t api_devices_screen_image_handler(httpd_req_t *req)
bool render_success = false; bool render_success = false;
// 1. Wrap the payload in a <component><view> if it's missing (LVGL 9.4 // 1. Prepare the XML payload
// requirement for register_component_from_data) const char *xml_to_register = NULL;
char xml_buffer[2500]; static char xml_buffer[DEVICE_XML_MAX + 100]; // static buffer to avoid stack overflow
const char *xml_to_register = (const char *)dev->xml_layout;
if (strstr(xml_to_register, "<view") == NULL && if (dev->xml_layout[0] == '\0')
strstr(xml_to_register, "<screen") == NULL)
{ {
ESP_LOGI(kTagDeviceScreenImage, "Device %s has no layout xml.", mac);
return ESP_FAIL;
}
if (strstr(dev->xml_layout, "<screen") != NULL)
{
// The user provided a correct <screen> wrapped XML
xml_to_register = dev->xml_layout;
ESP_LOGI(kTagDeviceScreenImage,
"XML already contains <screen>, passing directly to parser.");
}
else
{
// Backwards compatibility for early setups - wrap it in screen and view
snprintf(xml_buffer, sizeof(xml_buffer), snprintf(xml_buffer, sizeof(xml_buffer),
"<component>\n<view width=\"100%%\" " "<screen>\n<view name=\"current_device\" width=\"100%%\" height=\"100%%\">\n%s\n</view>\n</screen>",
"height=\"100%%\">\n%s\n</view>\n</component>",
dev->xml_layout); dev->xml_layout);
xml_to_register = xml_buffer; xml_to_register = xml_buffer;
ESP_LOGI(kTagDeviceScreenImage, ESP_LOGI(kTagDeviceScreenImage,
"Wrapped widget XML in component/view for parsing."); "Legacy XML without <screen> detected. Wrapped automatically.");
} }
// 2. Register the XML payload as a component // 2. Register the XML payload as a component
@@ -115,18 +126,12 @@ internal esp_err_t api_devices_screen_image_handler(httpd_req_t *req)
ESP_LOGI(kTagDeviceScreenImage, "Successfully registered XML for device %s", ESP_LOGI(kTagDeviceScreenImage, "Successfully registered XML for device %s",
mac); mac);
// 3. Determine if this XML describes a full <screen> or just a <component> // 3. Since we enforce <screen> now, we always create a screen instance
// layout Simple heuristic: check if the string contains "<screen"
if (strstr(xml_to_register, "<screen") != NULL)
{
ESP_LOGI(kTagDeviceScreenImage,
"XML contains <screen>, creating screen instance");
lv_obj_t *new_scr = lv_xml_create_screen("current_device"); lv_obj_t *new_scr = lv_xml_create_screen("current_device");
if (new_scr) if (new_scr)
{ {
// We must load this newly created screen to make it active before // We must load this newly created screen to make it active before rendering
// rendering
lv_screen_load(new_scr); lv_screen_load(new_scr);
scr = new_scr; // Update local pointer since active screen changed scr = new_scr; // Update local pointer since active screen changed
render_success = true; render_success = true;
@@ -138,23 +143,6 @@ internal esp_err_t api_devices_screen_image_handler(httpd_req_t *req)
} }
} }
else else
{
ESP_LOGI(kTagDeviceScreenImage,
"XML is a component/widget, creating on active screen");
// Create the component directly on the currently active cleaned screen
lv_obj_t *comp = (lv_obj_t *)lv_xml_create(scr, "current_device", NULL);
if (comp)
{
render_success = true;
}
else
{
ESP_LOGE(kTagDeviceScreenImage, "lv_xml_create failed for device %s",
mac);
}
}
}
else
{ {
ESP_LOGE(kTagDeviceScreenImage, ESP_LOGE(kTagDeviceScreenImage,
"lv_xml_register_component_from_data failed for device %s", mac); "lv_xml_register_component_from_data failed for device %s", mac);

View File

@@ -8,10 +8,12 @@ constexpr int MAX_DEVICES = 8;
constexpr int DEVICE_XML_MAX = 2048; constexpr int DEVICE_XML_MAX = 2048;
constexpr char kDefaultLayoutXml[] = constexpr char kDefaultLayoutXml[] =
"<lv_obj width=\"800\" height=\"480\" flex_flow=\"column\" flex_main_place=\"center\" flex_cross_place=\"center\" style_pad_row=\"10\">\n" "<screen>\n"
" <view width=\"100%\" height=\"100%\" layout=\"flex\" flex_flow=\"column\" style_flex_main_place=\"center\" style_flex_cross_place=\"center\" style_pad_row=\"10\">\n"
" <lv_label text=\"Hello World\" />\n" " <lv_label text=\"Hello World\" />\n"
" <lv_label bind_text=\"device_mac\" />\n" " <lv_label bind_text=\"device_mac\" />\n"
"</lv_obj>"; " </view>\n"
"</screen>";
struct device_t struct device_t
{ {