Second hand on the analog clock, sliders like Home Assistant's, one top bar on every card (0.2.43)

Firmware 0.2.37 for both boards:
- The analog clock card gets a thin red second hand that moves every second while the
  screen is awake (runtime_tiles::screen_awake, set by the profiles from display_dimmed);
  in standby it hides and the clock keeps its once-a-minute redraw.
- Card sliders follow HA's control slider: 12/42 and 8/42 corners, a 4 px handle an
  eighth of the height from the fill end, a stub of a third of the height at 0 or 1 %,
  the track in the fill colour at 20 %. An off light or fan shows only the empty grey
  track, also in the colour card's brightness row. A light's slider floors at 1 %.
- Every overlay card shares one top bar: a round back arrow at the left (60/40 px), the
  name centred, and only the climate power as a right-hand action. The "done"/"ok"
  buttons of the colour and vacuum cards are gone; the brightness card gets its arrow.
- The top bar reads "Connecting to Home Assistant..." / "Waiting for ESP Screens..."
  until the first layout, instead of "Choose tiles in HA".

Checked with the host renderer for both boards and on the Guition over USB.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-15 13:34:18 +02:00
parent 0ebb23a1f4
commit 5b50e70472
16 changed files with 419 additions and 449 deletions
+17 -10
View File
@@ -40,7 +40,7 @@ namespace light_controls {
inline State states[8]; inline State states[8];
inline State fallback; inline State fallback;
inline State *active = &fallback; inline State *active = &fallback;
struct Row { lv_obj_t *box{}, *slider{}, *value{}; bool dirty = false; unsigned index{}; }; struct Row { lv_obj_t *box{}, *slider{}, *value{}; bool dirty = false; unsigned index{}; bool off = false; };
inline Row rows[3]; inline Row rows[3];
inline std::function<void(int)> commits[3]; inline std::function<void(int)> commits[3];
inline bool ready = false; inline bool ready = false;
@@ -81,15 +81,20 @@ inline void preview(Row &row) {
lv_label_set_text_fmt(row.value, "%d K", value); lv_label_set_text_fmt(row.value, "%d K", value);
lv_obj_set_style_bg_color(row.slider, kelvin_color(value, lv_slider_get_min_value(row.slider), lv_slider_get_max_value(row.slider)), LV_PART_KNOB); lv_obj_set_style_bg_color(row.slider, kelvin_color(value, lv_slider_get_min_value(row.slider), lv_slider_get_max_value(row.slider)), LV_PART_KNOB);
} else { } else {
// The range reaches below 1 only to draw the round end that holds the handle at 1 %. // The range reaches below 1 only to draw the short stub that holds the handle at 1 %.
if (value < 1) { lv_slider_set_value(row.slider, 1, LV_ANIM_OFF); value = 1; } if (value < 1) { lv_slider_set_value(row.slider, 1, LV_ANIM_OFF); value = 1; }
lv_label_set_text_fmt(row.value, "%d %%", value); // An off light shows only the track, as in Home Assistant, until the slider moves.
lv_obj_set_style_bg_opa(row.slider, row.off ? LV_OPA_TRANSP : LV_OPA_COVER, LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(row.slider, row.off ? LV_OPA_TRANSP : LV_OPA_COVER, LV_PART_KNOB);
lv_obj_set_style_bg_color(row.slider, lv_color_hex(row.off ? 0xECECEC : 0xFDEFC3), LV_PART_MAIN);
if (row.off) lv_label_set_text(row.value, "Off");
else lv_label_set_text_fmt(row.value, "%d %%", value);
} }
} }
inline void event(lv_event_t *e) { inline void event(lv_event_t *e) {
auto &row = *static_cast<Row *>(lv_event_get_user_data(e)); auto &row = *static_cast<Row *>(lv_event_get_user_data(e));
auto code = lv_event_get_code(e); auto code = lv_event_get_code(e);
if (code == LV_EVENT_VALUE_CHANGED) { row.dirty = true; preview(row); } if (code == LV_EVENT_VALUE_CHANGED) { row.dirty = true; row.off = false; preview(row); }
if (code == LV_EVENT_PRESS_LOST) row.dirty = false; if (code == LV_EVENT_PRESS_LOST) row.dirty = false;
if (code == LV_EVENT_RELEASED && row.dirty) { if (code == LV_EVENT_RELEASED && row.dirty) {
row.dirty = false; row.dirty = false;
@@ -187,15 +192,16 @@ inline void setup(lv_obj_t *parent, const lv_font_t *font, int width, int height
lv_obj_set_style_outline_opa(row.slider, LV_OPA_20, LV_PART_KNOB); lv_obj_set_style_outline_opa(row.slider, LV_OPA_20, LV_PART_KNOB);
lv_slider_set_range(row.slider, i == 0 ? 0 : 2000, i == 0 ? 360 : 6500); lv_slider_set_range(row.slider, i == 0 ? 0 : 2000, i == 0 ? 360 : 6500);
} else { } else {
// Track and fill corners, handle and shortest fill as in Home Assistant's 42 px control slider.
lv_obj_set_pos(row.slider, track_x, track_y); lv_obj_set_size(row.slider, track_w, track_h); lv_obj_set_pos(row.slider, track_x, track_y); lv_obj_set_size(row.slider, track_w, track_h);
lv_obj_set_style_radius(row.slider, LV_RADIUS_CIRCLE, LV_PART_MAIN); lv_obj_set_style_radius(row.slider, track_h * 12 / 42, LV_PART_MAIN);
lv_obj_set_style_radius(row.slider, LV_RADIUS_CIRCLE, LV_PART_INDICATOR); lv_obj_set_style_radius(row.slider, track_h * 8 / 42, LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(row.slider, LV_OPA_COVER, LV_PART_MAIN); lv_obj_set_style_bg_opa(row.slider, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_bg_opa(row.slider, LV_OPA_COVER, LV_PART_INDICATOR); lv_obj_set_style_bg_opa(row.slider, LV_OPA_COVER, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(row.slider, lv_color_hex(0xFDEFC3), LV_PART_MAIN); lv_obj_set_style_bg_color(row.slider, lv_color_hex(0xFDEFC3), LV_PART_MAIN);
lv_obj_set_style_bg_color(row.slider, lv_color_hex(0xFFC107), LV_PART_INDICATOR); lv_obj_set_style_bg_color(row.slider, lv_color_hex(0xFFC107), LV_PART_INDICATOR);
// White handle inside the rounded end of the fill, as on the tiles (runtime_tiles::slider_handle). // White handle inside the end of the fill, as on the tiles (runtime_tiles::slider_handle).
int handle = large ? 6 : 4, back = track_h / 4 + handle / 2, half = track_h >> 1; int handle = large ? 4 : 3, back = std::max(1, track_h / 8) + handle / 2, half = track_h >> 1;
lv_obj_set_style_bg_opa(row.slider, LV_OPA_COVER, LV_PART_KNOB); lv_obj_set_style_bg_opa(row.slider, LV_OPA_COVER, LV_PART_KNOB);
lv_obj_set_style_bg_color(row.slider, lv_color_hex(0xFFFFFF), LV_PART_KNOB); lv_obj_set_style_bg_color(row.slider, lv_color_hex(0xFFFFFF), LV_PART_KNOB);
lv_obj_set_style_radius(row.slider, 2, LV_PART_KNOB); lv_obj_set_style_radius(row.slider, 2, LV_PART_KNOB);
@@ -203,8 +209,8 @@ inline void setup(lv_obj_t *parent, const lv_font_t *font, int width, int height
lv_obj_set_style_pad_right(row.slider, handle / 2 - back - (track_h - half), LV_PART_KNOB); lv_obj_set_style_pad_right(row.slider, handle / 2 - back - (track_h - half), LV_PART_KNOB);
lv_obj_set_style_pad_top(row.slider, -(track_h / 4), LV_PART_KNOB); lv_obj_set_style_pad_top(row.slider, -(track_h / 4), LV_PART_KNOB);
lv_obj_set_style_pad_bottom(row.slider, -(track_h / 4), LV_PART_KNOB); lv_obj_set_style_pad_bottom(row.slider, -(track_h / 4), LV_PART_KNOB);
// 1 % still shows one round end: the range starts that far below 1. // 1 % still shows a stub a third of the height wide: the range starts that far below 1.
int below = track_w > track_h ? 99 * track_h / (track_w - track_h) : 0; int stub = std::max(track_h / 3, 2 * std::max(1, track_h / 8) + handle), below = track_w > stub ? 99 * stub / (track_w - stub) : 0;
lv_slider_set_range(row.slider, 1 - below, 100); lv_slider_set_range(row.slider, 1 - below, 100);
} }
lv_obj_add_event_cb(row.slider, event, LV_EVENT_ALL, &row); lv_obj_add_event_cb(row.slider, event, LV_EVENT_ALL, &row);
@@ -235,6 +241,7 @@ inline void open(const std::string &entity, bool color, bool temperature, int br
if (i == 1 && !active->temperature_ready()) lv_obj_add_state(row.slider, LV_STATE_DISABLED); if (i == 1 && !active->temperature_ready()) lv_obj_add_state(row.slider, LV_STATE_DISABLED);
if (i == 1 && active->temperature_ready()) lv_slider_set_range(row.slider, active->minimum, active->maximum); if (i == 1 && active->temperature_ready()) lv_slider_set_range(row.slider, active->minimum, active->maximum);
lv_slider_set_value(row.slider, i == 0 ? active->hue : i == 1 ? active->kelvin : clamp(brightness, 1, 100), LV_ANIM_OFF); lv_slider_set_value(row.slider, i == 0 ? active->hue : i == 1 ? active->kelvin : clamp(brightness, 1, 100), LV_ANIM_OFF);
row.off = i == 2 && brightness <= 0;
preview(row); row.dirty = false; preview(row); row.dirty = false;
if (i == 1 && !active->temperature_ready()) lv_label_set_text(row.value, "Waiting..."); if (i == 1 && !active->temperature_ready()) lv_label_set_text(row.value, "Waiting...");
} }
+61 -19
View File
@@ -42,6 +42,10 @@ inline lv_obj_t *time_label = nullptr;
inline const lv_font_t *header_text_font = nullptr, *header_icon_font = nullptr; inline const lv_font_t *header_text_font = nullptr, *header_icon_font = nullptr;
inline void render_header(); inline void render_header();
inline std::function<esphome::ESPTime()> now_time; inline std::function<esphome::ESPTime()> now_time;
// True while the screen is in use: the profile reports standby (also the night level) as not awake.
// The analog clock's second hand runs only then; a standby screen stays on its minute redraws.
inline std::function<bool()> screen_awake;
inline bool awake() { return !screen_awake || screen_awake(); }
inline uint32_t now_epoch() { if (!now_time) return 0; auto t = now_time(); return t.is_valid() ? static_cast<uint32_t>(t.timestamp) : 0; } inline uint32_t now_epoch() { if (!now_time) return 0; auto t = now_time(); return t.is_valid() ? static_cast<uint32_t>(t.timestamp) : 0; }
inline void tick(); inline void tick();
inline void refresh_tile(size_t index); inline void refresh_tile(size_t index);
@@ -101,6 +105,8 @@ struct Widgets {
int title_x=0,title_y=0,value_x=0,value_y=0; const lv_font_t *value_font{}, *icon_font{}; int title_x=0,title_y=0,value_x=0,value_y=0; const lv_font_t *value_font{}, *icon_font{};
// Wide cards span both columns; custom cards (clock, forecast, graph) draw into `extra`. // Wide cards span both columns; custom cards (clock, forecast, graph) draw into `extra`.
bool wide=false; int base_width=0; lv_obj_t *extra{}; std::string extra_mode; std::array<lv_obj_t *, 20> parts{}; lv_point_precise_t *points{}; bool wide=false; int base_width=0; lv_obj_t *extra{}; std::string extra_mode; std::array<lv_obj_t *, 20> parts{}; lv_point_precise_t *points{};
// Analog clock: centre and radius of the dial, so the second hand can move without a card redraw.
int hand_cx=0, hand_cy=0, hand_r=0, hand_width=1;
// Soft area under a polyline (graph, sun path), painted by the extra container's draw event. // Soft area under a polyline (graph, sun path), painted by the extra container's draw event.
const lv_point_precise_t *fill_points{}; unsigned fill_count=0; int fill_x=0, fill_y=0, fill_base=0; lv_color_t fill_color{}; lv_opa_t fill_opa=0; const lv_point_precise_t *fill_points{}; unsigned fill_count=0; int fill_x=0, fill_y=0, fill_base=0; lv_color_t fill_color{}; lv_opa_t fill_opa=0;
// Direct controls on a wide card: a panel at the right with pill keys, a -/+ pill, // Direct controls on a wide card: a panel at the right with pill keys, a -/+ pill,
@@ -447,7 +453,8 @@ inline void slider_event(lv_event_t *e);
inline void commit_slider(unsigned i,int raw){ inline void commit_slider(unsigned i,int raw){
if(i>=model.count || !fresh())return;auto &t=model.tiles[i];if(!t.available() || t.loading(esphome::millis()))return; if(i>=model.count || !fresh())return;auto &t=model.tiles[i];if(!t.available() || t.loading(esphome::millis()))return;
float value=std::clamp(raw,0,1000)/1000.0f;auto d=t.domain(); float value=std::clamp(raw,0,1000)/1000.0f;auto d=t.domain();
if(d=="light")action("light.turn_on",t.entity,"brightness",std::to_string((int)std::lround(value*255))); // A light's slider stops at 1 %, as in Home Assistant; tapping the card turns it off.
if(d=="light")action("light.turn_on",t.entity,"brightness",std::to_string(std::max(3,(int)std::lround(value*255))));
if(d=="fan")action("fan.set_percentage",t.entity,"percentage",std::to_string((int)std::lround(value*100))); if(d=="fan")action("fan.set_percentage",t.entity,"percentage",std::to_string((int)std::lround(value*100)));
if(d=="cover")action("cover.set_cover_position",t.entity,"position",std::to_string((int)std::lround(value*100))); if(d=="cover")action("cover.set_cover_position",t.entity,"position",std::to_string((int)std::lround(value*100)));
if(d=="media_player")action("media_player.volume_set",t.entity,"volume_level",std::to_string(value)); if(d=="media_player")action("media_player.volume_set",t.entity,"volume_level",std::to_string(value));
@@ -601,10 +608,15 @@ inline void show_detail(unsigned index){
lv_obj_set_style_bg_color(detail_root,lv_color_hex(0xE7E7E7),0);lv_obj_set_style_bg_opa(detail_root,LV_OPA_COVER,0); lv_obj_set_style_bg_color(detail_root,lv_color_hex(0xE7E7E7),0);lv_obj_set_style_bg_opa(detail_root,LV_OPA_COVER,0);
int width=lv_display_get_horizontal_resolution(lv_display_get_default()), height=lv_display_get_vertical_resolution(lv_display_get_default()); int width=lv_display_get_horizontal_resolution(lv_display_get_default()), height=lv_display_get_vertical_resolution(lv_display_get_default());
bool large=width>=480;int pad=large?20:10, top=large?100:62, gap=large?12:6,bh=large?58:34,cw=(width-pad*2-gap)/2; bool large=width>=480;int pad=large?20:10, top=large?100:62, gap=large?12:6,bh=large?58:34,cw=(width-pad*2-gap)/2;
auto *heading=detail_label(detail_root,t.name,pad,large?24:12,width-80);if(watch_font){lv_obj_set_style_text_font(heading,watch_font,0);lv_obj_set_height(heading,lv_font_get_line_height(watch_font));} // The same top bar as the board's own cards: a round back arrow at the left, the name centred.
detail_button("X",width-58,8,48,40,-1); int bar=large?60:40,bar_x=large?16:10,bar_y=large?16:8;
auto *back=detail_button("",bar_x,bar_y,bar,bar,-1);lv_obj_set_style_radius(back,LV_RADIUS_CIRCLE,0);lv_obj_set_style_bg_color(back,lv_color_hex(0xEEEEEE),0);
auto *arrow=lv_obj_get_child(back,0);if(mini_icon_font)lv_obj_set_style_text_font(arrow,mini_icon_font,0);lv_label_set_text(arrow,"\U000F004D");lv_obj_set_size(arrow,LV_SIZE_CONTENT,LV_SIZE_CONTENT);lv_obj_center(arrow);
const lv_font_t *title_font=watch_font?watch_font:detail_font;
auto *heading=detail_label(detail_root,t.name,bar_x+bar+8,bar_y+(bar-lv_font_get_line_height(title_font))/2,width-2*(bar_x+bar+8));
lv_obj_set_style_text_font(heading,title_font,0);lv_obj_set_height(heading,lv_font_get_line_height(title_font));lv_obj_set_style_text_align(heading,LV_TEXT_ALIGN_CENTER,0);
std::string state=detail_state(t); std::string state=detail_state(t);
detail_status=detail_label(detail_root,state+(t.unit.empty()?"":" "+t.unit),pad,large?60:35,width-2*pad); detail_status=detail_label(detail_root,state+(t.unit.empty()?"":" "+t.unit),pad,large?80:50,width-2*pad);lv_obj_set_style_text_align(detail_status,LV_TEXT_ALIGN_CENTER,0);lv_obj_set_style_text_color(detail_status,lv_color_hex(0x616161),0);
auto d=t.domain(); auto d=t.domain();
if(t.is_switch()){ if(t.is_switch()){
detail_label(detail_root,"Tap to toggle",pad,top,width-2*pad); detail_label(detail_root,"Tap to toggle",pad,top,width-2*pad);
@@ -851,19 +863,31 @@ inline void set_number(lv_obj_t *obj, lv_style_prop_t prop, int32_t number, lv_s
lv_style_value_t value{}; value.num = number; lv_style_value_t value{}; value.num = number;
lv_obj_set_local_style_prop(obj, prop, value, selector); lv_obj_set_local_style_prop(obj, prop, value, selector);
} }
// The white handle of a card slider sits inside the rounded end of the fill, like Home Assistant's // Card sliders follow Home Assistant's control slider (a 42 px track there): corners of 12 and
// slider. LVGL centres the knob on the end of the fill, so the knob is narrowed and moved back by a // 8 px on track and fill, a white handle 4 px wide and half the height, an eighth of the height in
// quarter of the height; the range starts below zero, so at 0 the fill is still one round end that // from the end of the fill, and a fill that never gets shorter than a third of the height. LVGL
// holds the handle. Values below zero never leave the slider (see slider_event). // centres the knob on the end of the fill, so the knob is narrowed and moved back; the range starts
// below zero, so at 0 the fill is still that short stub holding the handle. Values below zero never
// leave the slider (see slider_event). Small strips keep round ends: the corners would not show.
inline int slider_handle_width(int height) { return height > 20 ? 4 : 2; }
inline int slider_stub(int height) { return std::max(height / 3, 2 * std::max(1, height / 8) + slider_handle_width(height)); }
inline void slider_handle(lv_obj_t *slider, int width, int height) { inline void slider_handle(lv_obj_t *slider, int width, int height) {
int handle = height > 20 ? 6 : 4, inset = height / 4 + handle / 2, half = height >> 1; int handle = slider_handle_width(height), inset = std::max(1, height / 8) + handle / 2, half = height >> 1;
set_number(slider, LV_STYLE_RADIUS, height > 20 ? height * 12 / 42 : LV_RADIUS_CIRCLE, LV_PART_MAIN);
set_number(slider, LV_STYLE_RADIUS, height > 20 ? height * 8 / 42 : LV_RADIUS_CIRCLE, LV_PART_INDICATOR);
set_number(slider, LV_STYLE_PAD_LEFT, inset + handle / 2 - half, LV_PART_KNOB); set_number(slider, LV_STYLE_PAD_LEFT, inset + handle / 2 - half, LV_PART_KNOB);
set_number(slider, LV_STYLE_PAD_RIGHT, handle / 2 - inset - (height - half), LV_PART_KNOB); set_number(slider, LV_STYLE_PAD_RIGHT, handle / 2 - inset - (height - half), LV_PART_KNOB);
set_number(slider, LV_STYLE_PAD_TOP, -(height / 4), LV_PART_KNOB); set_number(slider, LV_STYLE_PAD_TOP, -(height / 4), LV_PART_KNOB);
set_number(slider, LV_STYLE_PAD_BOTTOM, -(height / 4), LV_PART_KNOB); set_number(slider, LV_STYLE_PAD_BOTTOM, -(height / 4), LV_PART_KNOB);
int below = width > height ? 1000 * height / (width - height) : 0; int stub = slider_stub(height), below = width > stub ? 1000 * stub / (width - stub) : 0;
if (lv_slider_get_min_value(slider) != -below || lv_slider_get_max_value(slider) != 1000) lv_slider_set_range(slider, -below, 1000); if (lv_slider_get_min_value(slider) != -below || lv_slider_get_max_value(slider) != 1000) lv_slider_set_range(slider, -below, 1000);
} }
// An off light or fan shows only the grey track, as in Home Assistant: no fill, no handle.
inline void slider_bar(lv_obj_t *slider, bool shown) {
set_number(slider, LV_STYLE_BG_OPA, shown ? LV_OPA_COVER : LV_OPA_TRANSP, LV_PART_INDICATOR);
set_number(slider, LV_STYLE_BG_OPA, shown ? LV_OPA_COVER : LV_OPA_TRANSP, LV_PART_KNOB);
}
inline bool slider_bar_shown(const Tile &t, bool on) { auto d = t.domain(); return on || (d != "light" && d != "fan"); }
inline void set_hidden(lv_obj_t *obj, bool hidden) { if (hidden) lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN); else lv_obj_remove_flag(obj, LV_OBJ_FLAG_HIDDEN); } inline void set_hidden(lv_obj_t *obj, bool hidden) { if (hidden) lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN); else lv_obj_remove_flag(obj, LV_OBJ_FLAG_HIDDEN); }
// A card's size as its styles request it. LVGL's own getters only follow after a layout pass, and // A card's size as its styles request it. LVGL's own getters only follow after a layout pass, and
// that pass walks every object on the screen, so the cards are laid out without asking for one. // that pass walks every object on the screen, so the cards are laid out without asking for one.
@@ -899,7 +923,7 @@ inline void bind(size_t index, lv_obj_t *tile, lv_obj_t *title, lv_obj_t *value,
lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFFFFFF),LV_PART_KNOB);lv_obj_set_style_bg_opa(w.slider,LV_OPA_COVER,LV_PART_KNOB); lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFFFFFF),LV_PART_KNOB);lv_obj_set_style_bg_opa(w.slider,LV_OPA_COVER,LV_PART_KNOB);
slider_handle(w.slider,lv_obj_get_width(tile)-24,strip); slider_handle(w.slider,lv_obj_get_width(tile)-24,strip);
lv_obj_set_style_border_width(w.slider,0,LV_PART_KNOB);lv_obj_set_style_shadow_width(w.slider,0,LV_PART_KNOB); lv_obj_set_style_border_width(w.slider,0,LV_PART_KNOB);lv_obj_set_style_shadow_width(w.slider,0,LV_PART_KNOB);
lv_obj_set_style_radius(w.slider,14,LV_PART_MAIN);lv_obj_set_style_radius(w.slider,14,LV_PART_INDICATOR);lv_obj_set_style_radius(w.slider,2,LV_PART_KNOB);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFCE5B4),LV_PART_MAIN);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFFB900),LV_PART_INDICATOR); lv_obj_set_style_radius(w.slider,2,LV_PART_KNOB);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFCE5B4),LV_PART_MAIN);lv_obj_set_style_bg_color(w.slider,lv_color_hex(0xFFB900),LV_PART_INDICATOR);
lv_obj_add_flag(w.slider,LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(w.slider,LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(w.slider,LV_OBJ_FLAG_GESTURE_BUBBLE); lv_obj_remove_flag(w.slider,LV_OBJ_FLAG_GESTURE_BUBBLE);
lv_obj_add_event_cb(w.slider,slider_event,LV_EVENT_ALL,(void*)(uintptr_t)index); lv_obj_add_event_cb(w.slider,slider_event,LV_EVENT_ALL,(void*)(uintptr_t)index);
@@ -1028,7 +1052,14 @@ inline std::string time_text(esphome::ESPTime now) {
// large cards) with hour and minute hands. A single card adds a calendar block // large cards) with hour and minute hands. A single card adds a calendar block
// beside the dial (weekday, big day number, short month); a wide card adds the // beside the dial (weekday, big day number, short month); a wide card adds the
// digital time and the date instead. Parts: 0-11 marks, 12-13 hands, 14 centre, // digital time and the date instead. Parts: 0-11 marks, 12-13 hands, 14 centre,
// 15-17 text. // 15-17 text, 18 the second hand (points 28-29), shown while the screen is awake.
inline void second_hand(Widgets &w,const esphome::ESPTime &now) {
float a=(now.is_valid()?now.second:0)*3.14159265f/30;
w.points[28]={(lv_value_precise_t)(w.hand_cx-w.hand_r*0.2f*sinf(a)),(lv_value_precise_t)(w.hand_cy+w.hand_r*0.2f*cosf(a))};
w.points[29]={(lv_value_precise_t)(w.hand_cx+w.hand_r*0.92f*sinf(a)),(lv_value_precise_t)(w.hand_cy-w.hand_r*0.92f*cosf(a))};
auto *p=part_line(w,18,w.points+28,2,w.hand_width);
set_hidden(p,!(awake() && now.is_valid()));
}
inline void render_clock(Widgets &w,const Tile &t,bool large,int width,int height) { inline void render_clock(Widgets &w,const Tile &t,bool large,int width,int height) {
bool analog=t.display=="analog"; bool analog=t.display=="analog";
begin_extra(w,analog?(w.wide?"analog":"calendar"):"digital",width,height); begin_extra(w,analog?(w.wide?"analog":"calendar"):"digital",width,height);
@@ -1065,6 +1096,11 @@ inline void render_clock(Widgets &w,const Tile &t,bool large,int width,int heigh
w.points[2]={(lv_value_precise_t)cx,(lv_value_precise_t)cy};w.points[3]={(lv_value_precise_t)(cx+radius*0.82f*sinf(minute)),(lv_value_precise_t)(cy-radius*0.82f*cosf(minute))}; w.points[2]={(lv_value_precise_t)cx,(lv_value_precise_t)cy};w.points[3]={(lv_value_precise_t)(cx+radius*0.82f*sinf(minute)),(lv_value_precise_t)(cy-radius*0.82f*cosf(minute))};
part_line(w,12,w.points,2,large?5:3);part_line(w,13,w.points+2,2,large?3:2); part_line(w,12,w.points,2,large?5:3);part_line(w,13,w.points+2,2,large?3:2);
int center=large?8:4;part_dot(w,14,cx-center/2,cy-center/2,center); int center=large?8:4;part_dot(w,14,cx-center/2,cy-center/2,center);
// A thin red second hand with a short tail, under the centre dot; tick() moves it every second.
w.hand_cx=cx;w.hand_cy=cy;w.hand_r=radius;w.hand_width=large?2:1;
bool new_hand=!w.parts[18];
second_hand(w,now);
if(new_hand)lv_obj_move_to_index(w.parts[18],lv_obj_get_index(w.parts[14]));
std::string day=now.is_valid()?std::to_string(now.day_of_month):"--"; std::string day=now.is_valid()?std::to_string(now.day_of_month):"--";
if(w.wide){ if(w.wide){
int x=dial+(large?16:8),y=std::max(0,(height-text_h)/2); int x=dial+(large?16:8),y=std::max(0,(height-text_h)/2);
@@ -1257,7 +1293,6 @@ inline int layout_panel(Widgets &w,const Tile &t,bool large,int content_w,int co
lv_obj_remove_flag(slider,LV_OBJ_FLAG_GESTURE_BUBBLE);lv_obj_remove_flag(slider,LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(slider,LV_OBJ_FLAG_GESTURE_BUBBLE);lv_obj_remove_flag(slider,LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_size(slider,mode=="volume"?m.slider_w:m.pill_w,h); lv_obj_set_size(slider,mode=="volume"?m.slider_w:m.pill_w,h);
lv_obj_set_style_pad_all(slider,0,LV_PART_MAIN); lv_obj_set_style_pad_all(slider,0,LV_PART_MAIN);
lv_obj_set_style_radius(slider,LV_RADIUS_CIRCLE,LV_PART_MAIN);lv_obj_set_style_radius(slider,LV_RADIUS_CIRCLE,LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(slider,LV_OPA_COVER,LV_PART_MAIN);lv_obj_set_style_bg_opa(slider,LV_OPA_COVER,LV_PART_INDICATOR); lv_obj_set_style_bg_opa(slider,LV_OPA_COVER,LV_PART_MAIN);lv_obj_set_style_bg_opa(slider,LV_OPA_COVER,LV_PART_INDICATOR);
// The handle is a short white bar inside the fill, like Home Assistant's slider. // The handle is a short white bar inside the fill, like Home Assistant's slider.
lv_obj_set_style_radius(slider,2,LV_PART_KNOB);lv_obj_set_style_bg_color(slider,lv_color_hex(0xFFFFFF),LV_PART_KNOB);lv_obj_set_style_bg_opa(slider,LV_OPA_COVER,LV_PART_KNOB); lv_obj_set_style_radius(slider,2,LV_PART_KNOB);lv_obj_set_style_bg_color(slider,lv_color_hex(0xFFFFFF),LV_PART_KNOB);lv_obj_set_style_bg_opa(slider,LV_OPA_COVER,LV_PART_KNOB);
@@ -1355,9 +1390,12 @@ inline void style_panel(Widgets &w,const Tile &t,lv_color_t accent,lv_color_t te
} }
if(w.pill){set_color(w.pill,LV_STYLE_BG_COLOR,key_bg);set_color(w.pill_value,LV_STYLE_TEXT_COLOR,text);} if(w.pill){set_color(w.pill,LV_STYLE_BG_COLOR,key_bg);set_color(w.pill_value,LV_STYLE_TEXT_COLOR,text);}
if(w.control_slider){ if(w.control_slider){
lv_color_t fill=fresh() && t.active()?accent:lv_color_hex(0x9E9E9E); bool on=fresh() && t.active();
set_color(w.control_slider,LV_STYLE_BG_COLOR,lv_color_mix(fill,card,76),LV_PART_MAIN); lv_color_t fill=on?accent:lv_color_hex(0x9E9E9E);
// The track is the fill colour at 20 % over the card, as in Home Assistant.
set_color(w.control_slider,LV_STYLE_BG_COLOR,lv_color_mix(fill,card,51),LV_PART_MAIN);
set_color(w.control_slider,LV_STYLE_BG_COLOR,fill,LV_PART_INDICATOR); set_color(w.control_slider,LV_STYLE_BG_COLOR,fill,LV_PART_INDICATOR);
slider_bar(w.control_slider,slider_bar_shown(t,on));
} }
if(w.knob){set_color(w.keys[0],LV_STYLE_BG_COLOR,lv_color_hex(0xD7DADF));set_color(w.keys[0],LV_STYLE_BG_COLOR,lv_color_hex(0xC5C9CF),LV_STATE_PRESSED);} if(w.knob){set_color(w.keys[0],LV_STYLE_BG_COLOR,lv_color_hex(0xD7DADF));set_color(w.keys[0],LV_STYLE_BG_COLOR,lv_color_hex(0xC5C9CF),LV_STATE_PRESSED);}
} }
@@ -1536,10 +1574,11 @@ inline void render_slot(size_t slot) {
auto circle_color=available?lv_color_mix(color,lv_color_hex(0xFFFFFF),38):lv_color_hex(0xF0F0F0); auto circle_color=available?lv_color_mix(color,lv_color_hex(0xFFFFFF),38):lv_color_hex(0xF0F0F0);
// Darken the foreground slightly: very pale bulbs still need a visible icon. // Darken the foreground slightly: very pale bulbs still need a visible icon.
auto icon_color=available?lv_color_mix(color,lv_color_hex(0x333333),205):lv_color_hex(0x9E9E9E); auto icon_color=available?lv_color_mix(color,lv_color_hex(0x333333),205):lv_color_hex(0x9E9E9E);
// Off: the round end that holds the handle is grey, so the slider reads as off too. // Off: a grey track without fill or handle, as in Home Assistant.
auto fill_color=on?color:lv_color_hex(0x9E9E9E); auto fill_color=on?color:lv_color_hex(0x9E9E9E);
set_color(w.slider,LV_STYLE_BG_COLOR,fill_color,LV_PART_INDICATOR); set_color(w.slider,LV_STYLE_BG_COLOR,fill_color,LV_PART_INDICATOR);
set_color(w.slider,LV_STYLE_BG_COLOR,lv_color_mix(fill_color,lv_color_hex(0xFFFFFF),30),LV_PART_MAIN); set_color(w.slider,LV_STYLE_BG_COLOR,lv_color_mix(fill_color,lv_color_hex(0xFFFFFF),51),LV_PART_MAIN);
slider_bar(w.slider,slider_bar_shown(t,on));
set_color(w.tile,LV_STYLE_BG_COLOR,lv_color_hex(t.background ? t.background : 0xFFFFFF)); set_color(w.tile,LV_STYLE_BG_COLOR,lv_color_hex(t.background ? t.background : 0xFFFFFF));
set_number(w.tile,LV_STYLE_BORDER_WIDTH,1); set_number(w.tile,LV_STYLE_BORDER_WIDTH,1);
// "Background: none" hides only the card; geometry and padding stay identical, // "Background: none" hides only the card; geometry and padding stay identical,
@@ -1563,7 +1602,7 @@ inline void render_slot(size_t slot) {
bool muted=w.extra_mode=="forecast" ? i>=2 && i%3==2 : w.extra_mode=="sunpath" ? i>=1 : w.extra_mode=="calendar" ? i==15||i==17 : i==16; bool muted=w.extra_mode=="forecast" ? i>=2 && i%3==2 : w.extra_mode=="sunpath" ? i>=1 : w.extra_mode=="calendar" ? i==15||i==17 : i==16;
if(lv_obj_check_type(p,&lv_label_class))set_color(p,LV_STYLE_TEXT_COLOR,muted?value_color:title_color); if(lv_obj_check_type(p,&lv_label_class))set_color(p,LV_STYLE_TEXT_COLOR,muted?value_color:title_color);
else if(w.extra_mode=="sunpath")continue; else if(w.extra_mode=="sunpath")continue;
else if(lv_obj_check_type(p,&lv_line_class))set_color(p,LV_STYLE_LINE_COLOR,w.extra_mode=="graph"?color:i<12?value_color:i==13?icon_color:title_color); else if(lv_obj_check_type(p,&lv_line_class))set_color(p,LV_STYLE_LINE_COLOR,w.extra_mode=="graph"?color:i==18?lv_color_hex(0xE53935):i<12?value_color:i==13?icon_color:title_color);
else set_color(p,LV_STYLE_BG_COLOR,i==14?icon_color:value_color); else set_color(p,LV_STYLE_BG_COLOR,i==14?icon_color:value_color);
} }
lap(swipe_profile::PALETTE); lap(swipe_profile::PALETTE);
@@ -1586,10 +1625,11 @@ inline lv_timer_t *fill_timer=nullptr;
inline bool fill_refreshed=false; inline bool fill_refreshed=false;
inline uint32_t fill_step_ms=0; inline uint32_t fill_step_ms=0;
inline bool fill_cards(size_t cards); inline bool fill_cards(size_t cards);
// Before the first layout the screen is starting: HA connects, then ESP Screens sends the tiles.
inline void render(lv_obj_t *room) { inline void render(lv_obj_t *room) {
if (!enabled) return; if (!enabled) return;
room_label=room; swipe_profile::Lap lap; room_label=room; swipe_profile::Lap lap;
label(room, !model.configured ? "Choose tiles in HA" : !model.ready() ? "Loading tiles..." : !ha_connected() ? "HA not connected" : !feed_alive() ? "ESP Screens not active" : model.title); label(room, !model.configured ? (!ha_connected() ? "Connecting to Home Assistant..." : "Waiting for ESP Screens...") : !model.ready() ? "Loading tiles..." : !ha_connected() ? "HA not connected" : !feed_alive() ? "ESP Screens not active" : model.title);
render_header(); render_header();
lap(swipe_profile::HEADER); lap(swipe_profile::HEADER);
bool all=dirty_all || (!dirty_tiles && !dirty_header); bool all=dirty_all || (!dirty_tiles && !dirty_header);
@@ -2068,6 +2108,8 @@ inline void tick() {
auto &w=widgets[slot];if(!w.tile || w.index>=model.count || lv_obj_has_flag(w.tile,LV_OBJ_FLAG_HIDDEN))continue; auto &w=widgets[slot];if(!w.tile || w.index>=model.count || lv_obj_has_flag(w.tile,LV_OBJ_FLAG_HIDDEN))continue;
const auto &t=model.tiles[w.index]; const auto &t=model.tiles[w.index];
if((t.builtin() && new_minute) || (t.domain()=="timer" && t.state=="active") || (t.domain()=="sun" && second%60==0))card(w.index); if((t.builtin() && new_minute) || (t.domain()=="timer" && t.state=="active") || (t.domain()=="sun" && second%60==0))card(w.index);
// The second hand moves on its own: only its line is redrawn, and it hides during standby.
else if(w.parts[18] && w.points && t.builtin() && t.display=="analog")second_hand(w,now);
} }
} }
if(redraw && refresh)refresh(); if(redraw && refresh)refresh();
@@ -175,6 +175,7 @@ inline constexpr Named NAMES[] = {
{"chevron-left", 0xF0141}, {"chevron-left", 0xF0141},
{"chevron-right", 0xF0142}, {"chevron-right", 0xF0142},
{"close", 0xF0156}, {"close", 0xF0156},
{"arrow-left", 0xF004D},
{"sun-snowflake-variant", 0xF1A79}, {"sun-snowflake-variant", 0xF1A79},
{"thermostat-auto", 0xF1B17}, {"thermostat-auto", 0xF1B17},
{"weather-fog", 0xF0591}, {"weather-fog", 0xF0591},
+22
View File
@@ -176,6 +176,28 @@ icons sit off-center in the browser); run `generate_packages.py` afterward.
The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual
profiles must come from the set. No changed preferences or keys. profiles must come from the set. No changed preferences or keys.
### Compatibility 0.2.43 / firmware 0.2.37
Firmware only: storage version, tile protocol, preferences and keys are unchanged.
- `runtime_tiles::screen_awake` is a hook both profiles set to `!id(display_dimmed)`; without it
the runtime counts as awake. `second_hand()` draws part 18 (points 28-29) of an analog clock
card and `tick()` moves it once a second while awake, hiding it otherwise; the minute redraw
stays as it was.
- `slider_handle()` now sets the track and fill radii (12/42 and 8/42 of the height, round ends
under 20 px), a handle of 4 px (2 px on small strips) an eighth of the height from the end, and a
shortest fill of a third of the height (`slider_stub`). `slider_bar()` hides fill and knob for
an off light or fan (`slider_bar_shown`); tracks are the fill colour at 20 % over the card.
`light_controls::Row::off` does the same for the colour card's brightness row.
- `commit_slider` floors a light's brightness at 3 (1 %); the card tap turns it off.
- Overlay top bar: the profiles' `*_close_button`, `climate_mode_overlay_x`, `vacuum_overlay_x` and
the new `overlay_back_button` are one round `arrow-left` (glyph `F004D`, added to
`tile_icons.FIXED`) button at the top left, 60/40 px (Guition/CYD) at (16,16)/(10,8);
`climate_power_button` is the same size at the top right. `color_done_button` and
`vacuum_overlay_ok` are gone; `show_detail()` draws the same bar for the runtime detail card.
- `render()` shows "Connecting to Home Assistant..." / "Waiting for ESP Screens..." until the
first layout arrives.
### Compatibility 0.2.42 / firmware 0.2.36 ### Compatibility 0.2.42 / firmware 0.2.36
Firmware only: storage version, tile protocol, preferences and keys are unchanged. What moved Firmware only: storage version, tile protocol, preferences and keys are unchanged. What moved
+74 -106
View File
@@ -1,7 +1,7 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911. # Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile. # Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions: substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.36" SCREEN_FIRMWARE_VERSION: "0.2.37"
# 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not # 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not
# (like iOS). The CYD keeps a limit because its resistive panel jumps. # (like iOS). The CYD keeps a limit because its resistive panel jumps.
TOUCH_MOVE_LIMIT_PX: "0" TOUCH_MOVE_LIMIT_PX: "0"
@@ -641,6 +641,7 @@ esphome:
runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font();
runtime_tiles::clock_font = id(clock_digits)->get_lv_font(); runtime_tiles::clock_font = id(clock_digits)->get_lv_font();
runtime_tiles::now_time = []() { return id(homeassistant_time).now(); }; runtime_tiles::now_time = []() { return id(homeassistant_time).now(); };
runtime_tiles::screen_awake = []() { return !id(display_dimmed); };
runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font();
runtime_tiles::control_font = id(sublabel_big)->get_lv_font(); runtime_tiles::control_font = id(sublabel_big)->get_lv_font();
// Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock // Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock
@@ -1342,6 +1343,7 @@ font:
- "\U000F0141" # chevron-left - "\U000F0141" # chevron-left
- "\U000F0142" # chevron-right - "\U000F0142" # chevron-right
- "\U000F0156" # close - "\U000F0156" # close
- "\U000F004D" # arrow-left
- "\U000F1A79" # sun-snowflake-variant - "\U000F1A79" # sun-snowflake-variant
- "\U000F1B17" # thermostat-auto - "\U000F1B17" # thermostat-auto
- "\U000F0591" # weather-fog - "\U000F0591" # weather-fog
@@ -3678,10 +3680,33 @@ lvgl:
clickable: false clickable: false
scrollable: false scrollable: false
- obj:
id: overlay_back_button
align: TOP_LEFT
x: 16
y: 16
width: 60
height: 60
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "\U000F004D"
text_font: materialdesign_icons_mini
text_color: 0x1B1B1B
clickable: false
- label: - label:
id: overlay_title id: overlay_title
align: TOP_MID align: TOP_MID
y: 20 y: 30
text: "Light" text: "Light"
styles: style_room styles: style_room
@@ -3992,45 +4017,10 @@ lvgl:
- obj: - obj:
id: color_close_button id: color_close_button
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
width: 54 width: 60
height: 54 height: 60
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- label:
id: color_detail_title
width: 310
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 32
text: "Light Color"
styles: style_room
- obj:
id: color_done_button
align: TOP_RIGHT
x: -15
y: 18
width: 54
height: 54
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -4042,11 +4032,22 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
- label:
id: color_detail_title
width: 310
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 35
text: "Light Color"
styles: style_room
# ---------- CLIMATE DETAIL OVERLAY ---------- # ---------- CLIMATE DETAIL OVERLAY ----------
- obj: - obj:
id: climate_detail_overlay id: climate_detail_overlay
@@ -4089,18 +4090,14 @@ lvgl:
- obj: - obj:
id: climate_close_button id: climate_close_button
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
# 44 instead of 36: slightly bigger tap target, grows away from the arc width: 60
# (top-left anchor stays put) instead of towards it. height: 60
width: 66
height: 66
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xFFFFFF
border_opa: 20%
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4108,18 +4105,18 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
- obj: - obj:
id: climate_power_button id: climate_power_button
align: TOP_RIGHT align: TOP_RIGHT
x: -15 x: -16
y: 18 y: 16
width: 66 width: 60
height: 66 height: 60
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -4140,7 +4137,7 @@ lvgl:
- label: - label:
id: climate_detail_title id: climate_detail_title
align: TOP_MID align: TOP_MID
y: 32 y: 35
text: "Climate" text: "Climate"
styles: style_room styles: style_room
@@ -4311,7 +4308,7 @@ lvgl:
widgets: widgets:
- label: - label:
align: TOP_MID align: TOP_MID
y: 32 y: 35
text: "Mode" text: "Mode"
text_font: sublabel_big text_font: sublabel_big
text_color: 0x1B1B1B text_color: 0x1B1B1B
@@ -4684,16 +4681,14 @@ lvgl:
- obj: - obj:
id: climate_mode_overlay_x id: climate_mode_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
width: 54 width: 60
height: 54 height: 60
radius: 999 radius: 999
bg_color: 0xFFFFFF bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xDDDDDD
border_opa: COVER
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4701,8 +4696,8 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
@@ -4727,10 +4722,9 @@ lvgl:
- label: - label:
id: vacuum_title id: vacuum_title
align: TOP_MID align: TOP_MID
y: 32 y: 35
text: "Vacuum" text: "Vacuum"
text_font: sublabel_big styles: style_room
text_color: 0x1B1B1B
clickable: false clickable: false
- obj: - obj:
@@ -4948,38 +4942,13 @@ lvgl:
- obj: - obj:
id: vacuum_overlay_x id: vacuum_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
width: 54 width: 60
height: 54 height: 60
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- obj:
id: vacuum_overlay_ok
align: TOP_RIGHT
x: -15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0xD5EEFC
bg_opa: COVER
border_width: 0 border_width: 0
clickable: true clickable: true
scrollable: false scrollable: false
@@ -4988,11 +4957,12 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
# ---------- DIM / WAKE OVERLAY ---------- # ---------- DIM / WAKE OVERLAY ----------
- obj: - obj:
id: dim_wake_overlay id: dim_wake_overlay
@@ -5691,12 +5661,10 @@ script:
light_controls::open("diagnostic", true, true, 75); light_controls::open("diagnostic", true, true, 75);
lv_label_set_text(id(color_detail_title), "Example lamp"); lv_label_set_text(id(color_detail_title), "Example lamp");
- lvgl.widget.hide: color_close_button - lvgl.widget.hide: color_close_button
- lvgl.widget.hide: color_done_button
- lvgl.widget.show: color_detail_overlay - lvgl.widget.show: color_detail_overlay
- delay: 40s - delay: 40s
- lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: color_detail_overlay
- lvgl.widget.show: color_close_button - lvgl.widget.show: color_close_button
- lvgl.widget.show: color_done_button
- lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};' - lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};'
- id: open_color_detail - id: open_color_detail
+71 -103
View File
@@ -57,7 +57,7 @@
############################################################ ############################################################
substitutions: substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.36" SCREEN_FIRMWARE_VERSION: "0.2.37"
# A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe. # A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "56" TOUCH_MOVE_LIMIT_PX: "56"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not. # Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
@@ -823,6 +823,7 @@ esphome:
runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font();
runtime_tiles::clock_font = id(clock_digits)->get_lv_font(); runtime_tiles::clock_font = id(clock_digits)->get_lv_font();
runtime_tiles::now_time = []() { return id(homeassistant_time).now(); }; runtime_tiles::now_time = []() { return id(homeassistant_time).now(); };
runtime_tiles::screen_awake = []() { return !id(display_dimmed); };
runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font();
runtime_tiles::control_font = id(sublabel_big)->get_lv_font(); runtime_tiles::control_font = id(sublabel_big)->get_lv_font();
// Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock // Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock
@@ -1531,6 +1532,7 @@ font:
- "\U000F0141" # chevron-left - "\U000F0141" # chevron-left
- "\U000F0142" # chevron-right - "\U000F0142" # chevron-right
- "\U000F0156" # close - "\U000F0156" # close
- "\U000F004D" # arrow-left
- "\U000F1A79" # sun-snowflake-variant - "\U000F1A79" # sun-snowflake-variant
- "\U000F1B17" # thermostat-auto - "\U000F1B17" # thermostat-auto
- "\U000F0591" # weather-fog - "\U000F0591" # weather-fog
@@ -3797,24 +3799,47 @@ lvgl:
clickable: false clickable: false
scrollable: false scrollable: false
- obj:
id: overlay_back_button
align: TOP_LEFT
x: 10
y: 8
width: 40
height: 40
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "\U000F004D"
text_font: materialdesign_icons_mini
text_color: 0x1B1B1B
clickable: false
- label: - label:
id: overlay_title id: overlay_title
align: TOP_MID align: TOP_MID
y: 10 y: 19
text: "Light" text: "Light"
styles: style_room styles: style_room
- label: - label:
id: overlay_value id: overlay_value
align: TOP_MID align: TOP_MID
y: 35 y: 42
text: "100 %" text: "100 %"
styles: style_value_big styles: style_value_big
- slider: - slider:
id: overlay_slider id: overlay_slider
align: CENTER align: CENTER
y: 18 y: 24
width: ${OVERLAY_SLIDER_W} width: ${OVERLAY_SLIDER_W}
height: ${OVERLAY_SLIDER_H} height: ${OVERLAY_SLIDER_H}
min_value: 0 min_value: 0
@@ -4112,44 +4137,9 @@ lvgl:
id: color_close_button id: color_close_button
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
width: 36 width: 40
height: 36 height: 40
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- label:
id: color_detail_title
width: 210
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 16
text: "Light Color"
styles: style_room
- obj:
id: color_done_button
align: TOP_RIGHT
x: -10
y: 9
width: 36
height: 36
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -4161,11 +4151,22 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
- label:
id: color_detail_title
width: 210
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 21
text: "Light Color"
styles: style_room
# ---------- CLIMATE DETAIL OVERLAY ---------- # ---------- CLIMATE DETAIL OVERLAY ----------
- obj: - obj:
id: climate_detail_overlay id: climate_detail_overlay
@@ -4209,17 +4210,13 @@ lvgl:
id: climate_close_button id: climate_close_button
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
# 44 instead of 36: slightly bigger tap target, grows away from the arc width: 40
# (top-left anchor stays put) instead of towards it. height: 40
width: 44
height: 44
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xFFFFFF
border_opa: 20%
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4227,8 +4224,8 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
@@ -4236,9 +4233,9 @@ lvgl:
id: climate_power_button id: climate_power_button
align: TOP_RIGHT align: TOP_RIGHT
x: -10 x: -10
y: 9 y: 8
width: 44 width: 40
height: 44 height: 40
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -4259,7 +4256,7 @@ lvgl:
- label: - label:
id: climate_detail_title id: climate_detail_title
align: TOP_MID align: TOP_MID
y: 16 y: 21
text: "Climate" text: "Climate"
styles: style_room styles: style_room
@@ -4430,7 +4427,7 @@ lvgl:
widgets: widgets:
- label: - label:
align: TOP_MID align: TOP_MID
y: 16 y: 21
text: "Mode" text: "Mode"
text_font: sublabel_big text_font: sublabel_big
text_color: 0x1B1B1B text_color: 0x1B1B1B
@@ -4804,15 +4801,13 @@ lvgl:
id: climate_mode_overlay_x id: climate_mode_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
width: 36 width: 40
height: 36 height: 40
radius: 999 radius: 999
bg_color: 0xFFFFFF bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xDDDDDD
border_opa: COVER
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4820,8 +4815,8 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
@@ -4846,10 +4841,9 @@ lvgl:
- label: - label:
id: vacuum_title id: vacuum_title
align: TOP_MID align: TOP_MID
y: 16 y: 21
text: "Vacuum" text: "Vacuum"
text_font: sublabel_big styles: style_room
text_color: 0x1B1B1B
clickable: false clickable: false
- obj: - obj:
@@ -5068,37 +5062,12 @@ lvgl:
id: vacuum_overlay_x id: vacuum_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
width: 36 width: 40
height: 36 height: 40
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- obj:
id: vacuum_overlay_ok
align: TOP_RIGHT
x: -10
y: 9
width: 36
height: 36
radius: 999
bg_color: 0xD5EEFC
bg_opa: COVER
border_width: 0 border_width: 0
clickable: true clickable: true
scrollable: false scrollable: false
@@ -5107,11 +5076,12 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
# ---------- DIM / WAKE OVERLAY ---------- # ---------- DIM / WAKE OVERLAY ----------
- obj: - obj:
id: dim_wake_overlay id: dim_wake_overlay
@@ -5786,12 +5756,10 @@ script:
light_controls::open("diagnostic", true, true, 75); light_controls::open("diagnostic", true, true, 75);
lv_label_set_text(id(color_detail_title), "Example lamp"); lv_label_set_text(id(color_detail_title), "Example lamp");
- lvgl.widget.hide: color_close_button - lvgl.widget.hide: color_close_button
- lvgl.widget.hide: color_done_button
- lvgl.widget.show: color_detail_overlay - lvgl.widget.show: color_detail_overlay
- delay: 40s - delay: 40s
- lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: color_detail_overlay
- lvgl.widget.show: color_close_button - lvgl.widget.show: color_close_button
- lvgl.widget.show: color_done_button
- lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};' - lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};'
- id: open_color_detail - id: open_color_detail
+71 -103
View File
@@ -58,7 +58,7 @@
############################################################ ############################################################
substitutions: substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.36" SCREEN_FIRMWARE_VERSION: "0.2.37"
# A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe. # A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "56" TOUCH_MOVE_LIMIT_PX: "56"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not. # Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
@@ -827,6 +827,7 @@ esphome:
runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font();
runtime_tiles::clock_font = id(clock_digits)->get_lv_font(); runtime_tiles::clock_font = id(clock_digits)->get_lv_font();
runtime_tiles::now_time = []() { return id(homeassistant_time).now(); }; runtime_tiles::now_time = []() { return id(homeassistant_time).now(); };
runtime_tiles::screen_awake = []() { return !id(display_dimmed); };
runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font();
runtime_tiles::control_font = id(sublabel_big)->get_lv_font(); runtime_tiles::control_font = id(sublabel_big)->get_lv_font();
// Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock // Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock
@@ -1520,6 +1521,7 @@ font:
- "\U000F0141" # chevron-left - "\U000F0141" # chevron-left
- "\U000F0142" # chevron-right - "\U000F0142" # chevron-right
- "\U000F0156" # close - "\U000F0156" # close
- "\U000F004D" # arrow-left
- "\U000F1A79" # sun-snowflake-variant - "\U000F1A79" # sun-snowflake-variant
- "\U000F1B17" # thermostat-auto - "\U000F1B17" # thermostat-auto
- "\U000F0591" # weather-fog - "\U000F0591" # weather-fog
@@ -3728,24 +3730,47 @@ lvgl:
clickable: false clickable: false
scrollable: false scrollable: false
- obj:
id: overlay_back_button
align: TOP_LEFT
x: 10
y: 8
width: 40
height: 40
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "\U000F004D"
text_font: materialdesign_icons_mini
text_color: 0x1B1B1B
clickable: false
- label: - label:
id: overlay_title id: overlay_title
align: TOP_MID align: TOP_MID
y: 10 y: 19
text: "Light" text: "Light"
styles: style_room styles: style_room
- label: - label:
id: overlay_value id: overlay_value
align: TOP_MID align: TOP_MID
y: 35 y: 42
text: "100 %" text: "100 %"
styles: style_value_big styles: style_value_big
- slider: - slider:
id: overlay_slider id: overlay_slider
align: CENTER align: CENTER
y: 18 y: 24
width: ${OVERLAY_SLIDER_W} width: ${OVERLAY_SLIDER_W}
height: ${OVERLAY_SLIDER_H} height: ${OVERLAY_SLIDER_H}
min_value: 0 min_value: 0
@@ -4043,44 +4068,9 @@ lvgl:
id: color_close_button id: color_close_button
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
width: 36 width: 40
height: 36 height: 40
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- label:
id: color_detail_title
width: 210
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 16
text: "Light Color"
styles: style_room
- obj:
id: color_done_button
align: TOP_RIGHT
x: -10
y: 9
width: 36
height: 36
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -4092,11 +4082,22 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
- label:
id: color_detail_title
width: 210
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 21
text: "Light Color"
styles: style_room
# ---------- CLIMATE DETAIL OVERLAY ---------- # ---------- CLIMATE DETAIL OVERLAY ----------
- obj: - obj:
id: climate_detail_overlay id: climate_detail_overlay
@@ -4140,17 +4141,13 @@ lvgl:
id: climate_close_button id: climate_close_button
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
# 44 instead of 36: slightly bigger tap target, grows away from the arc width: 40
# (top-left anchor stays put) instead of towards it. height: 40
width: 44
height: 44
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xFFFFFF
border_opa: 20%
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4158,8 +4155,8 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
@@ -4167,9 +4164,9 @@ lvgl:
id: climate_power_button id: climate_power_button
align: TOP_RIGHT align: TOP_RIGHT
x: -10 x: -10
y: 9 y: 8
width: 44 width: 40
height: 44 height: 40
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -4190,7 +4187,7 @@ lvgl:
- label: - label:
id: climate_detail_title id: climate_detail_title
align: TOP_MID align: TOP_MID
y: 16 y: 21
text: "Climate" text: "Climate"
styles: style_room styles: style_room
@@ -4361,7 +4358,7 @@ lvgl:
widgets: widgets:
- label: - label:
align: TOP_MID align: TOP_MID
y: 16 y: 21
text: "Mode" text: "Mode"
text_font: sublabel_big text_font: sublabel_big
text_color: 0x1B1B1B text_color: 0x1B1B1B
@@ -4735,15 +4732,13 @@ lvgl:
id: climate_mode_overlay_x id: climate_mode_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
width: 36 width: 40
height: 36 height: 40
radius: 999 radius: 999
bg_color: 0xFFFFFF bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xDDDDDD
border_opa: COVER
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4751,8 +4746,8 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
@@ -4777,10 +4772,9 @@ lvgl:
- label: - label:
id: vacuum_title id: vacuum_title
align: TOP_MID align: TOP_MID
y: 16 y: 21
text: "Vacuum" text: "Vacuum"
text_font: sublabel_big styles: style_room
text_color: 0x1B1B1B
clickable: false clickable: false
- obj: - obj:
@@ -4999,37 +4993,12 @@ lvgl:
id: vacuum_overlay_x id: vacuum_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 10 x: 10
y: 9 y: 8
width: 36 width: 40
height: 36 height: 40
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- obj:
id: vacuum_overlay_ok
align: TOP_RIGHT
x: -10
y: 9
width: 36
height: 36
radius: 999
bg_color: 0xD5EEFC
bg_opa: COVER
border_width: 0 border_width: 0
clickable: true clickable: true
scrollable: false scrollable: false
@@ -5038,11 +5007,12 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
# ---------- DIM / WAKE OVERLAY ---------- # ---------- DIM / WAKE OVERLAY ----------
- obj: - obj:
id: dim_wake_overlay id: dim_wake_overlay
@@ -5717,12 +5687,10 @@ script:
light_controls::open("diagnostic", true, true, 75); light_controls::open("diagnostic", true, true, 75);
lv_label_set_text(id(color_detail_title), "Example lamp"); lv_label_set_text(id(color_detail_title), "Example lamp");
- lvgl.widget.hide: color_close_button - lvgl.widget.hide: color_close_button
- lvgl.widget.hide: color_done_button
- lvgl.widget.show: color_detail_overlay - lvgl.widget.show: color_detail_overlay
- delay: 40s - delay: 40s
- lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: color_detail_overlay
- lvgl.widget.show: color_close_button - lvgl.widget.show: color_close_button
- lvgl.widget.show: color_done_button
- lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};' - lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};'
- id: open_color_detail - id: open_color_detail
+74 -106
View File
@@ -2,7 +2,7 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911. # Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile. # Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions: substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.36" SCREEN_FIRMWARE_VERSION: "0.2.37"
# 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not # 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not
# (like iOS). The CYD keeps a limit because its resistive panel jumps. # (like iOS). The CYD keeps a limit because its resistive panel jumps.
TOUCH_MOVE_LIMIT_PX: "0" TOUCH_MOVE_LIMIT_PX: "0"
@@ -645,6 +645,7 @@ esphome:
runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font(); runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font();
runtime_tiles::clock_font = id(clock_digits)->get_lv_font(); runtime_tiles::clock_font = id(clock_digits)->get_lv_font();
runtime_tiles::now_time = []() { return id(homeassistant_time).now(); }; runtime_tiles::now_time = []() { return id(homeassistant_time).now(); };
runtime_tiles::screen_awake = []() { return !id(display_dimmed); };
runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font(); runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font();
runtime_tiles::control_font = id(sublabel_big)->get_lv_font(); runtime_tiles::control_font = id(sublabel_big)->get_lv_font();
// Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock // Top bar (0.2.32+): values in the text font, icons in the small icon font; the clock
@@ -1331,6 +1332,7 @@ font:
- "\U000F0141" # chevron-left - "\U000F0141" # chevron-left
- "\U000F0142" # chevron-right - "\U000F0142" # chevron-right
- "\U000F0156" # close - "\U000F0156" # close
- "\U000F004D" # arrow-left
- "\U000F1A79" # sun-snowflake-variant - "\U000F1A79" # sun-snowflake-variant
- "\U000F1B17" # thermostat-auto - "\U000F1B17" # thermostat-auto
- "\U000F0591" # weather-fog - "\U000F0591" # weather-fog
@@ -3609,10 +3611,33 @@ lvgl:
clickable: false clickable: false
scrollable: false scrollable: false
- obj:
id: overlay_back_button
align: TOP_LEFT
x: 16
y: 16
width: 60
height: 60
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "\U000F004D"
text_font: materialdesign_icons_mini
text_color: 0x1B1B1B
clickable: false
- label: - label:
id: overlay_title id: overlay_title
align: TOP_MID align: TOP_MID
y: 20 y: 30
text: "Light" text: "Light"
styles: style_room styles: style_room
@@ -3923,45 +3948,10 @@ lvgl:
- obj: - obj:
id: color_close_button id: color_close_button
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
width: 54 width: 60
height: 54 height: 60
radius: 999
bg_color: 0xEEEEEE
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- label:
id: color_detail_title
width: 310
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 32
text: "Light Color"
styles: style_room
- obj:
id: color_done_button
align: TOP_RIGHT
x: -15
y: 18
width: 54
height: 54
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -3973,11 +3963,22 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
- label:
id: color_detail_title
width: 310
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 35
text: "Light Color"
styles: style_room
# ---------- CLIMATE DETAIL OVERLAY ---------- # ---------- CLIMATE DETAIL OVERLAY ----------
- obj: - obj:
id: climate_detail_overlay id: climate_detail_overlay
@@ -4020,18 +4021,14 @@ lvgl:
- obj: - obj:
id: climate_close_button id: climate_close_button
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
# 44 instead of 36: slightly bigger tap target, grows away from the arc width: 60
# (top-left anchor stays put) instead of towards it. height: 60
width: 66
height: 66
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xFFFFFF
border_opa: 20%
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4039,18 +4036,18 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
- obj: - obj:
id: climate_power_button id: climate_power_button
align: TOP_RIGHT align: TOP_RIGHT
x: -15 x: -16
y: 18 y: 16
width: 66 width: 60
height: 66 height: 60
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
@@ -4071,7 +4068,7 @@ lvgl:
- label: - label:
id: climate_detail_title id: climate_detail_title
align: TOP_MID align: TOP_MID
y: 32 y: 35
text: "Climate" text: "Climate"
styles: style_room styles: style_room
@@ -4242,7 +4239,7 @@ lvgl:
widgets: widgets:
- label: - label:
align: TOP_MID align: TOP_MID
y: 32 y: 35
text: "Mode" text: "Mode"
text_font: sublabel_big text_font: sublabel_big
text_color: 0x1B1B1B text_color: 0x1B1B1B
@@ -4615,16 +4612,14 @@ lvgl:
- obj: - obj:
id: climate_mode_overlay_x id: climate_mode_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
width: 54 width: 60
height: 54 height: 60
radius: 999 radius: 999
bg_color: 0xFFFFFF bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1 border_width: 0
border_color: 0xDDDDDD
border_opa: COVER
clickable: true clickable: true
scrollable: false scrollable: false
on_click: on_click:
@@ -4632,8 +4627,8 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "x" text: "\U000F004D"
text_font: sublabel_big text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
@@ -4658,10 +4653,9 @@ lvgl:
- label: - label:
id: vacuum_title id: vacuum_title
align: TOP_MID align: TOP_MID
y: 32 y: 35
text: "Vacuum" text: "Vacuum"
text_font: sublabel_big styles: style_room
text_color: 0x1B1B1B
clickable: false clickable: false
- obj: - obj:
@@ -4879,38 +4873,13 @@ lvgl:
- obj: - obj:
id: vacuum_overlay_x id: vacuum_overlay_x
align: TOP_LEFT align: TOP_LEFT
x: 15 x: 16
y: 18 y: 16
width: 54 width: 60
height: 54 height: 60
radius: 999 radius: 999
bg_color: 0xEEEEEE bg_color: 0xEEEEEE
bg_opa: COVER bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- script.execute: close_cards
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0x1B1B1B
clickable: false
- obj:
id: vacuum_overlay_ok
align: TOP_RIGHT
x: -15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0xD5EEFC
bg_opa: COVER
border_width: 0 border_width: 0
clickable: true clickable: true
scrollable: false scrollable: false
@@ -4919,11 +4888,12 @@ lvgl:
widgets: widgets:
- label: - label:
align: CENTER align: CENTER
text: "\U000F012C" text: "\U000F004D"
text_font: materialdesign_icons text_font: materialdesign_icons_mini
text_color: 0x1B1B1B text_color: 0x1B1B1B
clickable: false clickable: false
# ---------- DIM / WAKE OVERLAY ---------- # ---------- DIM / WAKE OVERLAY ----------
- obj: - obj:
id: dim_wake_overlay id: dim_wake_overlay
@@ -5622,12 +5592,10 @@ script:
light_controls::open("diagnostic", true, true, 75); light_controls::open("diagnostic", true, true, 75);
lv_label_set_text(id(color_detail_title), "Example lamp"); lv_label_set_text(id(color_detail_title), "Example lamp");
- lvgl.widget.hide: color_close_button - lvgl.widget.hide: color_close_button
- lvgl.widget.hide: color_done_button
- lvgl.widget.show: color_detail_overlay - lvgl.widget.show: color_detail_overlay
- delay: 40s - delay: 40s
- lvgl.widget.hide: color_detail_overlay - lvgl.widget.hide: color_detail_overlay
- lvgl.widget.show: color_close_button - lvgl.widget.show: color_close_button
- lvgl.widget.show: color_done_button
- lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};' - lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};'
- id: open_color_detail - id: open_color_detail
+11
View File
@@ -1,3 +1,14 @@
## 0.2.43 (firmware 0.2.37)
A second hand on the analog clock, sliders that look like Home Assistant's, and one top bar for every card.
- **Second hand.** The analog clock card gets a thin red second hand with a short tail that moves every second while the screen is in use. During standby (also at the night level) it is hidden and the clock keeps its once-a-minute redraw, so a sleeping screen does no extra work. Only the hand's own line is redrawn, not the card. The small dial in the top bar stays as it was.
- **Sliders like Home Assistant.** Card sliders (a light's or fan's direct control, the Sonos volume, the brightness row of the colour card) now use the proportions of HA's control slider: softer corners on track and fill, a slimmer white handle a little in from the end of the fill, a track in the fill colour at 20 %, and a short stub instead of a full circle at 0 or 1 %. A light or fan that is off shows only the empty grey track, without fill or handle, exactly as HA does; the colour card's brightness row then reads Off until you move it.
- **A light's slider stops at 1 %.** As in HA, dragging a light's slider all the way down leaves it on at 1 %; tapping the card turns it off.
- **One top bar on every card.** Every card that opens over the tiles (brightness, colour, climate, vacuum, media, timer, switch, sensor history and the others) has the same bar: a round back arrow at the left, big enough for a finger (60 px on the Guition, 40 px on the CYD), and the name in the middle. Only a card with one overriding action keeps a round button at the right, such as the on/off of a climate device; the colour and vacuum cards lose their extra "done" buttons, which only closed the card. The brightness card, which had no button at all, gets the back arrow too.
- **A starting screen says so.** Until the first layout arrives the top bar reads "Connecting to Home Assistant..." and then "Waiting for ESP Screens...", instead of "Choose tiles in HA".
- Firmware 0.2.37 for both boards; the app offers the update.
## 0.2.42 (firmware 0.2.36) ## 0.2.42 (firmware 0.2.36)
Smoother page swipes and a new colour card for lights. Smoother page swipes and a new colour card for lights.
+1 -1
View File
@@ -17,7 +17,7 @@ WEEKDAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
REPO = 'https://github.com/MaxGramser/homeassistant_espscreen' REPO = 'https://github.com/MaxGramser/homeassistant_espscreen'
REFS = {'cyd': 'main', 'guition': 'main'} REFS = {'cyd': 'main', 'guition': 'main'}
# Firmware shipped with this app release; screens below it get an update offer. # Firmware shipped with this app release; screens below it get an update offer.
FIRMWARE_VERSION = '0.2.36' FIRMWARE_VERSION = '0.2.37'
ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hvac_action hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level is_volume_muted media_title options min max step temperature_unit supported_features device_class next_rising next_setting finishes_at duration remaining humidity wind_speed wind_speed_unit apparent_temperature fan_modes swing_modes fan_mode swing_mode'.split()) ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hvac_action hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level is_volume_muted media_title options min max step temperature_unit supported_features device_class next_rising next_setting finishes_at duration remaining humidity wind_speed wind_speed_unit apparent_temperature fan_modes swing_modes fan_mode swing_mode'.split())
# Attributes whose boolean value the screen needs; every other bool stays behind. # Attributes whose boolean value the screen needs; every other bool stays behind.
BOOL_ATTRS = frozenset(['is_volume_muted']) BOOL_ATTRS = frozenset(['is_volume_muted'])
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -205,6 +205,8 @@ FIXED = (
('chevron-left', 'F0141'), ('chevron-left', 'F0141'),
('chevron-right', 'F0142'), ('chevron-right', 'F0142'),
('close', 'F0156'), ('close', 'F0156'),
# Back button of every overlay (firmware 0.2.37+).
('arrow-left', 'F004D'),
('sun-snowflake-variant', 'F1A79'), ('sun-snowflake-variant', 'F1A79'),
('thermostat-auto', 'F1B17'), ('thermostat-auto', 'F1B17'),
('weather-fog', 'F0591'), ('weather-fog', 'F0591'),
+1 -1
View File
@@ -1,5 +1,5 @@
name: ESP Screen Manager name: ESP Screen Manager
version: "0.2.42" version: "0.2.43"
slug: esp_screen_manager slug: esp_screen_manager
description: Choose tiles for your CYD and Guition touchscreen, without reflashing. description: Choose tiles for your CYD and Guition touchscreen, without reflashing.
url: https://github.com/MaxGramser/homeassistant_espscreen url: https://github.com/MaxGramser/homeassistant_espscreen
+13
View File
@@ -222,6 +222,19 @@ class ParityTests(unittest.TestCase):
self.assertIn('render_header();', TILES.split('inline void render(lv_obj_t *room) {', 1)[1].split('\n', 5)[4]) self.assertIn('render_header();', TILES.split('inline void render(lv_obj_t *room) {', 1)[1].split('\n', 5)[4])
self.assertLessEqual(HEADER_MIN_FIRMWARE, tuple(int(p) for p in FIRMWARE_VERSION.split('.'))) self.assertLessEqual(HEADER_MIN_FIRMWARE, tuple(int(p) for p in FIRMWARE_VERSION.split('.')))
def test_second_hand_runs_only_while_the_screen_is_awake(self):
for name in PROFILES:
text = (ROOT / name).read_text()
self.assertIn('runtime_tiles::screen_awake = []() { return !id(display_dimmed); };', text, name)
for name in ('packages/cyd.yaml', 'packages/guition.yaml'):
self.assertIn('runtime_tiles::screen_awake', (ROOT / name).read_text(), name)
self.assertIn('inline bool awake() { return !screen_awake || screen_awake(); }', TILES)
second_hand = TILES.split('inline void second_hand(', 1)[1].split('\n}', 1)[0]
self.assertIn('set_hidden(p,!(awake() && now.is_valid()));', second_hand)
self.assertIn('part_line(w,18,w.points+28,2,w.hand_width)', second_hand)
tick = TILES.split('inline void tick() {', 1)[1]
self.assertIn('t.builtin() && t.display=="analog")second_hand(w,now);', tick)
HAS_AIOHTTP = importlib.util.find_spec('aiohttp') is not None HAS_AIOHTTP = importlib.util.find_spec('aiohttp') is not None
if HAS_AIOHTTP: if HAS_AIOHTTP:
from aiohttp.test_utils import TestClient, TestServer from aiohttp.test_utils import TestClient, TestServer