First commit
This commit is contained in:
commit
61c37de884
171 files changed
+9201
No files matched your search
@@ -0,0 +1,118 @@
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# DEMO WATCHFACE - a minimal, heavily-commented template. Copy this file to
|
||||
# base/watchfaces/<your-name>.yaml, change the 4 spots marked >>> EDIT <<< and
|
||||
# you have your own home-screen look.
|
||||
#
|
||||
# How watchfaces work (same idea as the carousel screens):
|
||||
# - The home base (the clock area) can show one of several "faces".
|
||||
# - "Classic" is built into core and is always the fallback (face id 0).
|
||||
# - Every OTHER face is an optional file like this one, switched on by adding it
|
||||
# to the `files:` list in your thin config, and chosen at runtime in
|
||||
# Settings > Home > Watchface (turn the knob to preview, tap to keep).
|
||||
# - Core can't reference a page that might not be compiled in, so each face
|
||||
# SHOWS ITSELF: core just raises a flag (g_wf_req) and the active face's own
|
||||
# interval calls lvgl.page.show. Same trick the screens use (g_nav_req).
|
||||
#
|
||||
# Everything you can read from core (no extra entities needed):
|
||||
# id(ha_time).now() - current time (ESPTime) -> strftime("%H"), etc.
|
||||
# id(wx_temp).state - outdoor temperature (float) + .has_state()
|
||||
# id(room_temp).state - room temperature (float)
|
||||
# id(room_hum).state - room humidity (float)
|
||||
# id(g_nf_glyph) - weather icon glyph (std::string, MDI in font icons34)
|
||||
# id(g_nf_cond) - weather condition text, UPPERCASE
|
||||
# id(g_batt) / id(g_charging) - battery % (int, -1 = unknown) / charging (bool)
|
||||
# id(g_show_weather) / id(g_show_climate) - the Display toggles (bool)
|
||||
# Fonts already provided by core: montserrat_18/48, font_med, font_artist,
|
||||
# font_sel, icons18, icons34. Define your own under `font:` if you want more.
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ── 1. REGISTER the face at boot ──────────────────────────────────────────────
|
||||
# Pick a UNIQUE id (0 = Classic, 1 = Neon are taken; use 2, 3, ...). The name is
|
||||
# what shows in the picker. Priority 655 runs after core but before the boot
|
||||
# finalize that validates the saved choice.
|
||||
esphome:
|
||||
on_boot:
|
||||
- priority: 655
|
||||
then:
|
||||
- lambda: 'id(g_wf_present)[2] = true; id(g_wf_name)[2] = "Demo";' # >>> EDIT <<< id + name
|
||||
|
||||
# ── 2. (optional) your own fonts ──────────────────────────────────────────────
|
||||
# This demo reuses core's fonts, so nothing here. Add a `font:` block if needed.
|
||||
|
||||
# ── 3. THE PAGE ───────────────────────────────────────────────────────────────
|
||||
# A full-screen page. Give it a unique id (page_home_<name>) and unique widget ids.
|
||||
lvgl:
|
||||
pages:
|
||||
- id: page_home_demo # >>> EDIT <<< unique page id
|
||||
bg_color: 0x000000
|
||||
widgets:
|
||||
- label: # big centered clock
|
||||
id: dwf_time
|
||||
align: CENTER
|
||||
y: -16
|
||||
text: "00:00"
|
||||
text_font: montserrat_48
|
||||
text_color: 0xFFFFFF
|
||||
- label: # small date under it
|
||||
id: dwf_date
|
||||
align: CENTER
|
||||
y: 30
|
||||
text: ""
|
||||
text_font: font_med
|
||||
text_color: 0x6f9a92
|
||||
|
||||
# ── 4. INTERVALS: self-show + refresh ─────────────────────────────────────────
|
||||
interval:
|
||||
# Self-show: core raises g_wf_req when it wants the home base shown. If we are
|
||||
# the active face, show OUR page (and render once so it isn't blank), then clear
|
||||
# the flag. Keep the id check (== 2) matching the one you registered above.
|
||||
- interval: 50ms
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_wf_req) && id(g_watchface) == 2;' } # >>> EDIT <<< id
|
||||
then:
|
||||
- lambda: 'id(g_wf_req) = false;'
|
||||
# slide in matching the carousel direction (like Classic / the screens) - no laggy fade
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==0;' }, then: [lvgl.page.show: { id: page_home_demo, animation: move_left, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==1;' }, then: [lvgl.page.show: { id: page_home_demo, animation: move_right, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==2;' }, then: [lvgl.page.show: { id: page_home_demo, animation: move_top, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==3;' }, then: [lvgl.page.show: { id: page_home_demo, animation: move_bottom, time: 250ms }] }
|
||||
- script.execute: dwf_render
|
||||
# Refresh once a second while we are the active home face.
|
||||
- interval: 1s
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_watchface) == 2 && id(g_base) == 0;' } # >>> EDIT <<< id
|
||||
then: [ script.execute: dwf_render ]
|
||||
|
||||
# ── 5. RENDER: fill your widgets from core's data ─────────────────────────────
|
||||
script:
|
||||
- id: dwf_render # >>> EDIT <<< unique script id
|
||||
then:
|
||||
# Time - HH:MM (24h). Use "%I:%M %p" for 12-hour.
|
||||
- lvgl.label.update:
|
||||
id: dwf_time
|
||||
text: !lambda 'return id(ha_time).now().is_valid() ? id(ha_time).now().strftime("%H:%M") : std::string("00:00");'
|
||||
# Date - e.g. "Mon 19 Jun"
|
||||
- lvgl.label.update:
|
||||
id: dwf_date
|
||||
text: !lambda |-
|
||||
auto t = id(ha_time).now();
|
||||
if (!t.is_valid()) return std::string("");
|
||||
static const char* d[8] = {"","Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
||||
std::string day = (t.day_of_week >= 1 && t.day_of_week <= 7) ? std::string(d[t.day_of_week]) : std::string("");
|
||||
return day + t.strftime(" %d %b");
|
||||
# ── Want more? Uncomment and add widgets above, then fill them here. ──
|
||||
# Example: show room temperature (respecting the "Show climate" toggle)
|
||||
# - lvgl.label.update:
|
||||
# id: dwf_temp
|
||||
# text: !lambda |-
|
||||
# if (!id(g_show_climate) || !id(room_temp).has_state()) return std::string("");
|
||||
# char b[12]; snprintf(b, sizeof(b), "%.1f\xC2\xB0", id(room_temp).state); return std::string(b);
|
||||
# Example: weather icon (font icons34) + outdoor temp
|
||||
# - lvgl.label.update: { id: dwf_wx, text: !lambda 'return id(g_show_weather) ? id(g_nf_glyph) : std::string("");' }
|
||||
|
||||
# ── 6. TURN IT ON ─────────────────────────────────────────────────────────────
|
||||
# Add this file to the `files:` list in your thin config:
|
||||
# - base/watchfaces/demo.yaml
|
||||
# then rebuild and pick "Demo" in Settings > Home > Watchface.
|
||||
@@ -0,0 +1,191 @@
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# MINECRAFT watchface (optional, id 3). A blocky scene with a day/night cycle:
|
||||
# sky + textured grass/dirt ground, the sun by day / moon + stars by night, pixel
|
||||
# flowers on the grass, a blocky pixel font for the time (classic drop shadow),
|
||||
# outdoor temp, and a Minecraft heart bar (HUD) for the battery.
|
||||
#
|
||||
# PERFORMANCE: the per-second tick is just a tiny lambda; the heavy redraws (the big
|
||||
# pixel digits, the day/night swap) only run when the minute actually changes or the
|
||||
# day/night state flips - so it stays light. No animated clouds.
|
||||
#
|
||||
# Assets (font + sprites) are fetched from this repo's /main/ raw GitHub URLs at compile time.
|
||||
# Watchface contract: see base/watchfaces/demo.yaml.
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- priority: 655
|
||||
then:
|
||||
- lambda: 'id(g_wf_present)[3] = true; id(g_wf_name)[3] = "Minecraft";'
|
||||
|
||||
globals:
|
||||
- { id: g_mc_last_min, type: int, restore_value: no, initial_value: '-1' }
|
||||
- { id: g_mc_last_night, type: int, restore_value: no, initial_value: '-1' }
|
||||
- { id: g_mc_upd_min, type: bool, restore_value: no, initial_value: 'false' }
|
||||
- { id: g_mc_upd_night, type: bool, restore_value: no, initial_value: 'false' }
|
||||
|
||||
font:
|
||||
- file: https://raw.githubusercontent.com/MichalZaniewicz/esphome-guition-jc3636k718c-va/main/assets/fonts/pixel-font.ttf
|
||||
id: mc_font_big
|
||||
size: 78
|
||||
glyphs: "0123456789:"
|
||||
- file: https://raw.githubusercontent.com/MichalZaniewicz/esphome-guition-jc3636k718c-va/main/assets/fonts/pixel-font.ttf
|
||||
id: mc_font_sm
|
||||
size: 26
|
||||
glyphs: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" # date only (uppercased) - no GF_Latin_Core, avoids missing-glyph warnings
|
||||
|
||||
image:
|
||||
- { id: img_mc_ground, file: https://raw.githubusercontent.com/MichalZaniewicz/esphome-guition-jc3636k718c-va/main/assets/sprites/minecraft/ground.png, type: RGB565 }
|
||||
- { id: img_mc_sun, file: https://raw.githubusercontent.com/MichalZaniewicz/esphome-guition-jc3636k718c-va/main/assets/sprites/minecraft/sun.png, type: RGB565, transparency: alpha_channel }
|
||||
- { id: img_mc_moon, file: https://raw.githubusercontent.com/MichalZaniewicz/esphome-guition-jc3636k718c-va/main/assets/sprites/minecraft/moon.png, type: RGB565, transparency: alpha_channel }
|
||||
- { id: img_mc_flower, file: https://raw.githubusercontent.com/MichalZaniewicz/esphome-guition-jc3636k718c-va/main/assets/sprites/minecraft/flower.png, type: RGB565, transparency: alpha_channel }
|
||||
|
||||
lvgl:
|
||||
pages:
|
||||
- id: page_home_mc
|
||||
bg_color: 0x000000
|
||||
widgets:
|
||||
# sky (recoloured day/night) - full screen, behind everything
|
||||
- obj: { id: mc_sky, x: 0, y: 0, width: 360, height: 360, bg_color: 0x5A9BD4, bg_opa: COVER, radius: 0, border_width: 0, clickable: false, scrollable: false }
|
||||
# stars (night only)
|
||||
- obj: { id: mc_star1, x: 44, y: 64, width: 6, height: 6, bg_color: 0xEAECF5, bg_opa: COVER, radius: 0, border_width: 0, clickable: false, scrollable: false, hidden: true }
|
||||
- obj: { id: mc_star2, x: 120, y: 42, width: 6, height: 6, bg_color: 0xEAECF5, bg_opa: COVER, radius: 0, border_width: 0, clickable: false, scrollable: false, hidden: true }
|
||||
- obj: { id: mc_star3, x: 250, y: 48, width: 6, height: 6, bg_color: 0xEAECF5, bg_opa: COVER, radius: 0, border_width: 0, clickable: false, scrollable: false, hidden: true }
|
||||
- obj: { id: mc_star4, x: 70, y: 116, width: 5, height: 5, bg_color: 0xC9D0E4, bg_opa: COVER, radius: 0, border_width: 0, clickable: false, scrollable: false, hidden: true }
|
||||
- obj: { id: mc_star5, x: 300, y: 120, width: 5, height: 5, bg_color: 0xC9D0E4, bg_opa: COVER, radius: 0, border_width: 0, clickable: false, scrollable: false, hidden: true }
|
||||
# textured ground (flush to the bottom edge)
|
||||
- image: { id: mc_ground, src: img_mc_ground, align: BOTTOM_MID, y: 0 }
|
||||
# pixel flowers standing on the grass
|
||||
- image: { id: mc_flower1, src: img_mc_flower, x: 56, y: 200 }
|
||||
- image: { id: mc_flower2, src: img_mc_flower, x: 282, y: 206 }
|
||||
# sun by day / moon by night (top-right)
|
||||
- image: { id: mc_sun, src: img_mc_sun, align: TOP_RIGHT, x: -38, y: 38 }
|
||||
- image: { id: mc_moon, src: img_mc_moon, align: TOP_RIGHT, x: -38, y: 38, hidden: true }
|
||||
- label: { id: mc_sun_temp, align: TOP_RIGHT, x: -52, y: 100, text: "--°", text_font: font_artist, text_color: 0x16314A }
|
||||
# time: dark shadow behind + white in front (the Minecraft look)
|
||||
- label: { id: mc_time_sh, align: CENTER, x: 3, y: -41, text: "00:00", text_font: mc_font_big, text_color: 0x1A2A40 }
|
||||
- label: { id: mc_time, align: CENTER, x: 0, y: -44, text: "00:00", text_font: mc_font_big, text_color: 0xFFFFFF }
|
||||
- label: { id: mc_date, align: CENTER, x: 0, y: 14, text: "", text_font: mc_font_sm, text_color: 0xEAF2FB }
|
||||
# battery as a Minecraft heart bar (HUD), low: 5 dark "empty" slots + red fill on top
|
||||
- label: { id: mc_hearts_bg, align: BOTTOM_LEFT, x: 128, y: -16, text: "", text_font: icons18, text_color: 0x3A1614 }
|
||||
- label: { id: mc_hearts, align: BOTTOM_LEFT, x: 128, y: -16, text: "", text_font: icons18, text_color: 0xE2453F }
|
||||
# timer badge (active timer - minutes left)
|
||||
- label:
|
||||
id: mc_timer
|
||||
align: TOP_MID
|
||||
y: 6
|
||||
text: ""
|
||||
text_font: montserrat_18
|
||||
text_color: 0xFFFFFF
|
||||
bg_color: 0xFF3B30
|
||||
bg_opa: COVER
|
||||
radius: 12
|
||||
pad_left: 11
|
||||
pad_right: 11
|
||||
pad_top: 4
|
||||
pad_bottom: 4
|
||||
hidden: true
|
||||
|
||||
interval:
|
||||
- interval: 50ms # self-show when core requests the home base
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_wf_req) && id(g_watchface) == 3;' }
|
||||
then:
|
||||
- lambda: 'id(g_wf_req) = false; id(g_mc_last_min) = -1; id(g_mc_last_night) = -1;' # force a full redraw on entry
|
||||
# slide in matching the carousel direction (like Classic / the screens) - no laggy fade
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==0;' }, then: [lvgl.page.show: { id: page_home_mc, animation: move_left, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==1;' }, then: [lvgl.page.show: { id: page_home_mc, animation: move_right, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==2;' }, then: [lvgl.page.show: { id: page_home_mc, animation: move_top, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==3;' }, then: [lvgl.page.show: { id: page_home_mc, animation: move_bottom, time: 250ms }] }
|
||||
- script.execute: mc_render
|
||||
- interval: 1s # cheap tick - only redraws on a real change
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_watchface) == 3 && id(g_base) == 0;' }
|
||||
then: [ script.execute: mc_render ]
|
||||
|
||||
script:
|
||||
- id: mc_render
|
||||
then:
|
||||
# Decide what actually changed (so the second-by-second tick is nearly free)
|
||||
- lambda: |-
|
||||
id(g_mc_upd_min) = false; id(g_mc_upd_night) = false;
|
||||
auto t = id(ha_time).now();
|
||||
if (!t.is_valid()) return;
|
||||
int mn = t.hour * 60 + t.minute;
|
||||
if (mn != id(g_mc_last_min)) { id(g_mc_last_min) = mn; id(g_mc_upd_min) = true; }
|
||||
int night = (t.hour < 6 || t.hour >= 20) ? 1 : 0;
|
||||
if (night != id(g_mc_last_night)) { id(g_mc_last_night) = night; id(g_mc_upd_night) = true; }
|
||||
# Minute changed -> redraw time / date / temp / hearts
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_mc_upd_min);' }
|
||||
then:
|
||||
- lvgl.label.update: { id: mc_time, text: !lambda 'return id(ha_time).now().strftime("%H:%M");' }
|
||||
- lvgl.label.update: { id: mc_time_sh, text: !lambda 'return id(ha_time).now().strftime("%H:%M");' }
|
||||
- lvgl.label.update:
|
||||
id: mc_date
|
||||
text: !lambda |-
|
||||
auto t = id(ha_time).now();
|
||||
static const char* d[8] = {"","SUN","MON","TUE","WED","THU","FRI","SAT"};
|
||||
std::string day = (t.day_of_week >= 1 && t.day_of_week <= 7) ? std::string(d[t.day_of_week]) : std::string("");
|
||||
std::string s = day + t.strftime(" %d %b");
|
||||
for (auto &c : s) { if (c >= 'a' && c <= 'z') c -= 32; }
|
||||
return s;
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_show_weather);' }
|
||||
then:
|
||||
- lvgl.widget.show: mc_sun_temp
|
||||
- lvgl.label.update:
|
||||
id: mc_sun_temp
|
||||
text: !lambda |-
|
||||
if (!id(wx_temp).has_state()) return std::string("--°");
|
||||
char b[10]; snprintf(b, sizeof(b), "%d°", (int) roundf(id(wx_temp).state)); return std::string(b);
|
||||
else: [ lvgl.widget.hide: mc_sun_temp ]
|
||||
- lvgl.label.update:
|
||||
id: mc_hearts
|
||||
text: !lambda |-
|
||||
int p = id(g_batt);
|
||||
int n = (p < 0) ? 5 : (p + 10) / 20; if (n < 0) n = 0; if (n > 5) n = 5;
|
||||
std::string s; for (int i = 0; i < n; i++) s += "";
|
||||
return s;
|
||||
# Day/night flipped -> swap sky tint, sun<->moon, stars, temp colour
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_mc_upd_night);' }
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_mc_last_night) == 1;' }
|
||||
then:
|
||||
- lvgl.widget.update: { id: mc_sky, bg_color: 0x101A33 }
|
||||
- lvgl.widget.hide: mc_sun
|
||||
- lvgl.widget.show: mc_moon
|
||||
- lvgl.widget.show: mc_star1
|
||||
- lvgl.widget.show: mc_star2
|
||||
- lvgl.widget.show: mc_star3
|
||||
- lvgl.widget.show: mc_star4
|
||||
- lvgl.widget.show: mc_star5
|
||||
- lvgl.label.update: { id: mc_sun_temp, text_color: 0xC8D2E2 }
|
||||
else:
|
||||
- lvgl.widget.update: { id: mc_sky, bg_color: 0x5A9BD4 }
|
||||
- lvgl.widget.show: mc_sun
|
||||
- lvgl.widget.hide: mc_moon
|
||||
- lvgl.widget.hide: mc_star1
|
||||
- lvgl.widget.hide: mc_star2
|
||||
- lvgl.widget.hide: mc_star3
|
||||
- lvgl.widget.hide: mc_star4
|
||||
- lvgl.widget.hide: mc_star5
|
||||
- lvgl.label.update: { id: mc_sun_temp, text_color: 0x16314A }
|
||||
# Timer badge (active timer - minutes left), kept responsive (outside the minute gate)
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_timer_active);' }
|
||||
then:
|
||||
- lvgl.widget.show: mc_timer
|
||||
- lvgl.label.update:
|
||||
id: mc_timer
|
||||
text: !lambda |-
|
||||
int s = id(g_timer_secs); if (s < 0) s = 0;
|
||||
int m = (s + 59) / 60;
|
||||
char b[24]; snprintf(b, sizeof(b), " %d min", m);
|
||||
return std::string(b);
|
||||
else:
|
||||
- lvgl.widget.hide: mc_timer
|
||||
@@ -0,0 +1,245 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# NEON watchface (optional home face). Add to the thin config's files: list to
|
||||
# enable; the picker (Settings > Home > Watchface) then offers it. Watchface id 1.
|
||||
#
|
||||
# A full-screen home page with huge two-tone digits, neon rim rings, weather +
|
||||
# room/battery stats. It reuses core's clock/weather/room/battery sensors and the
|
||||
# g_show_weather / g_show_climate display toggles - no extra entities needed.
|
||||
#
|
||||
# Contract (mirrors the screen carousel): self-registers in the watchface registry
|
||||
# at boot, renders itself while active, and self-shows its page when core requests
|
||||
# the home base (core can't reference an optional page id, hence the g_wf_req flag).
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- priority: 655
|
||||
then:
|
||||
- lambda: 'id(g_wf_present)[1] = true; id(g_wf_name)[1] = "Neon";'
|
||||
|
||||
globals:
|
||||
- { id: g_nf_last_min, type: int, restore_value: no, initial_value: '-1' } # perf: only redraw on minute change
|
||||
- { id: g_nf_upd_min, type: bool, restore_value: no, initial_value: 'false' }
|
||||
|
||||
font:
|
||||
# Huge bold digits - only 0-9 needed, so it stays tiny in flash
|
||||
- file: { type: gfonts, family: "Roboto", weight: 900 }
|
||||
id: font_neon
|
||||
size: 108
|
||||
glyphs: "0123456789"
|
||||
|
||||
lvgl:
|
||||
pages:
|
||||
- id: page_home_neon
|
||||
bg_color: 0x000000
|
||||
widgets:
|
||||
- obj: # outer neon ring at the rim
|
||||
id: nf_ring_out
|
||||
align: CENTER
|
||||
width: 352
|
||||
height: 352
|
||||
radius: 176
|
||||
bg_opa: TRANSP
|
||||
border_width: 4
|
||||
border_color: 0xFF2E88
|
||||
border_opa: 60%
|
||||
clickable: false
|
||||
scrollable: false
|
||||
- obj: # second neon ring, also hugging the rim (interior left free for content)
|
||||
id: nf_ring_in
|
||||
align: CENTER
|
||||
width: 320
|
||||
height: 320
|
||||
radius: 160
|
||||
bg_opa: TRANSP
|
||||
border_width: 4
|
||||
border_color: 0x9A5BFF
|
||||
border_opa: 55%
|
||||
clickable: false
|
||||
scrollable: false
|
||||
- label: # weather icon
|
||||
id: nf_wx_ic
|
||||
align: CENTER
|
||||
x: -34
|
||||
y: -103
|
||||
text: ""
|
||||
text_font: icons34
|
||||
text_color: 0xBFD2DA
|
||||
- label: # outdoor temperature
|
||||
id: nf_wx_temp
|
||||
align: CENTER
|
||||
x: 26
|
||||
y: -106
|
||||
text: "--°"
|
||||
text_font: font_sel
|
||||
text_color: 0xFFFFFF
|
||||
- label: # condition text (UPPERCASE)
|
||||
id: nf_wx_cond
|
||||
align: CENTER
|
||||
y: -76
|
||||
text: ""
|
||||
text_font: font_artist
|
||||
text_color: 0x8A93A0
|
||||
- label: # HOURS
|
||||
id: nf_hh
|
||||
align: CENTER
|
||||
x: -70
|
||||
y: -4
|
||||
text: "00"
|
||||
text_font: font_neon
|
||||
text_color: 0xFF2E88
|
||||
- label: # MINUTES
|
||||
id: nf_mm
|
||||
align: CENTER
|
||||
x: 70
|
||||
y: -4
|
||||
text: "00"
|
||||
text_font: font_neon
|
||||
text_color: 0x9A5BFF
|
||||
- label: # date
|
||||
id: nf_date
|
||||
align: CENTER
|
||||
y: 70
|
||||
text: ""
|
||||
text_font: font_med
|
||||
text_color: 0x6f9a92
|
||||
- label: # room temperature icon
|
||||
id: nf_ct_ic
|
||||
align: CENTER
|
||||
x: -60
|
||||
y: 99
|
||||
text: ""
|
||||
text_font: icons18
|
||||
text_color: 0xFF8A5C
|
||||
- label:
|
||||
id: nf_ct_val
|
||||
align: CENTER
|
||||
x: -32
|
||||
y: 99
|
||||
text: "--"
|
||||
text_font: font_artist
|
||||
text_color: 0x9AA3B0
|
||||
- label: # room humidity icon
|
||||
id: nf_hum_ic
|
||||
align: CENTER
|
||||
x: 16
|
||||
y: 99
|
||||
text: ""
|
||||
text_font: icons18
|
||||
text_color: 0x3FA9D6
|
||||
- label:
|
||||
id: nf_hum_val
|
||||
align: CENTER
|
||||
x: 42
|
||||
y: 99
|
||||
text: "--"
|
||||
text_font: font_artist
|
||||
text_color: 0x9AA3B0
|
||||
- label: # battery (glyph + %), mirrored from core's lbl_batt (same LVGL symbols)
|
||||
id: nf_bt
|
||||
align: CENTER
|
||||
y: 126
|
||||
text: ""
|
||||
text_font: montserrat_18
|
||||
text_color: 0x6f9a92
|
||||
- label: # timer badge (active timer - mins left), mirrors the classic face
|
||||
id: nf_timer
|
||||
align: TOP_MID
|
||||
y: 22
|
||||
text: ""
|
||||
text_font: montserrat_18
|
||||
text_color: 0xFFFFFF
|
||||
bg_color: 0xFF3B30
|
||||
bg_opa: COVER
|
||||
radius: 14
|
||||
pad_left: 12
|
||||
pad_right: 12
|
||||
pad_top: 5
|
||||
pad_bottom: 5
|
||||
hidden: true
|
||||
|
||||
interval:
|
||||
# Self-show: core sets g_wf_req when it wants the home base shown; we own page_home_neon.
|
||||
- interval: 50ms
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_wf_req) && id(g_watchface) == 1;' }
|
||||
then:
|
||||
- lambda: 'id(g_wf_req) = false; id(g_nf_last_min) = -1;' # force a full redraw on entry
|
||||
# slide in matching the carousel direction (like Classic / the screens) - no laggy fade
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==0;' }, then: [lvgl.page.show: { id: page_home_neon, animation: move_left, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==1;' }, then: [lvgl.page.show: { id: page_home_neon, animation: move_right, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==2;' }, then: [lvgl.page.show: { id: page_home_neon, animation: move_top, time: 250ms }] }
|
||||
- if: { condition: { lambda: 'return id(g_nav_anim)==3;' }, then: [lvgl.page.show: { id: page_home_neon, animation: move_bottom, time: 250ms }] }
|
||||
- script.execute: neon_render
|
||||
# Refresh while this is the active home face
|
||||
- interval: 1s
|
||||
then:
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_watchface) == 1 && id(g_base) == 0;' }
|
||||
then: [ script.execute: neon_render ]
|
||||
|
||||
script:
|
||||
- id: neon_render
|
||||
then:
|
||||
# Cheap tick: figure out if the minute actually changed (heavy redraws only then)
|
||||
- lambda: |-
|
||||
id(g_nf_upd_min) = false;
|
||||
auto t = id(ha_time).now();
|
||||
if (!t.is_valid()) return;
|
||||
int mn = t.hour * 60 + t.minute;
|
||||
if (mn != id(g_nf_last_min)) { id(g_nf_last_min) = mn; id(g_nf_upd_min) = true; }
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_nf_upd_min);' }
|
||||
then:
|
||||
- lvgl.label.update: { id: nf_hh, text: !lambda 'return id(ha_time).now().strftime("%H");' }
|
||||
- lvgl.label.update: { id: nf_mm, text: !lambda 'return id(ha_time).now().strftime("%M");' }
|
||||
- lvgl.label.update: { id: nf_wx_ic, text: !lambda 'return id(g_nf_glyph).empty() ? std::string("") : id(g_nf_glyph);' }
|
||||
- lvgl.label.update: { id: nf_wx_cond, text: !lambda 'return id(g_nf_cond);' }
|
||||
- lvgl.label.update:
|
||||
id: nf_wx_temp
|
||||
text: !lambda |-
|
||||
if (!id(wx_temp).has_state()) return std::string("--°");
|
||||
char b[10]; snprintf(b, sizeof(b), "%d°", (int) roundf(id(wx_temp).state)); return std::string(b);
|
||||
- lvgl.label.update:
|
||||
id: nf_date
|
||||
text: !lambda |-
|
||||
auto t = id(ha_time).now();
|
||||
static const char* d[8] = {"","Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
||||
std::string day = (t.day_of_week >= 1 && t.day_of_week <= 7) ? std::string(d[t.day_of_week]) : std::string("");
|
||||
return t.strftime("%d %b") + " " + day;
|
||||
- lvgl.label.update:
|
||||
id: nf_ct_val
|
||||
text: !lambda 'if (!id(room_temp).has_state()) return std::string("--"); char b[12]; snprintf(b, sizeof(b), "%.1f°", id(room_temp).state); return std::string(b);'
|
||||
- lvgl.label.update:
|
||||
id: nf_hum_val
|
||||
text: !lambda 'if (!id(room_hum).has_state()) return std::string("--"); char b[8]; snprintf(b, sizeof(b), "%d%%", (int) roundf(id(room_hum).state)); return std::string(b);'
|
||||
# Battery: mirror core's lbl_batt text + colour
|
||||
- lambda: 'lv_label_set_text(id(nf_bt), lv_label_get_text(id(lbl_batt)));'
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_charging);' }
|
||||
then: [ lvgl.label.update: { id: nf_bt, text_color: 0x35c759 } ]
|
||||
else: [ lvgl.label.update: { id: nf_bt, text_color: 0x6f9a92 } ]
|
||||
# Weather / climate visibility obey the Display toggles
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_show_weather);' }
|
||||
then: [ lvgl.widget.show: nf_wx_ic, lvgl.widget.show: nf_wx_temp, lvgl.widget.show: nf_wx_cond ]
|
||||
else: [ lvgl.widget.hide: nf_wx_ic, lvgl.widget.hide: nf_wx_temp, lvgl.widget.hide: nf_wx_cond ]
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_show_climate);' }
|
||||
then: [ lvgl.widget.show: nf_ct_ic, lvgl.widget.show: nf_ct_val, lvgl.widget.show: nf_hum_ic, lvgl.widget.show: nf_hum_val ]
|
||||
else: [ lvgl.widget.hide: nf_ct_ic, lvgl.widget.hide: nf_ct_val, lvgl.widget.hide: nf_hum_ic, lvgl.widget.hide: nf_hum_val ]
|
||||
# Timer badge - kept responsive (cheap) so it appears/updates promptly
|
||||
- if:
|
||||
condition: { lambda: 'return id(g_timer_active);' }
|
||||
then:
|
||||
- lvgl.widget.show: nf_timer
|
||||
- lvgl.label.update:
|
||||
id: nf_timer
|
||||
text: !lambda |-
|
||||
int s = id(g_timer_secs); if (s < 0) s = 0;
|
||||
int m = (s + 59) / 60;
|
||||
char b[24]; snprintf(b, sizeof(b), " %d min", m);
|
||||
return std::string(b);
|
||||
else:
|
||||
- lvgl.widget.hide: nf_timer
|
||||
Reference in new issue
Block a user