esphome: name: esdisplay-kitchen comment: "e-Paper display in the kitchen connected to Home Assistant" platformio_options: lib_deps: - bblanchon/ArduinoJson@7.4.2 includes: - prometheus_client.h globals: - id: prometheus_connected type: bool restore_value: no initial_value: 'true' esp32: board: nodemcu-32s framework: type: arduino # Enable logging logger: # Enable Home Assistant API (auto-discovery) api: password: "" # Over-The-Air updates ota: platform: esphome wifi: ssid: !env_var WIFI_SSID password: !env_var WIFI_PASSWORD # Enable fallback hotspot in case wifi fails ap: ssid: "Kitchen Display Fallback" password: "fallbackpassword" captive_portal: # Time synchronization to get current time on display time: - platform: sntp id: sntp_time timezone: "Europe/Berlin" # Define local and imported sensors sensor: - platform: homeassistant id: ha_outdoor_temp entity_id: sensor.regensburg_regensburg_temperatur internal: true - platform: homeassistant id: ha_kitchen_temp entity_id: sensor.home_kuche_wandthermostat_temperature internal: true # Local template sensors scraped from Prometheus - platform: template id: local_cellar_temp name: "Kitchen Display Cellar Temperature Indoor" unit_of_measurement: "°C" accuracy_decimals: 1 - platform: template id: local_cellar_humi name: "Kitchen Display Cellar Humidity Indoor" unit_of_measurement: "%" accuracy_decimals: 0 - platform: template id: local_cellar_dp name: "Kitchen Display Cellar Dewpoint Indoor" unit_of_measurement: "°C" accuracy_decimals: 1 - platform: template id: local_cellar_out_temp name: "Kitchen Display Cellar Temperature Outdoor" unit_of_measurement: "°C" accuracy_decimals: 1 - platform: template id: local_cellar_out_humi name: "Kitchen Display Cellar Humidity Outdoor" unit_of_measurement: "%" accuracy_decimals: 0 - platform: template id: local_cellar_out_dp name: "Kitchen Display Cellar Dewpoint Outdoor" unit_of_measurement: "°C" accuracy_decimals: 1 - platform: template id: local_fan_status name: "Kitchen Display Cellar Fan Status" accuracy_decimals: 0 - platform: template id: local_mold_critical name: "Kitchen Display Cellar Mold Critical" accuracy_decimals: 0 - platform: template id: local_fan_would_activate name: "Kitchen Display Cellar Fan Activation Criteria" accuracy_decimals: 0 - platform: template id: local_mold_threshold name: "Kitchen Display Cellar Mold Threshold" unit_of_measurement: "%" accuracy_decimals: 0 - platform: template id: local_shelly_online name: "Kitchen Display Cellar Shelly Online" accuracy_decimals: 0 # Interval to scrape metrics from Prometheus every 30 seconds interval: - interval: 30s then: - lambda: |- fetch_prometheus(); text_sensor: - platform: homeassistant id: ha_message entity_id: input_text.epaper_message internal: true - platform: wifi_info ip_address: name: "IP Address" id: wifi_ip # Font configuration (uses Google Fonts Roboto) font: - file: "gfonts://Roboto" id: font_title size: 16 - file: "gfonts://Roboto" id: font_body size: 12 # SPI bus configuration for e-Paper display spi: clk_pin: GPIO18 mosi_pin: GPIO23 display: - platform: waveshare_epaper id: epaper_display model: 2.13inv3 cs_pin: GPIO5 dc_pin: GPIO17 reset_pin: GPIO16 busy_pin: GPIO4 rotation: 90 update_interval: 60s lambda: |- // Fill background with white it.fill(COLOR_OFF); // Check if Prometheus is reachable if (!id(prometheus_connected)) { it.print(125, 20, id(font_title), TextAlign::CENTER, "PROMETHEUS ERROR"); it.print(125, 55, id(font_body), TextAlign::CENTER, "Cannot reach server"); it.print(125, 75, id(font_body), TextAlign::CENTER, "Check network or auth"); // Show time at the bottom if synced it.line(5, 96, 245, 96); char time_str[30]; auto time_now = id(sntp_time).now(); if (time_now.is_valid()) { it.strftime(5, 100, id(font_body), "Time: %a %H:%M", time_now); } else { it.print(5, 100, id(font_body), "Time: --:--"); } return; } // --- SECTION 1: HEADER --- it.print(5, 2, id(font_title), "Cellar Fan (TAUPI)"); // Time and weekday on the right auto time_now = id(sntp_time).now(); if (time_now.is_valid()) { it.strftime(245, 2, id(font_body), TextAlign::TOP_RIGHT, "%a %H:%M", time_now); } else { it.print(245, 2, id(font_body), TextAlign::TOP_RIGHT, "Time: --:--"); } // Draw header divider it.line(5, 18, 245, 18); // --- SECTION 2: CLIMATE CELLS --- // Vertical separator it.line(122, 22, 122, 82); // CELLAR INDOOR (Left) it.print(5, 22, id(font_body), "CELLAR INDOOR"); if (!isnan(id(local_cellar_temp).state)) { it.printf(5, 36, id(font_body), "Temp: %.1f C", id(local_cellar_temp).state); } else { it.print(5, 36, id(font_body), "Temp: -- C"); } if (!isnan(id(local_cellar_humi).state)) { it.printf(5, 49, id(font_body), "Humi: %.0f%%", id(local_cellar_humi).state); } else { it.print(5, 49, id(font_body), "Humi: --%%"); } if (!isnan(id(local_cellar_dp).state)) { it.printf(5, 62, id(font_body), "Dewp: %.1f C", id(local_cellar_dp).state); } else { it.print(5, 62, id(font_body), "Dewp: -- C"); } if (!isnan(id(local_mold_threshold).state)) { it.printf(5, 75, id(font_body), "Limit: %.0f%%", id(local_mold_threshold).state); } else { it.print(5, 75, id(font_body), "Limit: --%%"); } // CELLAR OUTDOOR (Right) it.print(128, 22, id(font_body), "CELLAR OUTDOOR"); if (!isnan(id(local_cellar_out_temp).state)) { it.printf(128, 36, id(font_body), "Temp: %.1f C", id(local_cellar_out_temp).state); } else { it.print(128, 36, id(font_body), "Temp: -- C"); } if (!isnan(id(local_cellar_out_humi).state)) { it.printf(128, 49, id(font_body), "Humi: %.0f%%", id(local_cellar_out_humi).state); } else { it.print(128, 49, id(font_body), "Humi: --%%"); } if (!isnan(id(local_cellar_out_dp).state)) { it.printf(128, 62, id(font_body), "Dewp: %.1f C", id(local_cellar_out_dp).state); } else { it.print(128, 62, id(font_body), "Dewp: -- C"); } // --- SECTION 3: STATUS BAR (Bottom) --- it.line(5, 86, 245, 86); // Fan status const char* fan_state = "OFF"; if (!isnan(id(local_fan_status).state) && id(local_fan_status).state > 0.5) { fan_state = "ON"; } it.printf(5, 90, id(font_body), "Fan: %s", fan_state); // Mold status const char* mold_state = "SAFE"; if (!isnan(id(local_mold_critical).state) && id(local_mold_critical).state > 0.5) { mold_state = "CRITICAL"; } it.printf(128, 90, id(font_body), "Mold: %s", mold_state); // Criteria status const char* crit_state = "NO"; if (!isnan(id(local_fan_would_activate).state) && id(local_fan_would_activate).state > 0.5) { crit_state = "YES"; } it.printf(5, 104, id(font_body), "Criteria: %s", crit_state); // Shelly Status const char* shelly_state = "OFFLINE"; if (!isnan(id(local_shelly_online).state) && id(local_shelly_online).state > 0.5) { shelly_state = "ONLINE"; } it.printf(128, 104, id(font_body), "Shelly: %s", shelly_state);