substitutions: name: epaperframe friendly_name: "Info-Dashboard" esphome: name: ${name} friendly_name: ${friendly_name} name_add_mac_suffix: false project: name: dashboard.epaper version: "1.0" min_version: 2022.4.0 on_boot: priority: -10 then: - delay: 10s - display.page.show: environment # temporary for power page dev - component.update: epaper esp32: board: nodemcu-32s framework: type: esp-idf wifi: ssid: "Voltage-legacy" password: !secret voltage_legacy_psk use_address: epaperframe.home power_save_mode: high fast_connect: on min_auth_mode: WPA2 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Epaperframe Fallback Hotspot" password: !secret fallback_psk captive_portal: # Enable logging logger: # Enable Home Assistant API api: encryption: key: !secret apikey ota: platform: esphome password: !secret ota # Power key of li-ion charger output: - platform: gpio pin: number: GPIO15 inverted: true mode: output: true id: power_key # Refresh screen on button click binary_sensor: - platform: gpio pin: number: GPIO18 mode: INPUT_PULLUP inverted: True name: "Refresh screen" internal: True on_multi_click: - timing: - ON for at most 1s - OFF for at most 0.3s - ON for at most 1s - OFF for at least 0.2s then: - logger.log: level: INFO format: "Button doubleclick, rendering next page!" - display.page.show_next: epaper - component.update: epaper - timing: - ON for at most 1s - OFF for at least 0.5s then: - logger.log: level: INFO format: "Button click, refreshing screen!" - component.update: epaper # HA pushbutton configuration button: - platform: template name: Refresh screen id: refresh_button icon: "mdi:refresh" on_press: - logger.log: level: INFO format: "HA Button click, refreshing screen!" - component.update: epaper - platform: template name: Next screen id: next_button icon: "mdi:skip-next" on_press: - logger.log: level: INFO format: "HA Button click, rendering next page!" - display.page.show_next: epaper - component.update: epaper sensor: # WiFi signal strength - platform: wifi_signal name: "WiFi Signalstärke" id: wifisignal update_interval: 60s - platform: homeassistant entity_id: sensor.wohnzimmer_temperatur id: temp_livingroom - platform: homeassistant entity_id: sensor.wohnzimmer_luftfeuchtigkeit id: humid_livingroom - platform: homeassistant entity_id: sensor.wohnzimmer_co2 id: co2_livingroom - platform: homeassistant entity_id: sensor.livingroom_wohnzimmer_feinstaub_pm_2_5_m id: pm25_livingroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_temperatur_2 id: temp_sleepingroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_luftfeuchtigkeit_2 id: humid_sleepingroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_kohlendioxid_2 id: co2_sleepingroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_pm25_2 id: pm25_sleepingroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_temperatur id: temp_childrensroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_luftfeuchtigkeit id: humid_childrensroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_kohlendioxid id: co2_childrensroom - platform: homeassistant entity_id: sensor.alpstuga_air_quality_monitor_pm25 id: pm25_childrensroom - platform: homeassistant entity_id: sensor.aussentemperatur id: temp_outdoor - platform: homeassistant entity_id: sensor.aussenluftfeuchtigkeit id: humid_outdoor - platform: homeassistant entity_id: sensor.serverroom_luftdruck id: airpressure - platform: homeassistant entity_id: sensor.serverroom_serveraum_temperatur id: temp_serverroom - platform: homeassistant entity_id: sensor.radioaktive_strahlungsleistung id: radiation text_sensor: - platform: homeassistant name: "Sun Rising ESP" #entity_id: sensor.sun_next_rising entity_id: sensor.sun_rising_template id: sun_rising internal: true - platform: homeassistant name: "Sun Setting ESP" #entity_id: sensor.sun_next_setting entity_id: sensor.sun_setting_template id: sun_setting internal: true - platform: homeassistant entity_id: sensor.moon id: moonphase - platform: homeassistant entity_id: weather.zuhause id: weather # Display-ready JSON produced by the Home Assistant calendar template sensor. - platform: homeassistant entity_id: sensor.epaper_kalenderdaten attribute: entries id: calendar_entries time: - platform: homeassistant id: homeassistant_time on_time: # Every 10 seconds - seconds: /10 then: - logger.log: level: INFO format: "Power button press" - output.turn_on: power_key - delay: 0.5s - output.turn_off: power_key # Every 10 minutes - seconds: 0 minutes: /10 then: - logger.log: level: INFO format: "Timer based screen refresh" - component.update: epaper # Font definitions font: - file: "fonts/Roboto-Medium.ttf" id: roboto_med_30 size: 30 - file: "fonts/Roboto-Regular.ttf" id: footer_font size: 15 glyphs: [ "&", "@", "!", ",", ".", '"', "%", "(", ")", "+", "-", "_", ":", "°", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "Ä", "ä", "ö", "/", "º", "µ", "³", "ß", "Ü", "ü", "Ö", "0", ] - file: "fonts/Roboto-Regular.ttf" id: sensor_unit size: 20 glyphs: [ "&", "@", "!", ",", ".", '"', "%", "(", ")", "+", "-", "_", ":", "°", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "Ä", "ä", "ö", "/", "º", "µ", "³", "ß", "Ü", "ü", "Ö", "0", ] - file: "fonts/Roboto-Medium.ttf" id: sub_sensor_font size: 30 glyphs: [ "&", "@", "!", ",", ".", '"', "%", "(", ")", "+", "-", "_", ":", "°", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "Ä", "ä", "ö", "/", "º", "µ", "³", "ß", "Ü", "ü", "Ö", "0", ] - file: "fonts/Roboto-Bold.ttf" id: big_sensor_font size: 50 glyphs: [ "&", "@", "!", ",", ".", '"', "%", "(", ")", "+", "-", "_", ":", "°", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "Ä", "ä", "ö", "/", "º", "µ", "³", "ß", "Ü", "ü", "Ö", "0", ] - file: "fonts/materialdesignicons-webfont.ttf" id: mdi_small size: 31 glyphs: [ "󰔏", # mdi-thermometer "󰖎", # mdi-water-percent "󰮬", # mdi-molecule (pm2.5) "󰟤", # mdi-co2 "󰊚", # mdi-gauge (air pressure) "󰖙", # mdi-weather-sunny "󰖛", # mdi-weather-sunset-down "󰖜", # mdi-weather-sunset-up "󱐋", # mdi-lightning-bolt "󰴾", # mdi-transmission-tower "󰚥", # mdi-power-plug "󱈹", # mdi-desk "󰔂", # mdi-television "󰐼", # mdi-radiation ] - file: "fonts/materialdesignicons-webfont.ttf" id: mdi_weather size: 80 glyphs: [ "󰽡", # mdi-moon-first-quarter "󰽢", # mdi-moon-full "󰽣", # mdi-moon-last-quarter "󰽤", # mdi-moon-new "󰽥", # mdi-moon-waning-crescent "󰽦", # mdi-moon-waning-gibbous "󰽧", # mdi-moon-waxing-crescent "󰽨", # mdi-moon-waxing-gibbous "󰖔", # mdi-weather-night "󰖐", # mdi-weather-cloudy "󰖑", # mdi-weather-fog "󰖒", # mdi-weather-hail "󰖓", # mdi-weather-lightning "󰙾", # mdi-weather-lightning-rainy "󰖕", # mdi-weather-partly-cloudy "󰖖", # mdi-weather-pouring "󰖗", # mdi-weather-rainy "󰖘", # mdi-weather-snowy "󰙿", # mdi-weather-snowy-rainy "󰖙", # mdi-weather-sunny "󰖝", # mdi-weather-windy "󰖞", # mdi-weather-windy-variant "󰳥", # mdi-weather-exceptional ] - file: "fonts/materialdesignicons-webfont.ttf" id: mdi_wifi size: 20 glyphs: [ # WiFi "󰤫", # mdi-wifi-strength-alert-outline "󰤟", # mdi-wifi-strength-1 "󰤢", # mdi-wifi-strength-2 "󰤥", # mdi-wifi-strength-3 "󰤨", # mdi-wifi-strength-4 ] - file: "fonts/materialdesignicons-webfont.ttf" id: mdi_med size: 60 glyphs: [ "󰀦", # mdi-alert ] image: - file: "epaperframe/C1024_logo_flat_bw.png" id: c1024_logo type: binary resize: 77x40 invert_alpha: True spi: clk_pin: 23 mosi_pin: 22 display: - platform: waveshare_epaper id: epaper cs_pin: 19 dc_pin: 21 busy_pin: 16 reset_pin: 17 model: 7.50in # full_update_every: 30 # not supported on this display update_interval: never pages: - id: environment lambda: |- /* Outdoor */ float outdoor_temp = id(temp_outdoor).state; int outdoor_humid = int(id(humid_outdoor).state); float air_pressure = id(airpressure).state; float outdoor_radiation = id(radiation).state; /* Living room */ float livingroom_temp = id(temp_livingroom).state; int livingroom_humid = int(id(humid_livingroom).state); float livingroom_co2 = id(co2_livingroom).state; int livingroom_pm25 = int(id(pm25_livingroom).state); /* Serverroom */ float serverroom_temp = id(temp_serverroom).state; /* Sleeping room */ float sleepingroom_temp = id(temp_sleepingroom).state; int sleepingroom_humid = int(id(humid_sleepingroom).state); float sleepingroom_co2 = id(co2_sleepingroom).state; int sleepingroom_pm25 = int(id(pm25_sleepingroom).state); /* Children's room */ float childrensroom_temp = id(temp_childrensroom).state; int childrensroom_humid = int(id(humid_childrensroom).state); float childrensroom_co2 = id(co2_childrensroom).state; int childrensroom_pm25 = int(id(pm25_childrensroom).state); int offsetX = 0; int offsetY = 0; /* OUTSIDE */ it.print(10, 10, id(sensor_unit), "Umwelt"); /* Draw the outline of a rectangle with the top left at [86,21], a width of 297 and a height of 3 */ it.filled_rectangle(86, 21, 297, 3); /* C1024 logo */ it.image(10 , 37, id(c1024_logo)); it.print(10, 130, id(mdi_small), TextAlign::BASELINE_LEFT, "󰊚"); // air pressure icon it.print(375, 80, id(mdi_small), TextAlign::BASELINE_CENTER, "󰔏"); // thermometer icon it.print(375, 130, id(mdi_small), TextAlign::BASELINE_CENTER, "󰖎"); // water percent icon it.print(95, 80, id(mdi_small), TextAlign::BASELINE_LEFT, "󰐼"); // radioactive icon if(outdoor_radiation >= 0) { it.printf(200, 80, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%2.2f", outdoor_radiation); it.print(205, 80, id(sensor_unit), TextAlign::BASELINE_LEFT, "µS/h"); } else { it.print(200, 80, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - "); it.print(205, 80, id(sensor_unit), TextAlign::BASELINE_LEFT, "µS/h"); } if(outdoor_temp > -100 && outdoor_temp < 100) { it.printf(357, 80, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%2.1fºC", outdoor_temp); } else { it.print(357, 80, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - ºC"); } if(outdoor_humid >=0 && outdoor_humid <= 100) { it.printf(357, 130, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%3d%%", outdoor_humid); } else { it.print(357, 130, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - %"); } if(air_pressure < 1200 && air_pressure >=700) { it.printf(220, 130, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%4.1f", air_pressure); it.print(225, 130, id(sensor_unit), TextAlign::BASELINE_LEFT, "hPa"); } else { it.print(220, 130, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "- "); it.print(225, 130, id(sensor_unit), TextAlign::BASELINE_LEFT, "hPa"); } /* Livingroom */ offsetY = 127; it.print(10, 10 + offsetY, id(sensor_unit), "Wohnzimmer"); it.filled_rectangle(136, 21 + offsetY, 169, 3); it.print(10, 125 + offsetY, id(mdi_small), TextAlign::BASELINE_LEFT, "󰮬"); // PM2.5 it.print(305, 75 + offsetY, id(mdi_small), TextAlign::BASELINE_CENTER, "󰔏"); // thermometer it.print(305, 125 + offsetY, id(mdi_small), TextAlign::BASELINE_CENTER, "󰖎"); // water percent it.print(10, 75 + offsetY, id(mdi_small), TextAlign::BASELINE_LEFT, "󰟤"); // CO2 if(livingroom_temp > -100 && livingroom_temp < 100) { it.printf(287, 75 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%2.1fºC", livingroom_temp); } else { it.print(287, 75 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - ºC"); } if(livingroom_humid >=0 && livingroom_humid <= 100) { it.printf(287, 125 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%3d%%", livingroom_humid); } else { it.print(287, 125 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - %"); } if(livingroom_co2 >=380 && livingroom_co2 <= 20000) { it.printf(150, 75 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%4.0f", livingroom_co2); it.print(155, 50 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "ppm"); } else { it.print(150, 75 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "- "); it.print(155, 50 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "ppm³"); } if(livingroom_pm25 < 255 && livingroom_pm25 >=0) { it.printf(150, 125 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%3d", livingroom_pm25); it.print(155, 125 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "µg/m³"); } else { it.print(150, 125 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "- "); it.print(155, 125 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "µg/m³"); } /* Sleepingroom */ offsetY = 247; it.print(10, 10 + offsetY, id(sensor_unit), "Schlafzimmer"); it.filled_rectangle(144, 21 + offsetY, 161, 3); it.print(10, 125 + offsetY, id(mdi_small), TextAlign::BASELINE_LEFT, "󰮬"); // PM2.5 it.print(305, 75 + offsetY, id(mdi_small), TextAlign::BASELINE_CENTER, "󰔏"); // thermometer it.print(305, 125 + offsetY, id(mdi_small), TextAlign::BASELINE_CENTER, "󰖎"); // water percent it.print(10, 75 + offsetY, id(mdi_small), TextAlign::BASELINE_LEFT, "󰟤"); // CO2 if(sleepingroom_temp > -100 && sleepingroom_temp < 100) { it.printf(287, 75 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%2.1fºC", sleepingroom_temp); } else { it.print(287, 75 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - ºC"); } if(sleepingroom_humid >=0 && sleepingroom_humid <= 100) { it.printf(287, 125 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%3d%%", sleepingroom_humid); } else { it.print(287, 125 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - %"); } if(sleepingroom_co2 >=380 && sleepingroom_co2 <= 20000) { it.printf(150, 75 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%4.0f", sleepingroom_co2); it.print(155, 50 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "ppm"); } else { it.print(150, 75 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "- "); it.print(155, 50 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "ppm³"); } if(sleepingroom_pm25 < 255 && sleepingroom_pm25 >=0) { it.printf(150, 125 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%3d", sleepingroom_pm25); it.print(155, 125 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "µg/m³"); } else { it.print(150, 125 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "- "); it.print(155, 125 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "µg/m³"); } /* Weather */ offsetX = 390; it.print(10 + offsetX, 10, id(sensor_unit), "Wetter"); it.filled_rectangle(80 + offsetX, 21, 155, 3); /* Current weather */ if(id(weather).has_state()) { int x = 227 + offsetX; int y = 96; if (id(weather).state == "clear-night") { // clear night it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖔"); ESP_LOGI("Weather", "clear night"); } else if (id(weather).state == "cloudy") { // cloudy it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖐"); ESP_LOGI("Weather", "cloudy"); } else if (id(weather).state == "fog") { // fog it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖑"); ESP_LOGI("Weather", "fog"); } else if (id(weather).state == "hail") { // hail it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖒"); ESP_LOGI("Weather", "hail"); } else if (id(weather).state == "lightning") { // lightning it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖓"); ESP_LOGI("Weather", "lightning"); } else if (id(weather).state == "lightning-rainy") { // lightning-rainy it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰙾"); ESP_LOGI("Weather", "lightning-rainy"); } else if (id(weather).state == "partlycloudy") { // partly cloudy it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖕"); ESP_LOGI("Weather", "partlycloudy"); } else if (id(weather).state == "pouring") { // pouring it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖖"); ESP_LOGI("Weather", "pouring"); } else if (id(weather).state == "rainy") { // rainy it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖗"); ESP_LOGI("Weather", "rainy"); } else if (id(weather).state == "snowy") { // snowy it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖘"); ESP_LOGI("Weather", "snowy"); } else if (id(weather).state == "snowy-rainy") { // snowy-rainy it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰙿"); ESP_LOGI("Weather", "snowy-rainy"); } else if (id(weather).state == "sunny") { // sunny it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖙"); ESP_LOGI("Weather", "sunny"); } else if (id(weather).state == "windy") { // windy it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖝"); ESP_LOGI("Weather", "windy"); } else if (id(weather).state == "windy-variant") { // windy variant it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰖞"); ESP_LOGI("Weather", "windy variant"); } else if (id(weather).state == "exceptional") { // exceptional it.print(x, y, id(mdi_weather), TextAlign::BASELINE_RIGHT, "󰳥"); ESP_LOGI("Weather", "exceptional"); } else { // N/A it.print(x, y, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "-"); ESP_LOGE("Weather", "No data"); } } /* Moon phase display */ if(id(moonphase).has_state()) { int x = 10 + offsetX; int y = 96; if (id(moonphase).state == "new_moon") { // new moon it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽤"); ESP_LOGI("Moonphase", "new moon"); } else if (id(moonphase).state == "waxing_crescent") { // waxing crescent it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽧"); ESP_LOGI("Moonphase", "waxing crescent"); } else if (id(moonphase).state == "first_quarter") { // first_quarter it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽡"); ESP_LOGI("Moonphase", "first quarter"); } else if (id(moonphase).state == "waxing_gibbous") { // waxing gibbous it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽨"); ESP_LOGI("Moonphase", "waxing gibbous"); } else if (id(moonphase).state == "full_moon") { // full moon it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽢"); ESP_LOGI("Moonphase", "full moon"); } else if (id(moonphase).state == "waning_gibbous") { // waning gibbous it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽦"); ESP_LOGI("Moonphase", "waning gibbous"); } else if (id(moonphase).state == "last_quarter") { // last quarter it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽣"); ESP_LOGI("Moonphase", "last quarter"); } else if (id(moonphase).state == "waning_crescent") { // waning crescent it.print(x, y, id(mdi_weather), TextAlign::BASELINE_LEFT, "󰽥"); ESP_LOGI("Moonphase", "waning crescent"); } else { // N/A it.print(x, y, id(big_sensor_font), TextAlign::BASELINE_LEFT, "-"); ESP_LOGE("Moonphase", "No data"); } } it.print(10 + offsetX, 130, id(mdi_small), TextAlign::BASELINE_LEFT, "󰖜"); // sun-up it.printf(50 + offsetX, 130, id(sensor_unit), TextAlign::BASELINE_LEFT, "%s", id(sun_rising).state.c_str()); it.print(135 + offsetX, 130, id(mdi_small), TextAlign::BASELINE_LEFT, "󰖛"); // sun-down it.printf(175 + offsetX, 130, id(sensor_unit), TextAlign::BASELINE_LEFT, "%s", id(sun_setting).state.c_str()); /* Children's room */ offsetX = 320; offsetY = 127; it.print(10 + offsetX, 10 + offsetY, id(sensor_unit), "Kinderzimmer"); it.filled_rectangle(148 + offsetX, 21 + offsetY, 157, 3); it.print(10 + offsetX, 125 + offsetY, id(mdi_small), TextAlign::BASELINE_LEFT, "󰮬"); // PM2.5 it.print(305 + offsetX, 75 + offsetY, id(mdi_small), TextAlign::BASELINE_CENTER, "󰔏"); // thermometer it.print(305 + offsetX, 125 + offsetY, id(mdi_small), TextAlign::BASELINE_CENTER, "󰖎"); // water percent it.print(10 + offsetX, 75 + offsetY, id(mdi_small), TextAlign::BASELINE_LEFT, "󰟤"); // CO2 if(childrensroom_temp > -100 && childrensroom_temp < 100) { it.printf(287 + offsetX, 75 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%2.1fºC", childrensroom_temp); } else { it.print(287 + offsetX, 75 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - ºC"); } if(childrensroom_humid >=0 && childrensroom_humid <= 100) { it.printf(287 + offsetX, 125 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%3d%%", childrensroom_humid); } else { it.print(287 + offsetX, 125 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - %"); } if(childrensroom_co2 >=380 && childrensroom_co2 <= 20000) { it.printf(150 + offsetX, 75 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%4.0f", childrensroom_co2); it.print(155 + offsetX, 50 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "ppm"); } else { it.print(150 + offsetX, 75 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "- "); it.print(155 + offsetX, 50 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "ppm³"); } if(childrensroom_pm25 < 255 && childrensroom_pm25 >=0) { it.printf(150 + offsetX, 125 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%3d", childrensroom_pm25); it.print(155 + offsetX, 125 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "µg/m³"); } else { it.print(150 + offsetX, 125 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "- "); it.print(155 + offsetX, 125 + offsetY, id(sensor_unit), TextAlign::BASELINE_LEFT, "µg/m³"); } /* Serverroom */ offsetY = 247; it.print(10 + offsetX, 10 + offsetY, id(sensor_unit), "Serverraum"); it.filled_rectangle(125 + offsetX, 21 + offsetY, 180, 3); it.print(305 + offsetX, 110 + offsetY, id(mdi_small), TextAlign::BASELINE_CENTER, "󰔏"); // thermometer if(serverroom_temp > -100 && serverroom_temp < 100) { it.printf(287 + offsetX, 110 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "ºC"); it.printf(250 + offsetX, 110 + offsetY, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%2.1f", serverroom_temp); } else { it.print(287 + offsetX, 110 + offsetY, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - ºC"); } /* FOOTER */ it.strftime(614, 380, id(footer_font), TextAlign::BASELINE_RIGHT , "Aktualisiert um %d.%m.%Y %H:%M", id(homeassistant_time).now()); /* WiFi Signal Strenght */ if(id(wifisignal).has_state()) { int x = 630; int y = 384; if (id(wifisignal).state >= -50) { // Excellent it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰤨"); ESP_LOGI("WiFi", "Excellent"); } else if (id(wifisignal).state >= -60) { // Good it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰤥"); ESP_LOGI("WiFi", "Good"); } else if (id(wifisignal).state >= -75) { // Fair it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰤢"); ESP_LOGI("WiFi", "Fair"); } else if (id(wifisignal).state >= -100) { // Weak it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰤟"); ESP_LOGI("WiFi", "Weak"); } else { // Unlikely working signal it.print(x, y, id(mdi_wifi), TextAlign::BASELINE_CENTER, "󰤫"); ESP_LOGI("WiFi", "Unlikely"); } } - id: calendar lambda: |- auto now = id(homeassistant_time).now(); const char *months[] = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}; const char *weekdays[] = {"Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"}; const int year = now.year; const int month = now.month; const int today = now.day_of_month; 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) { return (calendar_year % 4 == 0 && (calendar_year % 100 != 0 || calendar_year % 400 == 0)) ? 29 : 28; } return days[calendar_month - 1]; }; auto weekday_monday = [](int calendar_year, int calendar_month, int day) { const int offsets[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; calendar_year -= calendar_month < 3; const int sunday_first = (calendar_year + calendar_year / 4 - calendar_year / 100 + calendar_year / 400 + offsets[calendar_month - 1] + day) % 7; return (sunday_first + 6) % 7; }; auto iso_weeks_in_year = [&weekday_monday](int calendar_year) { // 28 December is always in the final ISO week of its year. const int day_of_year = 362 + ((calendar_year % 4 == 0 && (calendar_year % 100 != 0 || calendar_year % 400 == 0)) ? 1 : 0); const int weekday = weekday_monday(calendar_year, 12, 28) + 1; return (day_of_year - weekday + 10) / 7; }; auto iso_week = [&weekday_monday, &days_in_month, &iso_weeks_in_year](int calendar_year, int calendar_month, int day) { int day_of_year = day; for (int current_month = 1; current_month < calendar_month; current_month++) { day_of_year += days_in_month(calendar_year, current_month); } const int weekday = weekday_monday(calendar_year, calendar_month, day) + 1; int week = (day_of_year - weekday + 10) / 7; if (week < 1) return iso_weeks_in_year(calendar_year - 1); if (week > iso_weeks_in_year(calendar_year)) return 1; return week; }; /* LEFT PANE: agenda */ it.image(10, 10, id(c1024_logo)); it.print(100, 27, id(roboto_med_30), TextAlign::BASELINE_LEFT, "TERMINE"); it.line(10, 55, 355, 55); it.line(365, 10, 365, 352); DynamicJsonDocument calendar_doc(3072); const DeserializationError calendar_error = deserializeJson(calendar_doc, id(calendar_entries).state.c_str()); JsonArray calendar_days = calendar_doc.as(); int agenda_y = 72; bool has_events = false; bool has_hidden_events = false; if (calendar_error || calendar_days.isNull()) { it.print(18, agenda_y, id(sensor_unit), TextAlign::TOP_LEFT, "Kalenderdaten werden geladen ..."); } else if (calendar_days.size() == 0) { it.print(18, agenda_y, id(sensor_unit), TextAlign::TOP_LEFT, "Keine Termine in den nächsten Tagen."); } else { for (JsonVariant day_variant : calendar_days) { JsonObject day = day_variant.as(); JsonArray events = day["events"].as(); if (events.isNull() || events.size() == 0) continue; if (agenda_y + 38 > 345) { has_hidden_events = true; break; } const int day_number = day["day"] | 0; const char *weekday = day["weekday"] | ""; const bool is_today = (day["today"] | 0) == 1; it.printf(30, agenda_y, id(sub_sensor_font), TextAlign::TOP_CENTER, "%d", day_number); it.printf(30, agenda_y + 28, id(footer_font), TextAlign::TOP_CENTER, "%s", weekday); it.print(68, agenda_y + 2, id(footer_font), TextAlign::TOP_LEFT, is_today ? "HEUTE" : ""); it.line(58, agenda_y, 58, agenda_y + 32 + events.size() * 35); agenda_y += 36; for (JsonVariant event_variant : events) { if (agenda_y + 33 > 345) { has_hidden_events = true; break; } JsonObject event = event_variant.as(); const char *title = event["title"] | "Ohne Titel"; const char *time = event["time"] | ""; const char *meta = event["meta"] | ""; it.printf(72, agenda_y, id(sensor_unit), TextAlign::TOP_LEFT, "%s", title); it.printf(350, agenda_y, id(sensor_unit), TextAlign::TOP_RIGHT, "%s", time); it.printf(72, agenda_y + 20, id(footer_font), TextAlign::TOP_LEFT, "%s", meta); agenda_y += 35; has_events = true; } agenda_y += 5; if (has_hidden_events) break; } } if (!has_events && !calendar_error && !calendar_days.isNull() && calendar_days.size() > 0) { it.print(18, 72, id(sensor_unit), TextAlign::TOP_LEFT, "Keine Termine im sichtbaren Bereich."); } if (has_hidden_events) { it.print(72, 340, id(footer_font), TextAlign::TOP_LEFT, "Weitere Termine vorhanden ..."); } /* RIGHT TOP: month view, Monday-first with ISO week numbers */ 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); it.line(375, 28, 630, 28); it.print(384, 47, id(footer_font), TextAlign::TOP_CENTER, "KW"); for (int column = 0; column < 7; column++) { it.printf(415 + column * 30, 47, id(footer_font), TextAlign::TOP_CENTER, "%s", weekdays[column]); } const int first_weekday = weekday_monday(year, month, 1); const int month_days = days_in_month(year, month); for (int row = 0; row < 6; row++) { const int monday_day = 1 - first_weekday + row * 7; int week_year = year; int week_month = month; int week_day = monday_day; if (week_day < 1) { week_month--; if (week_month == 0) { week_month = 12; week_year--; } week_day += days_in_month(week_year, week_month); } else if (week_day > month_days) { week_day -= month_days; week_month++; if (week_month == 13) { week_month = 1; week_year++; } } const int cell_y = 69 + row * 23; it.printf(384, cell_y, id(footer_font), TextAlign::TOP_CENTER, "%02d", iso_week(week_year, week_month, week_day)); for (int column = 0; column < 7; column++) { const int day = row * 7 + column - first_weekday + 1; if (day < 1 || day > month_days) continue; const int cell_x = 415 + column * 30; if (day == today) { it.filled_rectangle(cell_x - 12, cell_y - 2, 25, 18, Color::BLACK); it.printf(cell_x, cell_y, id(footer_font), Color::WHITE, TextAlign::TOP_CENTER, "%d", day); } else { it.printf(cell_x, cell_y, id(footer_font), TextAlign::TOP_CENTER, "%d", day); } } } /* RIGHT BOTTOM: quote placeholder, intentionally inverted */ it.filled_rectangle(375, 220, 255, 130, Color::BLACK); it.print(502, 234, id(footer_font), Color::WHITE, TextAlign::TOP_CENTER, "DEMOTIVATION"); it.print(502, 267, id(sensor_unit), Color::WHITE, TextAlign::TOP_CENTER, "Es ist nie zu spät,"); it.print(502, 291, id(sensor_unit), Color::WHITE, TextAlign::TOP_CENTER, "die Erwartungen zu senken."); it.print(502, 326, id(footer_font), Color::WHITE, TextAlign::TOP_CENTER, "Zitatquelle folgt"); /* FOOTER */ it.strftime(614, 380, id(footer_font), TextAlign::BASELINE_RIGHT, "Aktualisiert um %d.%m.%Y %H:%M", now);