Files
esphome-guition-jc3636k518c…/base/watchfaces/minecraft.yaml
T
2026-07-19 16:49:55 +08:00

192 lines
11 KiB
YAML

# ═════════════════════════════════════════════════════════════════════════════
# 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