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