#:########################################################################################:# # TITLE: ESP reTERMINAL E1001 DASHBOARD # # zorruno.com layout v1.1 2026 # #:########################################################################################:# # REPO: # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-reterminal-e1001.yaml #:########################################################################################:# # VERSIONS: # V0.3 2026-07-20 Added two-page Jobs and Appliances dashboard, white-button page # selection, rounded tiles, full date header, Home Assistant job and # appliance data, inverse alert tiles, and change-only display refreshes. # V0.2 2026-07-20 Enabled ESP-IDF experimental features required by ESPHome # 2026.7.0 for OTA support with 32MB flash. # V0.1 2026-07-20 Initial ESPHome bring-up, Home Assistant API connection, # E1001 e-paper display test matrix and physical button setup. #:########################################################################################:# # HARDWARE: # - Seeed Studio reTerminal E1001 # - ESP32-S3 # - 32MB flash # - 8MB octal PSRAM # - 7.5 inch monochrome e-paper display # - 800x480 resolution # - Green refresh button: GPIO3 # - Right white button: GPIO4 # - Left white button: GPIO5 # - Onboard green LED: GPIO6, inverted # - Display SPI clock: GPIO7 # - Display SPI MOSI: GPIO9 # - Display CS: GPIO10 # - Display DC: GPIO11 # - Display reset: GPIO12 # - Display busy: GPIO13, inverted #:########################################################################################:# # OPERATION: # - The left white button selects the View Road Jobs page. # - The right white button selects the View Road Appliances page. # - The display is checked every 30 seconds, but the e-paper panel is refreshed only when # the visible page content has actually changed. # - Page changes and manual refreshes intentionally force one physical display refresh. # - Job countdown wording changes only when its displayed hour/day value changes. # - OVERDUE and MISSED job tiles use a black background with white text. # - Appliance tiles show OPERATING while active. # - A completed appliance shows inverse FINISHED for one hour after cycle completion. # - After one hour, the appliance tile shows Completed at HH:MM with a normal background. # - Home Assistant entity data is imported through the encrypted ESPHome native API. # - MQTT is not required on this ESPHome device. # - Deep sleep remains disabled during dashboard development and OTA testing. #:########################################################################################:# #:########################################################################################:# # SUBSTITUTIONS: # Specific device variable substitutions # If NOT using a secrets file, replace the passwords etc with quoted values. #:########################################################################################:# substitutions: # Device Naming device_name: "esp-reterminal-e1001" friendly_name: "reTerminal E1001 Dashboard" description_comment: "Seeed Studio reTerminal E1001 two-page Home Assistant jobs and appliance status dashboard. (Layout V1.1)" device_area: "Lounge" # Project Naming project_name: "SeeedStudio.reTerminal-E1001" project_version: "v0.3" # Passwords and Secrets api_key: !secret esp-api_key ota_pass: !secret esp-ota_pass # Device Settings log_level: "INFO" display_check_interval: "30s" finished_highlight_seconds: "3600" job_hours_display_limit: "48" #:########################################################################################:# # PACKAGES: # Included Common Packages # https://esphome.io/components/packages.html #:########################################################################################:# packages: #### WIFI, Network DHCP, Fallback AP, Safemode #### common_wifi: !include file: common/network_common_dhcp.yaml vars: local_device_name: "${device_name}" local_ota_pass: "${ota_pass}" #### HOME ASSISTANT API #### common_api: !include file: common/api_common.yaml #file: common/api_common_noencryption.yaml vars: local_api_key: "${api_key}" #### MQTT #### # Home Assistant entity states are imported through the ESPHome native API. #common_mqtt: !include # file: common/mqtt_common.yaml # vars: # local_device_name: "${device_name}" #### WEB PORTAL #### #common_webportal: !include common/webportal_common_nopass.yaml #### SNTP #### # Home Assistant provides local time through the native API below. #common_sntp: !include common/sntp_common.yaml #### DIAGNOSTICS SENSORS #### #diag_basic: !include common/include_basic_diag_sensors.yaml #diag_more: !include common/include_more_diag_sensors.yaml #diag_debug: !include common/include_debug_diag_sensors.yaml #diag_resetcount: !include common/include_resetcount_diag_sensors.yaml #:########################################################################################:# # ESPHOME: # https://esphome.io/components/esphome.html #:########################################################################################:# esphome: name: "${device_name}" friendly_name: "${friendly_name}" comment: "${description_comment}" area: "${device_area}" project: name: "${project_name}" version: "${project_version}" on_boot: priority: -100 then: # Allow Wi-Fi, API entities and Home Assistant time to initialise. - delay: 15s - display.page.show: page_jobs - globals.set: id: current_page value: "0" - script.execute: id: refresh_display_if_changed force: true #:########################################################################################:# # ESP PLATFORM AND FRAMEWORK: # https://esphome.io/components/esp32.html #:########################################################################################:# esp32: board: esp32-s3-devkitc-1 flash_size: 32MB framework: type: arduino advanced: enable_idf_experimental_features: true #:########################################################################################:# # PSRAM: # https://esphome.io/components/psram.html #:########################################################################################:# psram: mode: octal speed: 80MHz #:########################################################################################:# # LOGGER: # The E1001 uses its CH340K USB-to-UART bridge on UART0. # https://esphome.io/components/logger.html #:########################################################################################:# logger: level: "${log_level}" baud_rate: 115200 hardware_uart: UART0 #:########################################################################################:# # SPI: # E-paper display bus # https://esphome.io/components/spi.html #:########################################################################################:# spi: id: epaper_spi clk_pin: GPIO7 mosi_pin: GPIO9 #:########################################################################################:# # HOME ASSISTANT TIME: # Used for the date header, job countdowns and appliance completion age. # https://esphome.io/components/time/homeassistant.html #:########################################################################################:# time: - platform: homeassistant id: homeassistant_time on_time_sync: then: - script.execute: id: refresh_display_if_changed force: false #:########################################################################################:# # GLOBALS: # Display page and change-detection state # https://esphome.io/components/globals.html #:########################################################################################:# globals: - id: current_page type: int restore_value: no initial_value: "0" - id: last_display_signature type: std::string restore_value: no initial_value: '""' - id: display_update_required type: bool restore_value: no initial_value: "false" #:########################################################################################:# # FONTS: # Bold, compact fonts for clear monochrome e-paper rendering. # https://esphome.io/components/font.html #:########################################################################################:# font: - file: type: gfonts family: Inter weight: 700 id: font_header size: 31 - file: type: gfonts family: Inter weight: 700 id: font_date size: 18 - file: type: gfonts family: Inter weight: 700 id: font_job_title size: 22 - file: type: gfonts family: Inter weight: 700 id: font_job_status size: 20 - file: type: gfonts family: Inter weight: 700 id: font_appliance_title size: 16 - file: type: gfonts family: Inter weight: 700 id: font_appliance_status size: 17 #:########################################################################################:# # DISPLAY: # Two pages with rounded status tiles. # The display itself never runs on a blind polling interval. All refreshes pass through # refresh_display_if_changed before the physical e-paper update occurs. # https://esphome.io/components/display/waveshare_epaper.html #:########################################################################################:# display: - platform: waveshare_epaper id: epaper_display spi_id: epaper_spi model: 7.50inv2 cs_pin: GPIO10 dc_pin: GPIO11 reset_pin: number: GPIO12 inverted: false busy_pin: number: GPIO13 inverted: true update_interval: never pages: #:####################################################################################:# # PAGE 1 - VIEW ROAD JOBS # #:####################################################################################:# - id: page_jobs lambda: |- const int screen_width = 800; const int header_y = 58; const int radius = 9; const int normal_border = 2; const int emphasis_border = 4; const int hour_limit = ${job_hours_display_limit}; const auto now = id(homeassistant_time).now(); const int64_t now_ts = now.is_valid() ? now.timestamp : 0; auto fill_rounded = [&](int x, int y, int w, int h, int r, Color colour) { it.filled_rectangle(x + r, y, w - (2 * r), h, colour); it.filled_rectangle(x, y + r, w, h - (2 * r), colour); it.filled_circle(x + r, y + r, r, colour); it.filled_circle(x + w - r - 1, y + r, r, colour); it.filled_circle(x + r, y + h - r - 1, r, colour); it.filled_circle(x + w - r - 1, y + h - r - 1, r, colour); }; auto rounded_tile = [&](int x, int y, int w, int h, bool inverse, int border) { fill_rounded(x, y, w, h, radius, COLOR_ON); if (!inverse) { int inner_radius = radius - border; if (inner_radius < 2) inner_radius = 2; fill_rounded( x + border, y + border, w - (2 * border), h - (2 * border), inner_radius, COLOR_OFF ); } }; auto make_date = [&]() -> std::string { if (!now.is_valid()) return "Waiting for Home Assistant time"; const char *weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; const char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; const int day = now.day_of_month; const int last_two = day % 100; const char *suffix = "th"; if (last_two < 11 || last_two > 13) { if ((day % 10) == 1) suffix = "st"; else if ((day % 10) == 2) suffix = "nd"; else if ((day % 10) == 3) suffix = "rd"; } char buffer[72]; snprintf( buffer, sizeof(buffer), "%s, %d%s %s %d", weekdays[now.day_of_week - 1], day, suffix, months[now.month - 1], now.year ); return std::string(buffer); }; auto draw_job = [&](int x, int y, const char *name, const std::string &state, float due_ts, const std::string &completed_time) { const int w = 256; const int h = 197; std::string status = "Unavailable"; bool inverse = false; int border = normal_border; if (state == "Complete") { if (!completed_time.empty() && completed_time != "Never" && completed_time != "unknown") { status = "Completed " + completed_time; } else { status = "Complete"; } } else if (state == "Not Due") { if (now_ts > 0 && !isnan(due_ts) && due_ts > now_ts) { const int64_t remaining = static_cast(due_ts) - now_ts; const int hours = static_cast((remaining + 3599) / 3600); if (hours <= hour_limit) { status = "Due in " + std::to_string(hours) + (hours == 1 ? " hour" : " hours"); } else { const int days = static_cast((remaining + 86399) / 86400); status = "Due in " + std::to_string(days) + (days == 1 ? " day" : " days"); } } else { status = "Not Due"; } } else if (state == "Due") { status = "DUE"; border = emphasis_border; } else if (state == "Overdue") { status = "OVERDUE"; inverse = true; } else if (state == "Missed") { status = "MISSED"; inverse = true; } else if (!state.empty()) { status = state; } rounded_tile(x, y, w, h, inverse, border); const Color text_colour = inverse ? COLOR_OFF : COLOR_ON; it.printf( x + (w / 2), y + 67, id(font_job_title), text_colour, TextAlign::CENTER, "%s", name ); it.printf( x + (w / 2), y + 132, id(font_job_status), text_colour, TextAlign::CENTER, "%s", status.c_str() ); }; // Header. it.print(14, 28, id(font_header), TextAlign::CENTER_LEFT, "View Road Jobs"); const std::string date_text = make_date(); it.printf( screen_width - 14, 29, id(font_date), TextAlign::CENTER_RIGHT, "%s", date_text.c_str() ); it.line(8, header_y, screen_width - 9, header_y); // Three tiles on the top row and two centred on the lower row. draw_job( 8, 70, "Dog Feeding Morning", id(job_dog_feeding_morning_state).state, id(job_dog_feeding_morning_due_ts).state, id(job_dog_feeding_morning_completed_time).state ); draw_job( 272, 70, "Dog Feeding Evening", id(job_dog_feeding_evening_state).state, id(job_dog_feeding_evening_due_ts).state, id(job_dog_feeding_evening_completed_time).state ); draw_job( 536, 70, "Dog De-pooping", id(job_dog_depooping_state).state, id(job_dog_depooping_due_ts).state, id(job_dog_depooping_completed_time).state ); draw_job( 140, 275, "Dishwasher Unpacked", id(job_dishwasher_unpacked_state).state, id(job_dishwasher_unpacked_due_ts).state, id(job_dishwasher_unpacked_completed_time).state ); draw_job( 404, 275, "Rubbish Removed", id(job_rubbish_removed_state).state, id(job_rubbish_removed_due_ts).state, id(job_rubbish_removed_completed_time).state ); #:####################################################################################:# # PAGE 2 - VIEW ROAD APPLIANCES # #:####################################################################################:# - id: page_appliances lambda: |- const int screen_width = 800; const int header_y = 58; const int radius = 8; const int normal_border = 2; const int emphasis_border = 4; const int finished_seconds = ${finished_highlight_seconds}; const auto now = id(homeassistant_time).now(); const int64_t now_ts = now.is_valid() ? now.timestamp : 0; auto fill_rounded = [&](int x, int y, int w, int h, int r, Color colour) { it.filled_rectangle(x + r, y, w - (2 * r), h, colour); it.filled_rectangle(x, y + r, w, h - (2 * r), colour); it.filled_circle(x + r, y + r, r, colour); it.filled_circle(x + w - r - 1, y + r, r, colour); it.filled_circle(x + r, y + h - r - 1, r, colour); it.filled_circle(x + w - r - 1, y + h - r - 1, r, colour); }; auto rounded_tile = [&](int x, int y, int w, int h, bool inverse, int border) { fill_rounded(x, y, w, h, radius, COLOR_ON); if (!inverse) { int inner_radius = radius - border; if (inner_radius < 2) inner_radius = 2; fill_rounded( x + border, y + border, w - (2 * border), h - (2 * border), inner_radius, COLOR_OFF ); } }; auto make_date = [&]() -> std::string { if (!now.is_valid()) return "Waiting for Home Assistant time"; const char *weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; const char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; const int day = now.day_of_month; const int last_two = day % 100; const char *suffix = "th"; if (last_two < 11 || last_two > 13) { if ((day % 10) == 1) suffix = "st"; else if ((day % 10) == 2) suffix = "nd"; else if ((day % 10) == 3) suffix = "rd"; } char buffer[72]; snprintf( buffer, sizeof(buffer), "%s, %d%s %s %d", weekdays[now.day_of_week - 1], day, suffix, months[now.month - 1], now.year ); return std::string(buffer); }; auto completed_clock = [&](const std::string &stop_local) -> std::string { if (stop_local.length() >= 16) return stop_local.substr(11, 5); return "unknown"; }; auto draw_appliance = [&](int x, int y, const char *title_line_1, const char *title_line_2, const std::string &state, float stop_ts, float duration_seconds, const std::string &stop_local) { const int w = 190; const int h = 128; const bool valid_cycle = !isnan(stop_ts) && stop_ts > 0 && !isnan(duration_seconds) && duration_seconds > 0; bool recent_finish = false; if (valid_cycle && now_ts > 0) { const int64_t age = now_ts - static_cast(stop_ts); recent_finish = age >= 0 && age < finished_seconds; } std::string status = "Unavailable"; bool inverse = false; int border = normal_border; if (state == "Operating") { status = "OPERATING"; border = emphasis_border; } else if (valid_cycle && recent_finish) { status = "FINISHED"; inverse = true; } else if (valid_cycle) { status = "Completed at " + completed_clock(stop_local); } else if (state == "Standby") { status = "STANDBY"; } else if (state == "Off") { status = "OFF"; } else if (state == "Finished") { status = "FINISHED"; inverse = true; } else if (!state.empty()) { status = state; } rounded_tile(x, y, w, h, inverse, border); const Color text_colour = inverse ? COLOR_OFF : COLOR_ON; if (title_line_2[0] == '\0') { it.printf( x + (w / 2), y + 39, id(font_appliance_title), text_colour, TextAlign::CENTER, "%s", title_line_1 ); } else { it.printf( x + (w / 2), y + 25, id(font_appliance_title), text_colour, TextAlign::CENTER, "%s", title_line_1 ); it.printf( x + (w / 2), y + 49, id(font_appliance_title), text_colour, TextAlign::CENTER, "%s", title_line_2 ); } it.printf( x + (w / 2), y + 91, id(font_appliance_status), text_colour, TextAlign::CENTER, "%s", status.c_str() ); }; // Header. it.print(14, 28, id(font_header), TextAlign::CENTER_LEFT, "View Road Appliances"); const std::string date_text = make_date(); it.printf( screen_width - 14, 29, id(font_date), TextAlign::CENTER_RIGHT, "%s", date_text.c_str() ); it.line(8, header_y, screen_width - 9, header_y); // Four tiles on rows one and two; two centred tiles on row three. draw_appliance( 8, 70, "Washing", "Machine", id(appliance_washing_state).state, id(appliance_washing_stop_ts).state, id(appliance_washing_duration).state, id(appliance_washing_stop_local).state ); draw_appliance( 206, 70, "Dryer", "", id(appliance_dryer_state).state, id(appliance_dryer_stop_ts).state, id(appliance_dryer_duration).state, id(appliance_dryer_stop_local).state ); draw_appliance( 404, 70, "Main", "Dishwasher", id(appliance_main_dishwasher_state).state, id(appliance_main_dishwasher_stop_ts).state, id(appliance_main_dishwasher_duration).state, id(appliance_main_dishwasher_stop_local).state ); draw_appliance( 602, 70, "Downstairs", "Dishwasher", id(appliance_downstairs_dishwasher_state).state, id(appliance_downstairs_dishwasher_stop_ts).state, id(appliance_downstairs_dishwasher_duration).state, id(appliance_downstairs_dishwasher_stop_local).state ); draw_appliance( 8, 206, "EV Charger", "32A", id(appliance_evcharger_32a_state).state, id(appliance_evcharger_32a_stop_ts).state, id(appliance_evcharger_32a_duration).state, id(appliance_evcharger_32a_stop_local).state ); draw_appliance( 206, 206, "EV Charger", "16A", id(appliance_evcharger_16a_state).state, id(appliance_evcharger_16a_stop_ts).state, id(appliance_evcharger_16a_duration).state, id(appliance_evcharger_16a_stop_local).state ); draw_appliance( 404, 206, "Ryobi Charger", "Left", id(appliance_ryobi_left_state).state, id(appliance_ryobi_left_stop_ts).state, id(appliance_ryobi_left_duration).state, id(appliance_ryobi_left_stop_local).state ); draw_appliance( 602, 206, "Ryobi Charger", "Right", id(appliance_ryobi_right_state).state, id(appliance_ryobi_right_stop_ts).state, id(appliance_ryobi_right_duration).state, id(appliance_ryobi_right_stop_local).state ); draw_appliance( 206, 342, "Pool Pump", "", id(appliance_pool_pump_state).state, id(appliance_pool_pump_stop_ts).state, id(appliance_pool_pump_duration).state, id(appliance_pool_pump_stop_local).state ); draw_appliance( 404, 342, "3D Printer", "", id(appliance_3d_printer_state).state, id(appliance_3d_printer_stop_ts).state, id(appliance_3d_printer_duration).state, id(appliance_3d_printer_stop_local).state ); #:########################################################################################:# # DISPLAY CHANGE DETECTION: # Builds a signature from only the content that is currently visible. The display is not # physically refreshed when the visible wording and style would remain unchanged. # https://esphome.io/components/script.html #:########################################################################################:# script: - id: refresh_display_if_changed mode: restart parameters: force: bool then: # Debounce bursts of Home Assistant state restoration and page changes. - delay: 750ms - lambda: |- const auto now = id(homeassistant_time).now(); const int64_t now_ts = now.is_valid() ? now.timestamp : 0; const int hour_limit = ${job_hours_display_limit}; const int finished_seconds = ${finished_highlight_seconds}; std::string signature = id(current_page) == 0 ? "jobs" : "appliances"; auto append_text = [&](const std::string &value) { signature += "|"; signature += value; }; auto append_number = [&](int64_t value) { char buffer[32]; snprintf(buffer, sizeof(buffer), "|%lld", static_cast(value)); signature += buffer; }; // The visible header changes once per day, not every minute. if (now.is_valid()) { append_number(now.year); append_number(now.day_of_year); } else { append_text("no_time"); } if (id(current_page) == 0) { auto append_job = [&](const std::string &state, float due_ts, const std::string &completed_time) { append_text(state); if (state == "Complete") { append_text(completed_time); } else if (state == "Not Due" && now_ts > 0 && !isnan(due_ts) && due_ts > now_ts) { const int64_t remaining = static_cast(due_ts) - now_ts; const int hours = static_cast((remaining + 3599) / 3600); if (hours <= hour_limit) { append_text("hours"); append_number(hours); } else { const int days = static_cast((remaining + 86399) / 86400); append_text("days"); append_number(days); } } }; append_job( id(job_dog_feeding_morning_state).state, id(job_dog_feeding_morning_due_ts).state, id(job_dog_feeding_morning_completed_time).state ); append_job( id(job_dog_feeding_evening_state).state, id(job_dog_feeding_evening_due_ts).state, id(job_dog_feeding_evening_completed_time).state ); append_job( id(job_dog_depooping_state).state, id(job_dog_depooping_due_ts).state, id(job_dog_depooping_completed_time).state ); append_job( id(job_dishwasher_unpacked_state).state, id(job_dishwasher_unpacked_due_ts).state, id(job_dishwasher_unpacked_completed_time).state ); append_job( id(job_rubbish_removed_state).state, id(job_rubbish_removed_due_ts).state, id(job_rubbish_removed_completed_time).state ); } else { auto append_appliance = [&](const std::string &state, float stop_ts, float duration_seconds, const std::string &stop_local) { const bool valid_cycle = !isnan(stop_ts) && stop_ts > 0 && !isnan(duration_seconds) && duration_seconds > 0; if (state == "Operating") { append_text("OPERATING"); return; } if (valid_cycle) { bool recent_finish = false; if (now_ts > 0) { const int64_t age = now_ts - static_cast(stop_ts); recent_finish = age >= 0 && age < finished_seconds; } append_text(recent_finish ? "FINISHED" : "COMPLETED"); append_text(stop_local); return; } append_text(state); }; append_appliance( id(appliance_washing_state).state, id(appliance_washing_stop_ts).state, id(appliance_washing_duration).state, id(appliance_washing_stop_local).state ); append_appliance( id(appliance_dryer_state).state, id(appliance_dryer_stop_ts).state, id(appliance_dryer_duration).state, id(appliance_dryer_stop_local).state ); append_appliance( id(appliance_main_dishwasher_state).state, id(appliance_main_dishwasher_stop_ts).state, id(appliance_main_dishwasher_duration).state, id(appliance_main_dishwasher_stop_local).state ); append_appliance( id(appliance_downstairs_dishwasher_state).state, id(appliance_downstairs_dishwasher_stop_ts).state, id(appliance_downstairs_dishwasher_duration).state, id(appliance_downstairs_dishwasher_stop_local).state ); append_appliance( id(appliance_evcharger_32a_state).state, id(appliance_evcharger_32a_stop_ts).state, id(appliance_evcharger_32a_duration).state, id(appliance_evcharger_32a_stop_local).state ); append_appliance( id(appliance_evcharger_16a_state).state, id(appliance_evcharger_16a_stop_ts).state, id(appliance_evcharger_16a_duration).state, id(appliance_evcharger_16a_stop_local).state ); append_appliance( id(appliance_ryobi_left_state).state, id(appliance_ryobi_left_stop_ts).state, id(appliance_ryobi_left_duration).state, id(appliance_ryobi_left_stop_local).state ); append_appliance( id(appliance_ryobi_right_state).state, id(appliance_ryobi_right_stop_ts).state, id(appliance_ryobi_right_duration).state, id(appliance_ryobi_right_stop_local).state ); append_appliance( id(appliance_pool_pump_state).state, id(appliance_pool_pump_stop_ts).state, id(appliance_pool_pump_duration).state, id(appliance_pool_pump_stop_local).state ); append_appliance( id(appliance_3d_printer_state).state, id(appliance_3d_printer_stop_ts).state, id(appliance_3d_printer_duration).state, id(appliance_3d_printer_stop_local).state ); } id(display_update_required) = force || signature != id(last_display_signature); if (id(display_update_required)) { id(last_display_signature) = signature; ESP_LOGI("display", "Visible display content changed; refreshing page %d", id(current_page) + 1); } else { ESP_LOGD("display", "Visible display content unchanged; e-paper refresh skipped"); } - if: condition: lambda: |- return id(display_update_required); then: - light.turn_on: onboard_led - component.update: epaper_display - delay: 300ms - light.turn_off: onboard_led - globals.set: id: display_update_required value: "false" #:########################################################################################:# # PERIODIC DISPLAY CHECK: # This checks in-memory values only; it does not refresh the e-paper unless the signature # differs from the last rendered content. # https://esphome.io/components/interval.html #:########################################################################################:# interval: - interval: "${display_check_interval}" then: - script.execute: id: refresh_display_if_changed force: false #:########################################################################################:# # HOME ASSISTANT JOB STATE IMPORTS: # Jobs Tracker states and display attributes # https://esphome.io/components/text_sensor/homeassistant.html # https://esphome.io/components/sensor/homeassistant.html #:########################################################################################:# text_sensor: # Device diagnostics retained from the initial build. - platform: wifi_info ip_address: id: wifi_ip_address name: "${friendly_name} IP Address" entity_category: diagnostic #:######################################################################################:# # Job states and completion times # #:######################################################################################:# - platform: homeassistant id: job_dog_feeding_morning_state entity_id: sensor.jobs_tracker_dog_feeding_morning internal: true - platform: homeassistant id: job_dog_feeding_morning_completed_time entity_id: sensor.jobs_tracker_dog_feeding_morning attribute: last_completed_time internal: true - platform: homeassistant id: job_dog_feeding_evening_state entity_id: sensor.jobs_tracker_dog_feeding_evening internal: true - platform: homeassistant id: job_dog_feeding_evening_completed_time entity_id: sensor.jobs_tracker_dog_feeding_evening attribute: last_completed_time internal: true - platform: homeassistant id: job_dog_depooping_state entity_id: sensor.jobs_tracker_dog_depooping internal: true - platform: homeassistant id: job_dog_depooping_completed_time entity_id: sensor.jobs_tracker_dog_depooping attribute: last_completed_time internal: true - platform: homeassistant id: job_dishwasher_unpacked_state entity_id: sensor.jobs_tracker_dishwasher_unpacked internal: true - platform: homeassistant id: job_dishwasher_unpacked_completed_time entity_id: sensor.jobs_tracker_dishwasher_unpacked attribute: last_completed_time internal: true - platform: homeassistant id: job_rubbish_removed_state entity_id: sensor.jobs_tracker_rubbish_removed internal: true - platform: homeassistant id: job_rubbish_removed_completed_time entity_id: sensor.jobs_tracker_rubbish_removed attribute: last_completed_time internal: true #:######################################################################################:# # Appliance states and completion-local timestamps # #:######################################################################################:# - platform: homeassistant id: appliance_washing_state entity_id: sensor.appliance_monitor_washing_machine_monitor internal: true - platform: homeassistant id: appliance_washing_stop_local entity_id: sensor.appliance_monitor_washing_machine_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_dryer_state entity_id: sensor.appliance_monitor_dryer_monitor internal: true - platform: homeassistant id: appliance_dryer_stop_local entity_id: sensor.appliance_monitor_dryer_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_main_dishwasher_state entity_id: sensor.appliance_monitor_main_dishwasher_monitor internal: true - platform: homeassistant id: appliance_main_dishwasher_stop_local entity_id: sensor.appliance_monitor_main_dishwasher_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_downstairs_dishwasher_state entity_id: sensor.appliance_monitor_downstairs_dishwasher_monitor internal: true - platform: homeassistant id: appliance_downstairs_dishwasher_stop_local entity_id: sensor.appliance_monitor_downstairs_dishwasher_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_evcharger_32a_state entity_id: sensor.appliance_monitor_ev_charger_32a_monitor internal: true - platform: homeassistant id: appliance_evcharger_32a_stop_local entity_id: sensor.appliance_monitor_ev_charger_32a_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_evcharger_16a_state entity_id: sensor.appliance_monitor_ev_charger_16a_monitor internal: true - platform: homeassistant id: appliance_evcharger_16a_stop_local entity_id: sensor.appliance_monitor_ev_charger_16a_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_ryobi_left_state entity_id: sensor.appliance_monitor_ryobicharger_left_monitor internal: true - platform: homeassistant id: appliance_ryobi_left_stop_local entity_id: sensor.appliance_monitor_ryobicharger_left_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_ryobi_right_state entity_id: sensor.appliance_monitor_ryobicharger_right_monitor internal: true - platform: homeassistant id: appliance_ryobi_right_stop_local entity_id: sensor.appliance_monitor_ryobicharger_right_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_pool_pump_state entity_id: sensor.appliance_monitor_pool_pump_monitor internal: true - platform: homeassistant id: appliance_pool_pump_stop_local entity_id: sensor.appliance_monitor_pool_pump_monitor attribute: cycle_stop_local internal: true - platform: homeassistant id: appliance_3d_printer_state entity_id: sensor.appliance_monitor_3d_printer_monitor internal: true - platform: homeassistant id: appliance_3d_printer_stop_local entity_id: sensor.appliance_monitor_3d_printer_monitor attribute: cycle_stop_local internal: true #:########################################################################################:# # HOME ASSISTANT NUMERIC ATTRIBUTE IMPORTS: # Job due timestamps plus appliance cycle stop times and durations #:########################################################################################:# sensor: #:######################################################################################:# # Job current due timestamps # #:######################################################################################:# - platform: homeassistant id: job_dog_feeding_morning_due_ts entity_id: sensor.jobs_tracker_dog_feeding_morning attribute: current_due_ts internal: true - platform: homeassistant id: job_dog_feeding_evening_due_ts entity_id: sensor.jobs_tracker_dog_feeding_evening attribute: current_due_ts internal: true - platform: homeassistant id: job_dog_depooping_due_ts entity_id: sensor.jobs_tracker_dog_depooping attribute: current_due_ts internal: true - platform: homeassistant id: job_dishwasher_unpacked_due_ts entity_id: sensor.jobs_tracker_dishwasher_unpacked attribute: current_due_ts internal: true - platform: homeassistant id: job_rubbish_removed_due_ts entity_id: sensor.jobs_tracker_rubbish_removed attribute: current_due_ts internal: true #:######################################################################################:# # Appliance cycle stop timestamps and completed cycle durations # #:######################################################################################:# - platform: homeassistant id: appliance_washing_stop_ts entity_id: sensor.appliance_monitor_washing_machine_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_washing_duration entity_id: sensor.appliance_monitor_washing_machine_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_dryer_stop_ts entity_id: sensor.appliance_monitor_dryer_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_dryer_duration entity_id: sensor.appliance_monitor_dryer_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_main_dishwasher_stop_ts entity_id: sensor.appliance_monitor_main_dishwasher_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_main_dishwasher_duration entity_id: sensor.appliance_monitor_main_dishwasher_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_downstairs_dishwasher_stop_ts entity_id: sensor.appliance_monitor_downstairs_dishwasher_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_downstairs_dishwasher_duration entity_id: sensor.appliance_monitor_downstairs_dishwasher_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_evcharger_32a_stop_ts entity_id: sensor.appliance_monitor_ev_charger_32a_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_evcharger_32a_duration entity_id: sensor.appliance_monitor_ev_charger_32a_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_evcharger_16a_stop_ts entity_id: sensor.appliance_monitor_ev_charger_16a_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_evcharger_16a_duration entity_id: sensor.appliance_monitor_ev_charger_16a_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_ryobi_left_stop_ts entity_id: sensor.appliance_monitor_ryobicharger_left_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_ryobi_left_duration entity_id: sensor.appliance_monitor_ryobicharger_left_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_ryobi_right_stop_ts entity_id: sensor.appliance_monitor_ryobicharger_right_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_ryobi_right_duration entity_id: sensor.appliance_monitor_ryobicharger_right_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_pool_pump_stop_ts entity_id: sensor.appliance_monitor_pool_pump_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_pool_pump_duration entity_id: sensor.appliance_monitor_pool_pump_monitor attribute: cycle_duration_seconds internal: true - platform: homeassistant id: appliance_3d_printer_stop_ts entity_id: sensor.appliance_monitor_3d_printer_monitor attribute: cycle_stop_ts internal: true - platform: homeassistant id: appliance_3d_printer_duration entity_id: sensor.appliance_monitor_3d_printer_monitor attribute: cycle_duration_seconds internal: true #:########################################################################################:# # OUTPUTS: # Onboard green LED # https://esphome.io/components/output/gpio.html #:########################################################################################:# output: - platform: gpio id: onboard_led_output pin: GPIO6 inverted: true #:########################################################################################:# # LIGHTS: # https://esphome.io/components/light/binary.html #:########################################################################################:# light: - platform: binary id: onboard_led name: "${friendly_name} Onboard LED" output: onboard_led_output restore_mode: ALWAYS_OFF entity_category: diagnostic #:########################################################################################:# # PHYSICAL BUTTONS: # Left white selects Jobs; right white selects Appliances; green performs a forced refresh. # https://esphome.io/components/binary_sensor/gpio.html #:########################################################################################:# binary_sensor: - platform: gpio id: button_refresh name: "${friendly_name} Green Refresh Button" pin: number: GPIO3 mode: input: true pullup: true inverted: true filters: - delayed_on: 20ms - delayed_off: 20ms on_press: then: - script.execute: id: refresh_display_if_changed force: true - platform: gpio id: button_right name: "${friendly_name} Right Button - Appliances" pin: number: GPIO4 mode: input: true pullup: true inverted: true filters: - delayed_on: 20ms - delayed_off: 20ms on_press: then: - if: condition: lambda: |- return id(current_page) != 1; then: - globals.set: id: current_page value: "1" - display.page.show: page_appliances - script.execute: id: refresh_display_if_changed force: true - platform: gpio id: button_left name: "${friendly_name} Left Button - Jobs" pin: number: GPIO5 mode: input: true pullup: true inverted: true filters: - delayed_on: 20ms - delayed_off: 20ms on_press: then: - if: condition: lambda: |- return id(current_page) != 0; then: - globals.set: id: current_page value: "0" - display.page.show: page_jobs - script.execute: id: refresh_display_if_changed force: true #:########################################################################################:# # HOME ASSISTANT CONTROLS: # Manual refresh and page-selection buttons # https://esphome.io/components/button/template.html #:########################################################################################:# button: - platform: template id: refresh_display_button name: "${friendly_name} Refresh Display" icon: "mdi:refresh" on_press: then: - script.execute: id: refresh_display_if_changed force: true - platform: template id: show_jobs_page_button name: "${friendly_name} Show Jobs Page" icon: "mdi:clipboard-check-outline" on_press: then: - if: condition: lambda: |- return id(current_page) != 0; then: - globals.set: id: current_page value: "0" - display.page.show: page_jobs - script.execute: id: refresh_display_if_changed force: true - platform: template id: show_appliances_page_button name: "${friendly_name} Show Appliances Page" icon: "mdi:washing-machine" on_press: then: - if: condition: lambda: |- return id(current_page) != 1; then: - globals.set: id: current_page value: "1" - display.page.show: page_appliances - script.execute: id: refresh_display_if_changed force: true