diff --git a/esphome/epaperframe.yaml b/esphome/epaperframe.yaml index 2ebf8f9..d90e40c 100644 --- a/esphome/epaperframe.yaml +++ b/esphome/epaperframe.yaml @@ -1,7 +1,10 @@ +# Device identity shared by ESPHome and Home Assistant. substitutions: name: epaperframe friendly_name: "Info-Dashboard" +# Startup selects the default page only after Home Assistant has had time to +# provide the sensor states needed by its display lambda. esphome: name: ${name} friendly_name: ${friendly_name} @@ -18,13 +21,15 @@ esphome: - display.page.show: calendar # temporary for power page dev - component.update: epaper +# ESP32 target and ESP-IDF build settings for the hardware revision in use. esp32: board: nodemcu-32s framework: type: esp-idf - advanced: + advanced: minimum_chip_revision: "3.0" +# Network access is required for Home Assistant state delivery and OTA updates. wifi: ssid: "Voltage-legacy" password: !secret voltage_legacy_psk @@ -124,6 +129,7 @@ button: - display.page.show_next: epaper - component.update: epaper +# Numeric telemetry received from Home Assistant for the environment dashboard. sensor: # WiFi signal strength - platform: wifi_signal @@ -201,6 +207,7 @@ sensor: entity_id: sensor.radioaktive_strahlungsleistung id: radiation +# Textual Home Assistant state, including the compact calendar payload and quote. text_sensor: - platform: homeassistant name: "Sun Rising ESP" @@ -229,6 +236,8 @@ text_sensor: entity_id: sensor.epaper_kalenderdaten attribute: entries id: calendar_entries + # Refresh the e-paper only for changed agenda data while the calendar page + # is visible; Home Assistant may otherwise republish identical JSON. on_value: then: - if: @@ -248,6 +257,8 @@ text_sensor: +# Time drives panel power maintenance, the environment refresh cadence, and the +# midnight redraw that advances the calendar and daily quote. time: - platform: homeassistant id: homeassistant_time @@ -283,6 +294,7 @@ time: format: "Update screen on new day" - component.update: epaper +# Visual assets used by both 1-bit e-paper pages. # Font definitions font: - file: "fonts/Roboto-Medium.ttf" @@ -743,6 +755,7 @@ font: "󰀦", # mdi-alert ] +# Binary logo stored in firmware for rendering without network access. image: - platform: file file: "epaperframe/C1024_logo_flat_bw.png" @@ -751,10 +764,12 @@ image: resize: 77x40 invert_alpha: true +# Shared SPI bus for the Waveshare e-paper panel. spi: clk_pin: 23 mosi_pin: 22 +# The two pages draw directly onto the 640x384 1-bit Waveshare e-paper panel. display: - platform: waveshare_epaper id: epaper @@ -766,8 +781,11 @@ display: # full_update_every: 30 # not supported on this display update_interval: never pages: + # Environmental telemetry dashboard, refreshed on the 10-minute timer. - id: environment lambda: |- + // Snapshot all Home Assistant values once per render so each panel uses + // a consistent set of readings throughout this e-paper refresh. /* Outdoor */ float outdoor_temp = id(temp_outdoor).state; int outdoor_humid = int(id(humid_outdoor).state); @@ -791,10 +809,11 @@ display: float childrensroom_co2 = id(co2_childrensroom).state; int childrensroom_pm25 = int(id(pm25_childrensroom).state); + // Reuse one room widget layout by moving its origin across the two-column grid. int offsetX = 0; int offsetY = 0; - /* OUTSIDE */ + /* OUTSIDE: logo, headline, and outdoor sensor summary */ it.print(10, 10, id(sensor_unit), "Umwelt"); /* Draw the outline of a rectangle with the top left at [86,21], @@ -1138,8 +1157,11 @@ display: } } + # Calendar agenda, month grid, and daily quote; redraws when event JSON changes. - id: calendar lambda: |- + // Use Home Assistant time for all date labels, month calculations, and + // the footer, avoiding locale-dependent formatting on the ESP32. auto now = id(homeassistant_time).now(); const char *months[] = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}; @@ -1149,6 +1171,8 @@ display: const int month = now.month; const int today = now.day_of_month; + // Date helpers build a Monday-first month grid and ISO-8601 week numbers + // without relying on platform-specific C library time functions. auto days_in_month = [](int calendar_year, int calendar_month) { const int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (calendar_month == 2) { @@ -1188,6 +1212,8 @@ display: it.line(10, 55, 355, 55); it.line(365, 10, 365, 352); + // Parse the compact JSON attribute produced by Home Assistant. The empty + // and error states below ensure a useful screen while data is unavailable. JsonDocument calendar_doc; const DeserializationError calendar_error = deserializeJson(calendar_doc, id(calendar_entries).state.c_str()); JsonArray calendar_days = calendar_doc.as(); @@ -1209,6 +1235,8 @@ display: break; } + // A date rail occupies the left edge; event rows start alongside it + // and stop before the footer rather than overflowing the panel. const int group_y = agenda_y; int event_y = group_y; const int day_number = day["day"] | 0; @@ -1250,6 +1278,8 @@ display: /* RIGHT TOP: month view, Monday-first with ISO week numbers */ + // Draw only the five or six rows this month actually needs so no orphan + // week number is rendered beneath shorter months. char month_title[24]; snprintf(month_title, sizeof(month_title), "%s %d", months[month - 1], year); it.printf(502, 18, id(sensor_unit), TextAlign::TOP_CENTER, "%s", month_title); @@ -1298,6 +1328,7 @@ display: } /* RIGHT BOTTOM: daily quote selected by Home Assistant */ + // Wrap on word boundaries into the fixed four-line, inverted quote panel. it.filled_rectangle(375, 220, 255, 130, COLOR_ON); std::string quote = "Zitat wird geladen."; if (id(demotivational_quote).has_state() && !id(demotivational_quote).state.empty()) { @@ -1319,7 +1350,7 @@ display: while (quote_start < quote.length() && quote[quote_start] == ' ') quote_start++; } - /* FOOTER */ + /* FOOTER: Footer is shared visual status: render timestamp plus a Wi-Fi-strength glyph. */ it.strftime(614, 380, id(footer_font), TextAlign::BASELINE_RIGHT, "Aktualisiert um %d.%m.%Y %H:%M", now); diff --git a/python_scripts/epaper_calendar_data.py b/python_scripts/epaper_calendar_data.py index 1adedb7..3749820 100644 --- a/python_scripts/epaper_calendar_data.py +++ b/python_scripts/epaper_calendar_data.py @@ -1,3 +1,5 @@ +# Display contract shared with the ESPHome calendar page: compact German labels, +# a bounded number of events, and strings sized for the e-paper layout. CALENDAR_NAMES = { "calendar.tkrz_kalender": "Arbeit", "calendar.privat": "Privat", @@ -9,6 +11,7 @@ MAX_TITLE_LENGTH = 22 MAX_META_LENGTH = 35 +# Keep variable event text within the fixed-width e-paper agenda columns. def shorten(value, maximum): value = (value or "").strip() if len(value) <= maximum: @@ -16,6 +19,8 @@ def shorten(value, maximum): return value[: maximum - 3].rstrip() + "..." +# Convert one Home Assistant calendar event into the small JSON shape consumed +# by ESPHome, separating all-day, start-time, end-time, and metadata display data. def compact_event(event, calendar_name): start = event.get("start", "") all_day = "T" not in start @@ -37,6 +42,8 @@ def compact_event(event, calendar_name): } +# Read the calendar.get_events response supplied by the calling template sensor +# and collect events under the date on which they should appear in the agenda. calendar_data = data.get("calendar", {}) today = str(data.get("now", ""))[:10] events_by_date = {} @@ -62,6 +69,8 @@ for calendar_id, calendar in calendar_data.items(): compact_event(event, calendar_name) ) +# Sort day groups and events, put all-day entries first, and cap the payload so +# deserializing it stays safe on the ESP32 without PSRAM. entries = [] entry_count = 0 for date in sorted(events_by_date): @@ -84,4 +93,5 @@ for date in sorted(events_by_date): } ) +# Return only the normalized agenda payload to the Home Assistant template sensor. output["entries"] = entries