#include #include #include "esp_log.h" #include "lvgl.h" #include "lvgl_port.h" #include "ui_manager.h" static const char *TAG = "ui_manager"; #define UI_LOCK_TIMEOUT_MS 1000 static lv_obj_t *temp_label = NULL; static lv_obj_t *cond_label = NULL; static lv_obj_t *status_label = NULL; static lv_obj_t *touch_label = NULL; /* * lv_label_set_text() invalidates the label unconditionally, even when the * new text is identical -- which would repaint the temperature every poll * whether or not it moved. Comparing first means an unchanged value costs * nothing: no invalidation, so no redraw and no flush. */ static void label_set_if_changed(lv_obj_t *label, const char *text) { if (label == NULL || text == NULL) { return; } const char *current = lv_label_get_text(label); if (current != NULL && strcmp(current, text) == 0) { return; } lv_label_set_text(label, text); } /* * Live readout of the last contact. * * Polled from an LVGL timer rather than driven by a widget event. An event * handler on the screen object only fires for taps that actually reach the * screen: LVGL does not bubble events to parents by default, so anything * landing on the header or the temperature card is consumed by that child and * never seen here. That made whole regions of the panel look like dead touch * zones when the hardware was reporting them perfectly well. */ static void touch_readout_timer_cb(lv_timer_t *timer) { lv_indev_t *indev = lv_indev_get_next(NULL); if (indev == NULL || touch_label == NULL) { return; } lv_point_t p; lv_indev_get_point(indev, &p); char buf[48]; if (lv_indev_get_state(indev) == LV_INDEV_STATE_PRESSED) { snprintf(buf, sizeof(buf), "touch %d, %d", (int)p.x, (int)p.y); } else { snprintf(buf, sizeof(buf), "touch %d, %d (released)", (int)p.x, (int)p.y); } label_set_if_changed(touch_label, buf); } void ui_update_weather(int16_t temp_f, const char *condition) { if (!lvgl_port_lock(UI_LOCK_TIMEOUT_MS)) { ESP_LOGW(TAG, "Dropped weather update: LVGL lock timed out"); return; } char buf[32]; snprintf(buf, sizeof(buf), "%d°F", temp_f); label_set_if_changed(temp_label, buf); label_set_if_changed(cond_label, condition); lvgl_port_unlock(); } void ui_set_status(const char *status) { if (status == NULL) { return; } if (!lvgl_port_lock(UI_LOCK_TIMEOUT_MS)) { ESP_LOGW(TAG, "Dropped status update: LVGL lock timed out"); return; } label_set_if_changed(status_label, status); lvgl_port_unlock(); } void ui_init(void) { /* ui_init() runs from app_main, not the LVGL task, so it needs the lock * like any other outside caller. */ if (!lvgl_port_lock(UI_LOCK_TIMEOUT_MS)) { ESP_LOGE(TAG, "Could not acquire LVGL lock for UI init"); return; } lv_obj_t *scr = lv_screen_active(); lv_obj_set_style_bg_color(scr, lv_color_hex(0x1A2332), LV_PART_MAIN); lv_obj_set_style_pad_all(scr, 0, LV_PART_MAIN); lv_obj_set_flex_flow(scr, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(scr, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_t *header = lv_obj_create(scr); lv_obj_set_size(header, LV_PCT(100), 64); lv_obj_set_style_radius(header, 0, LV_PART_MAIN); lv_obj_set_style_bg_color(header, lv_color_hex(0x0D1620), LV_PART_MAIN); lv_obj_set_style_border_width(header, 0, LV_PART_MAIN); lv_obj_clear_flag(header, LV_OBJ_FLAG_SCROLLABLE); lv_obj_t *title = lv_label_create(header); lv_label_set_text(title, "Thermostat"); lv_obj_set_style_text_color(title, lv_color_white(), LV_PART_MAIN); lv_obj_set_style_text_font(title, &lv_font_montserrat_28, LV_PART_MAIN); lv_obj_center(title); lv_obj_t *card = lv_obj_create(scr); lv_obj_set_size(card, LV_PCT(80), LV_SIZE_CONTENT); lv_obj_set_style_bg_color(card, lv_color_hex(0x2C3E50), LV_PART_MAIN); lv_obj_set_style_border_width(card, 2, LV_PART_MAIN); lv_obj_set_style_border_color(card, lv_color_hex(0x3498DB), LV_PART_MAIN); lv_obj_set_style_radius(card, 12, LV_PART_MAIN); lv_obj_set_style_pad_all(card, 24, LV_PART_MAIN); lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(card, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE); temp_label = lv_label_create(card); lv_label_set_text(temp_label, "--°F"); lv_obj_set_style_text_color(temp_label, lv_color_white(), LV_PART_MAIN); lv_obj_set_style_text_font(temp_label, &lv_font_montserrat_48, LV_PART_MAIN); cond_label = lv_label_create(card); lv_label_set_text(cond_label, "Waiting for data..."); lv_obj_set_style_text_color(cond_label, lv_color_hex(0xBDC3C7), LV_PART_MAIN); status_label = lv_label_create(scr); lv_label_set_text(status_label, "Starting..."); lv_obj_set_style_text_color(status_label, lv_color_hex(0x7F8C8D), LV_PART_MAIN); touch_label = lv_label_create(scr); lv_label_set_text(touch_label, "touch --, --"); lv_obj_set_style_text_color(touch_label, lv_color_hex(0x3498DB), LV_PART_MAIN); lv_timer_create(touch_readout_timer_cb, 100, NULL); lvgl_port_unlock(); ESP_LOGI(TAG, "UI initialized"); }