Use ESPHome with e-ink Displays to blend in with your home decor!

Hello, I am also in the process of creating a picture frame. After big problems with the weather integration (hourly forecast) I finally managed to get it running.

Since the time in the clock jump code is output with e.g. 7PM I would like to convert this to 19:00
I’m not sure if I have to work with timestamp_custom or if I should specify something else? Thank you

Thx that kind of worked. It tries to load it with a black background, the display goes corrupt and then it refreshes back to white/grey background. Suspect it’s an issue with my screen and ‘model’ setting. Going to have a play.

turns out I needed to set ‘model’ to ‘7.50in-bV3’.

Hi, I was struggling with this yesterday. With this you get the time in the right form.

          weather_timestamp_0: >
            {{ as_timestamp(state_attr('weather.home_hourly', 'forecast')[0].datetime) | timestamp_custom('%H:%M') }}
1 Like

Great project… I’m sure I will build something like this. :ok_hand:

But I seriously would advice to take note of this inverted ‘busy_pin’ thing. Obviously this can harm the display.

Would be good if there is a clear sign whether the display is always in power on mode or in sleep. Seems to be hard to detect.
I found something with a description how to check if the display is powered or not (but did not test it by myself):
https://github.com/esphome/issues/issues/4739

Here is something where the fix is described:
https://github.com/esphome/esphome/pull/5283

And again (but it was mentioned before) from the ESPHome docs:
https://esphome.io/components/display/waveshare_epaper.html

1 Like

Any luck ? Facing the same issue

Hi, I made my own screen last weekend. Thanks everyone for the pictures and tips. This was a nice project, I learned new things. At the bottom of the screen you can see the electricity prices that come from the Nordpool sensor. Now you no longer need a phone to check prices.
Thank you @ToHi. Yesterday I noticed black dots on my screen when the sun hit it. Today I tested it with a flashlight (my screen went black) and inverted the busy pin. Inverting also removed the error message from esp’s log that came when the screen was updated.

2 Likes

Finally managed to customize the display as desired. Thanks everyone for the code (examples). Unfortunately getting an issue when the temperature is below 0 degree. The “-” symbol is not shown correctly. Any ideas why?

That may be because you need to add it to the font glyph

Great project! I built a weather and tide display using it, really pleased with how it came out! I used an HC-SR501 PIR sensor mounted in a 3D printed case to detect motion near the display and reduce refreshes.


Here’s the code for anyone who wants to do something similar.

esphome:
  name: tidedisplay
  friendly_name: TideDisplay
  on_boot:
      priority: 200.0
      then:
        - component.update: eink_display
        - wait_until:
            condition:
              lambda: 'return id(data_updated) == true;'
              # Wait a bit longer so all the items are received
        - delay: 5s
        - logger.log: "Initial sensor data received: Refreshing display..."
        - lambda: 'id(initial_data_received) = true;'
        - script.execute: update_screen

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: ""

ota:
  password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Tidedisplay Fallback Hotspot"
    password: "x5AGryOQrAfq"

captive_portal:

# Import JSON library
json:
     
# Global variables for detecting if the display needs to be refreshed. (Thanks @paviro!)
globals:
  - id: data_updated
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: initial_data_received
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: recorded_display_refresh
    type: int
    restore_value: yes
    initial_value: '0'


# Script for updating screen - Refresh display and publish refresh count and time. (Thanks @paviro!)
script:
  - id: update_screen
    then:
      - lambda: 'id(data_updated) = false;'
      - component.update: eink_display
      - lambda: 'id(recorded_display_refresh) += 1;'
      - lambda: 'id(display_last_update).publish_state(id(homeassistant_time).now().timestamp);'
      

# Check whether the display needs to be refreshed every minute,
# based on whether new data is received or motion is detected. (Thanks @paviro!)
time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:
      - seconds: 0
        minutes: /1
        then:
          - if:
              condition:
                lambda: 'return id(data_updated) == true;'
              then:
#                - script.execute: update_screen
                - if:
                    condition:
                      binary_sensor.is_on: motion_detected
                    then:
                      - logger.log: "Sensor data updated and activity in home detected: Refreshing display..."
                      - script.execute: update_screen
                    else:
                      - logger.log: "Sensor data updated but no activity in home - skipping display refresh."
              else:
                - logger.log: "No sensors updated - skipping display refresh."


# Include custom fonts
font:
  - file: "fonts/GothamRnd-Bold.ttf"
    id: font_xtra_large
    size: 80
    glyphs:
      [ '-', '.','°', '0','1', '2', '3', '4', '5', '6', '7', '8', '9']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_large
    size: 54
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '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', '/']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_medium
    size: 30
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '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', '/']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_small
    size: 18
    # glyphs: [' ', '-', '°', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', 'M', 'I', 'N']

  # Include Material Design Icons font
  # Thanks to https://community.home-assistant.io/t/display-materialdesign-icons-on-esphome-attached-to-screen/199790/16
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_large
    size: 80
    glyphs: &mdi-weather-glyphs
      - "\U000F0590" # mdi-weather-cloudy
      - "\U000F0F2F" # mdi-weather-cloudy-alert
      - "\U000F0E6E" # mdi-weather-cloudy-arrow-right
      - "\U000F0591" # mdi-weather-fog
      - "\U000F0592" # mdi-weather-hail
      - "\U000F0F30" # mdi-weather-hazy
      - "\U000F0898" # mdi-weather-hurricane
      - "\U000F0593" # mdi-weather-lightning
      - "\U000F067E" # mdi-weather-lightning-rainy
      - "\U000F0594" # mdi-weather-night
      - "\U000F0F31" # mdi-weather-night-partly-cloudy
      - "\U000F0595" # mdi-weather-partly-cloudy
      - "\U000F0F32" # mdi-weather-partly-lightning
      - "\U000F0F33" # mdi-weather-partly-rainy
      - "\U000F0F34" # mdi-weather-partly-snowy
      - "\U000F0F35" # mdi-weather-partly-snowy-rainy
      - "\U000F0596" # mdi-weather-pouring
      - "\U000F0597" # mdi-weather-rainy
      - "\U000F0598" # mdi-weather-snowy
      - "\U000F0F36" # mdi-weather-snowy-heavy
      - "\U000F067F" # mdi-weather-snowy-rainy
      - "\U000F0599" # mdi-weather-sunny
      - "\U000F0F37" # mdi-weather-sunny-alert
      - "\U000F14E4" # mdi-weather-sunny-off
      - "\U000F059A" # mdi-weather-sunset
      - "\U000F0F38" # mdi-weather-tornado
      - "\U000F059D" # mdi-weather-windy
      - "\U000F059E" # mdi-weather-windy-variant
  - file: "fonts/materialdesignicons-webfont.ttf"
    id: font_icons_medium
    size: 36
    glyphs:
      - "\U000F10C2" # Temperature High
      - "\U000F10C3" # Temperature Low
      - "\U000F050F" # mdi-thermometer
      - "\U000F029A" # mdi-gauge
      - "\U000F058E" # mdi-water-percent
      - "\U000F07E4" # mdi-molecule-co2
      - "\U000F059D" # mdi-weather-windy
      - "\U000F0140" # mdi-chevron-down
      - "\U000F0143" # mdi-chevron-up
      - "\U000F078D" # mdi-waves
      - "\U000F059B" # mdi-sunset
      - "\U000F059C" # mdi-sunrise
  - file: "fonts/materialdesignicons-webfont.ttf"
    id: font_icons_small
    size: 18
    glyphs:
      - "\U000F059D" # mdi-weather-windy

binary_sensor:
  - platform: gpio
    pin: GPIO32
    name: "PIR Sensor"
    id: motion_detected
    device_class: motion

sensor:
  # Create sensors for monitoring Weatherman remotely.
  - platform: template
    name: "Tide Display - Display Last Update"
    device_class: timestamp
    entity_category: "diagnostic"
    id: display_last_update
    
  - platform: template
    name: "Tide Display - Recorded Display Refresh"
    accuracy_decimals: 0
    unit_of_measurement: "Refreshes"
    state_class: "total_increasing"
    entity_category: "diagnostic"
    lambda: 'return id(recorded_display_refresh);'
  
  - platform: wifi_signal
    name: "Tide Display - WiFi Signal Strength"
    id: wifisignal
    unit_of_measurement: "dBm"
    entity_category: "diagnostic"
    update_interval: 60s

  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: temperature
    id: weather_temperature_now
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: humidity
    id: weather_humidity_now
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: pressure
    id: weather_pressure_now
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: wind_speed
    id: weather_wind_speed_now
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: wind_bearing
    id: weather_wind_bearing_now
    on_value:
      then:
       - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.holyhead_tide_next_high_tide_height
    id: tide_next_high_height
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.holyhead_tide_next_low_tide_height
    id: tide_next_low_height
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

text_sensor:
  - platform: homeassistant
    entity_id: weather.forecast_home
    id: weather_condition_now
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
        
  - platform: homeassistant
    entity_id: weather.forecast_home
    attribute: forecast
    id: weather_forecast_5
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.holyhead_tide_next_high_tide_time
    id: tide_next_high_time
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.holyhead_tide_next_low_tide_time
    id: tide_next_low_time
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
     
  - platform: homeassistant
    entity_id: sensor.sun_next_rising_time
    id: sun_next_rising
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.sun_next_setting_time
    id: sun_next_setting
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
     

color:
  - id: color_bg
    red: 0%
    green: 0%
    blue: 0%
    white: 0%
  - id: color_text
    red: 0%
    green: 0%
    blue: 0%
    white: 100%


# Pins for Waveshare ePaper ESP Board
spi:
  clk_pin: GPIO13
  mosi_pin: GPIO14

# Now render everything on the ePaper screen.
display:
  - platform: waveshare_epaper
    id: eink_display
    cs_pin: GPIO15
    dc_pin: GPIO27
    busy_pin:
      number: GPIO25
      inverted: True
    reset_pin: GPIO26
    reset_duration: 2ms
    model: 7.50inV2
    update_interval: never
    rotation: 90°
    lambda: |-
      #define xres 480 
      #define yres 800
      #define x_pad 10 // border padding
      #define y_pad 60 // border padding      
      #define val_pad 70 // padding before value
      #define icon_y_pad 8 //padding after icons      
      #define y_header_weather 40
      #define y_header_forecast 290
      #define y_header_tides 570

      int y = 10;
      char str[17];
      time_t Time;

      auto wind_dir = [](float dir) 
      { 
        if (11.25 < dir && dir <= 33.75) return "NNE";
        else if (33.75 < dir && dir <= 56.25) return "NE";
        else if (56.25 < dir && dir <= 78.75) return "ENE";
        else if (78.75 < dir && dir <= 101.25) return "E";
        else if (101.25 < dir && dir <= 123.75) return "ESE";
        else if (123.75 < dir && dir <= 146.25) return "SE";
        else if (146.25 < dir && dir <= 168.75) return "SSE";
        else if (168.75 < dir && dir <= 191.25) return "S";
        else if (191.25 < dir && dir <= 213.75) return "SSW";
        else if (213.75 < dir && dir <= 236.25) return "SW";
        else if (236.25 < dir && dir <= 258.75) return "WSW";
        else if (258.75 < dir && dir <= 281.25) return "W";
        else if (281.25 < dir && dir <= 303.75) return "WNW";
        else if (303.75 < dir && dir <= 326.25) return "NW";
        else if (326.25 < dir && dir <= 348.75) return "NNW";
        else return "N";
      };

      static std::map<int, const char *> DoW {
       { 0, "Sunday"},
       { 1, "Monday"},
       { 2, "Tuesday"},
       { 3, "Wednesday"},
       { 4, "Thursday"},
       { 5, "Friday"},
       { 6, "Saturday"},
      };

      auto weather_icon = [](std::string condition) {
        // Map weather states to MDI characters.
        static std::map<std::string, std::string> weather_icon_map
        {
          {"clear-night", "\U000F0594"},
          {"cloudy", "\U000F0590"},
          {"cloudy-alert", "\U000F0F2F"},
          {"cloudy-arrow-right", "\U000F0E6E"},
          {"fog", "\U000F0591"},
          {"hail", "\U000F0592"},
          {"hazy", "\U000F0F30"},
          {"hurricane", "\U000F0898"},
          {"lightning", "\U000F0593"},
          {"lightning-rainy", "\U000F067E"},
          {"night", "\U000F0594"},
          {"night-partly-cloudy", "\U000F0F31"},
          {"partlycloudy", "\U000F0595"},
          {"partly-lightning", "\U000F0F32"},
          {"partly-rainy", "\U000F0F33"},
          {"partly-snowy", "\U000F0F34"},
          {"partly-snowy-rainy", "\U000F0F35"},
          {"pouring", "\U000F0596"},
          {"rainy", "\U000F0597"},
          {"snowy", "\U000F0598"},
          {"snowy-heavy", "\U000F0F36"},
          {"snowy-rainy", "\U000F067F"},
          {"sunny", "\U000F0599"},
          {"sunny-alert", "\U000F0F37"},
          {"sunny-off", "\U000F14E4"},
          {"tornado", "\U000F0F38"},
          {"windy", "\U000F059D"},
          {"windy-variant", "\U000F059E"},
        };
        return weather_icon_map[condition].c_str();
      };

      // Show loading screen before data is received.
      if (id(initial_data_received) == false) {
        it.printf(240, 390, id(font_medium), color_text, TextAlign::TOP_CENTER, "WAITING FOR DATA...");
      } else {
        // Current Weather Section
        it.printf(x_pad, y_header_weather, id(font_medium), TextAlign::BASELINE_LEFT, "Current Conditions");
        it.line(x_pad, y_header_weather+5, xres-x_pad, y_header_weather+5);

        // Temperature
        if(id(weather_temperature_now).has_state())
          it.printf(xres - x_pad, y_header_weather+70, id(font_large), TextAlign::BASELINE_RIGHT, "%2.1f°C", id(weather_temperature_now).state);

        // Humidity
        it.printf(x_pad, y_header_weather+60, id(font_icons_medium), TextAlign::BASELINE_LEFT, "\U000F058E");
        if(id(weather_humidity_now).has_state())
          it.printf(x_pad+40, y_header_weather+60, id(font_medium), TextAlign::BASELINE_LEFT, "%2.0f%%", id(weather_humidity_now).state);

        // Pressure
        it.printf(x_pad, y_header_weather+105, id(font_icons_medium), TextAlign::BASELINE_LEFT, "\U000F029A");
        if(id(weather_pressure_now).has_state())
          it.printf(x_pad+40, y_header_weather+105, id(font_medium), TextAlign::BASELINE_LEFT, "%4.0f hPa", id(weather_pressure_now).state);

        // Wind
        it.printf(x_pad, y_header_weather+150, id(font_icons_medium), TextAlign::BASELINE_LEFT, "\U000F059D");
        if(id(weather_wind_speed_now).has_state() && id(weather_wind_bearing_now).has_state())
          it.printf(x_pad+40, y_header_weather+150, id(font_medium), TextAlign::BASELINE_LEFT, "%2.1f km/h %s", id(weather_wind_speed_now).state, wind_dir(id(weather_wind_bearing_now).state));  

        // Icon
        if(id(weather_condition_now).has_state())
          it.printf(xres-x_pad, y_header_weather+150, id(font_mdi_large), TextAlign::BASELINE_RIGHT, "%s", weather_icon(id(weather_condition_now).state));

        // Print sunrise and sunset times
        it.printf(x_pad, y_header_weather+195, id(font_icons_medium), TextAlign::BASELINE_LEFT, "\U000F059C");
        if(id(sun_next_rising).has_state())
          it.printf(x_pad + 40, y_header_weather+195, id(font_medium), TextAlign::BASELINE_LEFT, "%s", id(sun_next_rising).state.c_str());

        it.printf(x_pad + 160, y_header_weather+195, id(font_icons_medium), TextAlign::BASELINE_LEFT, "\U000F059B");
        if(id(sun_next_setting).has_state ())
          it.printf(x_pad + 160 + 40, y_header_weather+195, id(font_medium), TextAlign::BASELINE_LEFT, "%s", id(sun_next_setting).state.c_str());

        // Forecast Weather Section
        it.printf(x_pad, y_header_forecast, id(font_medium), TextAlign::BASELINE_LEFT, "Forecast");
        it.line(x_pad, y_header_forecast+5, xres-x_pad, y_header_forecast+5);
        if (id(weather_forecast_5).has_state()) {
          int wxx = x_pad - 5;
          int wyy = y_header_forecast + 30;
          int forday = id(homeassistant_time).now().day_of_week; // HA DoW is 1 based. Array is 0 based so this is "tomorrow"
          DynamicJsonDocument doc(2048);
          deserializeJson(doc, (id(weather_forecast_5).state.c_str()));
          JsonArray root = doc.as<JsonArray>();
          
          for (int i = 0; i < 3; ++i) {
              JsonObject root_x = root[i];
              std::string root_0_condition = root_x["condition"];
              float root_0_temperature = root_x["temperature"];
              float root_0_wind_speed = root_x["wind_speed"];
              float root_0_wind_bearing = root_x["wind_bearing"];

              it.printf(wxx + 70, wyy, id(font_small), TextAlign::TOP_CENTER, "%s", DoW[(forday + i) % 7]);
              it.printf(wxx + 70, wyy + 30, id(font_mdi_large), TextAlign::TOP_CENTER, "%s", weather_icon(root_0_condition));
              it.printf(wxx + 70, wyy + 120, id(font_medium), TextAlign::TOP_CENTER, "%.0f°C", root_0_temperature);

              it.printf(wxx, wyy+180, id(font_icons_small), TextAlign::BASELINE_LEFT, "\U000F059D");
              it.printf(wxx+20, wyy+180, id(font_small), TextAlign::BASELINE_LEFT, "%.0f km/h %s", root_0_wind_speed, wind_dir(root_0_wind_bearing));  

              wxx += 160;
          }
        }
      
        // Tides Section
        it.printf(x_pad, y_header_tides, id(font_medium), TextAlign::BASELINE_LEFT, "Next Tides");
        it.line(x_pad, y_header_tides+5, xres-x_pad, y_header_tides+5);

        it.printf(x_pad, y_header_tides+60, id(font_icons_medium), TextAlign::BASELINE_LEFT, "\U000F0143");
        if(id(tide_next_high_time).has_state() && id(tide_next_high_height).has_state())
          it.printf(x_pad + 40, y_header_tides+60, id(font_medium), TextAlign::BASELINE_LEFT, "High %s (%.2fm)", id(tide_next_high_time).state.c_str(), id(tide_next_high_height).state);  
        it.printf(x_pad, y_header_tides+105, id(font_icons_medium), TextAlign::BASELINE_LEFT, "\U000F0140");
        if(id(tide_next_low_time).has_state() && id(tide_next_low_height).has_state())
          it.printf(x_pad + 40, y_header_tides+105, id(font_medium), TextAlign::BASELINE_LEFT, "Low %s (%.2fm)", id(tide_next_low_time).state.c_str(), id(tide_next_low_height).state); 

        // Refresh Timestamp
        Time = id(homeassistant_time).now().timestamp;
        strftime(str, sizeof(str), "%H:%M %x", localtime(&Time));
        it.printf(240, yres-80, id(font_small), color_text, TextAlign::TOP_CENTER, "Refreshed at %s", str);
      }
1 Like

Many thanks @mpredfearn
which forecast integration do you use for?

entity_id: weather.forecast_home

Looks nice; willing to share the yaml?

I recently purchased the components to start putting this together myself but I’m having issues with display refreshes after the 1st taking 20s+ to run. I’m running HA on a Raspberry Pi 4 8GB. This is what stands out in the logs:

[16:50:42][D][main:020]: Initial sensor data received: Refreshing display...
[16:50:42][I][waveshare_epaper:1663]: Power on the display and hat
[16:50:53][E][waveshare_epaper:1608]: Timeout while displaying image!
[16:51:05][E][waveshare_epaper:1608]: Timeout while displaying image!
[16:51:05][W][component:214]: Component esphome.coroutine took a long time for an operation (23.18 s).
[16:51:05][W][component:215]: Components should block for at most 20-30ms.

Is this related to faulty hardware? The only difference between OP’s setup and mine is I use a RESTapi for transit information.

Partially resolved as per this issue

Under the display settings,:

busy_pin: GPIO25

needs to change to:

busy_pin:
      number: GPIO25
      inverted: true

It’s now taking 8s to refresh the display. Is that the expected amount of time?

Has anyone seen this Waveshare ESP32 ePaper display? I haven’t been able to get this one to work.

In case, other users have a bad picture. Setting it to A and On, solved my problem (before it was B and on).

Hello everyone. Really inspiring ideas all through the post!

I was wondering if anyone have tried on a bigger screen (i was hoping i could use a 10" e-ink display on my project), and if so, what model did you use.
I’ve came across this https://aliexpress.com/item/1005005413312620.html but am unsure if i’d be able to get it working with the referenced ESP32 e-ink driver board.

For a second project i have in mind, i’d be using also a 10" e-ink display but with touch. Would this be possible assuming i’d be using the rest of the mentioned parts?

Any input is much appreciated!

Such a nice project.

I am struggling to make my google calendar work on the screen.
Does anyone have the code for both Home Assistant and ESPHome to make it work ?

I have seen people taking calendar results through Nodered but can’t find a way yet to make it work.

Thanks in advance !
@blackie333
@cbhiii

Hi, I don’t use Nodered, but few template sensors in HA containing text array for multiple days values.
Then in ESPhome these array sensor values are decoded to single day values needed for display.
It took me a lot of time and testing to make it work as intended and it still contains a lot of debug stuff…hope it helps.
Wanted to attach both full config files but are too big so had to split them to parts.
see the related part of HA configuration.yaml below:

        # Daily Forecast string sensor all-in-one - condition/temps/precipitation
    daily_forecast_string:
      friendly_name: "Daily Forecast string"
      # conversion of UTF-32 code to UTF-8 character: https://onlinetools.com/utf8/convert-utf32-to-utf8
      value_template: >-
        {% set icon = {
        "cloudy": "󰖐",
        "cloudy-alert": "󰼯",
        "cloudy-arrow-right": "󰹮",
        "cloudy-clock": "󱣶",
        "exceptional": "󰼸",
        "fog": "󰖑",
        "hail": "󰖒",
        "hazy": "󰼰",
        "hurricane": "󰢘",
        "lightning": "󰖓",
        "lightning-rainy": "󰙾",
        "clear-night": "󰖔",
        "night-partly-cloudy": "󰼱",
        "partlycloudy": "󰖕",
        "partly-lightning": "󰼲",
        "partly-rainy": "󰼳",
        "partly-snowy": "󰼴",
        "partly-snowy-rainy": "󰼵",
        "pouring": "󰖖",
        "rainy": "󰖗",
        "snowy": "󰖘",
        "snowy-heavy": "󰼶",
        "snowy-rainy": "󰙿",
        'sunny': '󰖙',
        "sunny-alert": "󰼷",
        "sunny-off": "󱓤",
        "sunset": "󰖚",
        "sunset-down": "󰖛",
        "sunset-up": "󰖜",
        "tornado": "󰼸",
        "windy": "󰖝",
        "windy-variant": "󰖞",
        } %}
        {%- set days = {'Mon':'Po','Tue':'Ut','Wed':'St','Thu':'Št','Fri':'Pi','Sat':'So','Sun':'Ne'} -%}
        {%- for state in states.sensor.weather_forecast_daily.attributes.forecast[0:7] -%}
            {{ days[as_timestamp(state.datetime)| timestamp_custom("%a")] }};{{state.templow| round(0)}}/{{ state.temperature| round(0) }}°C;{{ state.precipitation| round(0)| replace('.', ',') }}mm;{{icon[state.condition]}}#
        {%- endfor -%}

    # Hourly Forecast string sensor all-in-one - condition/temps/precipitation (use 'weather.home' or 'weather.openweathermap')
    hourly_forecast_string:
      friendly_name: "Hourly Forecast string"
      # conversion of UTF-32 code to UTF-8 character: https://onlinetools.com/utf8/convert-utf32-to-utf8
      value_template: >-
        {% set icon = {
        "cloudy": "󰖐",
        "cloudy-alert": "󰼯",
        "cloudy-arrow-right": "󰹮",
        "cloudy-clock": "󱣶",
        "exceptional": "󰼸",
        "fog": "󰖑",
        "hail": "󰖒",
        "hazy": "󰼰",
        "hurricane": "󰢘",
        "lightning": "󰖓",
        "lightning-rainy": "󰙾",
        "clear-night": "󰖔",
        "night-partly-cloudy": "󰼱",
        "partlycloudy": "󰖕",
        "partly-lightning": "󰼲",
        "partly-rainy": "󰼳",
        "partly-snowy": "󰼴",
        "partly-snowy-rainy": "󰼵",
        "pouring": "󰖖",
        "rainy": "󰖗",
        "snowy": "󰖘",
        "snowy-heavy": "󰼶",
        "snowy-rainy": "󰙿",
        'sunny': '󰖙',
        "sunny-alert": "󰼷",
        "sunny-off": "󱓤",
        "sunset": "󰖚",
        "sunset-down": "󰖛",
        "sunset-up": "󰖜",
        "tornado": "󰼸",
        "windy": "󰖝",
        "windy-variant": "󰖞",
        } %}
        {%- for state in states.sensor.weather_forecast_hourly.attributes.forecast[0:6] -%}
            {{ as_timestamp(state.datetime)| timestamp_custom("%H") }};{{ state.temperature| round(0) }}°C;{{ state.precipitation | round(0) | replace('.', ',') }}mm;{{icon[state.condition]}}#
        {%- endfor -%}

    # Holidays Calendar string sensor all-in-one - Date/Name
    daily_holidays_string:
      friendly_name: "Daily Holidays string"
      value_template: >-
        {% set icon = {
        "briefcase": "󰠔",
        "sofa": "󰒹",
        "flag-variant": "󰉀",
        } %}
        {%- set today = as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0).replace(microsecond=0)) | timestamp_custom("%s") | int -%}
        {%- set days = {'Mon':'Po','Tue':'Ut','Wed':'St','Thu':'Št','Fri':'Pi','Sat':'So','Sun':'Ne'} -%}
        {%- for i in range(0,7) -%}
          {%- set date = today + i*(24*60*60) -%}
          {%- set ns = namespace(datetype='briefcase',daysummary = ' ') -%}
          {%- for state in states.sensor.events_holidays.attributes.events[0:7] -%}
            {%- if ( as_timestamp(state.start) | timestamp_custom("%s") | int == date ) -%}
              {%- set ns.datetype = 'flag-variant' -%}
              {% if ns.daysummary == ' ' -%}
                {%- set ns.daysummary = state.summary | string -%}
              {%- else -%}
                {%- set ns.daysummary = ns.daysummary ~ ',' ~ state.summary | string -%}
              {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
          {%- if ( ns.datetype == 'briefcase' ) and ( (date | timestamp_custom("%a") ) in ('Sat', 'Sun') ) -%}
            {%- set ns.datetype = 'sofa' -%}
            {{ days[date| timestamp_custom("%a")] }};{{ 'weekend'}};{{icon[ns.datetype]}}#
          {%- elif ( ns.datetype == 'briefcase' ) -%}
            {{ days[date| timestamp_custom("%a")] }};{{ 'workday'}};{{icon[ns.datetype]}}#
          {%- else -%}
            {{ days[date| timestamp_custom("%a")] }};{{ns.daysummary}};{{icon[ns.datetype]}}#
          {%- endif -%}
        {%- endfor -%}

    # NameDays Calendar string sensor all-in-one - Date/Name
    daily_namedays_string:
      friendly_name: "Daily Namedays string"
      value_template: >-
        {% set icon = {
        "calendar-blank": "󰃮",
        "calendar-account": "󰻗",
        } %}
        {%- set today = as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0).replace(microsecond=0)) | timestamp_custom("%s") | int -%}
        {%- set lastdate = (today + (6*24*60*60)) -%}
        {%- set days = {'Mon':'Po','Tue':'Ut','Wed':'St','Thu':'Št','Fri':'Pi','Sat':'So','Sun':'Ne'} -%}
        {%- for i in range(0,7) -%}
          {%- set date = today + i*(24*60*60) -%}
          {%- set ns = namespace(datetype='calendar-blank',daysummary = ' ') -%}
          {%- for state in states.sensor.events_namedays.attributes.events[0:14] -%}
            {%- if ( as_timestamp(state.start) | timestamp_custom("%s") | int == date ) -%}
              {%- set ns.datetype = 'calendar-account' -%}
              {% if ns.daysummary == ' ' -%}
                {%- set ns.daysummary = state.summary | string -%}
              {%- else -%}
                {%- set ns.daysummary = ns.daysummary ~ ',' ~ state.summary | string -%}
              {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
          {{ days[date| timestamp_custom("%a")] }};{{ns.daysummary}};{{icon[ns.datetype]}}#
        {%- endfor -%}

    # Personal Agenda Daily Summary string sensor all-in-one - Events Day/Count
    daily_agenda_string:
      friendly_name: "Daily Agenda string"
      value_template: >-
        {% set icon = {
        "calendar-blank": "󰃮",
        "calendar-star": "󰧓",
        "numeric-1-box": "󰎤","numeric-2-box": "󰎧","numeric-3-box": "󰎪","numeric-4-box": "󰎭","numeric-5-box": "󰎱","numeric-6-box": "󰎳","numeric-7-box": "󰎶","numeric-8-box": "󰎹","numeric-9-box": "󰎼",
        } %}
        {%- set today = as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0).replace(microsecond=0)) | timestamp_custom("%s") | int -%}
        {%- set lastdate = (today + (6*24*60*60)) -%}
        {%- set days = {'Mon':'Po','Tue':'Ut','Wed':'St','Thu':'Št','Fri':'Pi','Sat':'So','Sun':'Ne'} -%}
        {%- set ct = namespace(index=0,icons='') -%}
        {%- for i in range(0,7) -%}
          {%- set ns = namespace(index=0,datetype='calendar-blank',daysummary=' ',icons='') -%}
          {%- set date = today + i*(24*60*60) -%}
          {% for state in states.sensor.events_personal_agenda.attributes.events[0:8] %}
            {%- set ct.index = ct.index + 1 -%}
            {%- if ( as_timestamp(state.end) | timestamp_custom("%s") | int > date ) and ( as_timestamp(state.start) | timestamp_custom("%s") | int < date+24*60*60 ) -%}
              {% if ns.daysummary == ' ' -%}
                {%- set ns.daysummary = state.summary | string -%}
                {%- set ns.index = ct.index -%}
              {%- else -%}
                {%- set ns.daysummary = ns.daysummary ~ ',' ~ state.summary | string -%}
                {%- set ns.index = ns.index~ ',' ~ ct.index  -%}
              {%- endif -%}
              {%- set ns.datetype = 'numeric-'~ct.index~'-box' -%}
              {%- set ns.icons = ns.icons~icon[ns.datetype] -%}
            {%- endif -%}
          {%- endfor -%}
          {{ days[date| timestamp_custom("%a")] }};{{ns.index}};{{ns.daysummary}};{{ns.icons}}#
          {%- set ct.index = 0 -%}
        {%- endfor -%}

    # Personal Detailed Agenda string sensor all-in-one - Events start/end/message/location
    detailed_agenda_string:
      friendly_name: "Detailed Agenda string"
      value_template: >-
        {% set icon = {
        "calendar-clock": "󰃰",
        "calendar": "󰃭",
        "calendar-range": "󰙹",
        "numeric-1-box": "󰎤","numeric-2-box": "󰎧","numeric-3-box": "󰎪","numeric-4-box": "󰎭","numeric-5-box": "󰎱","numeric-6-box": "󰎳","numeric-7-box": "󰎶","numeric-8-box": "󰎹","numeric-9-box": "󰎼",
        } %}
        {%- set today = as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0).replace(microsecond=0)) | timestamp_custom("%s") | int -%}
        {%- set lastdate = (today + (7*24*60*60)) -%}
        {%- set days = {'Mon':'Po','Tue':'Ut','Wed':'St','Thu':'Št','Fri':'Pi','Sat':'So','Sun':'Ne'} -%}
        {%- set ns = namespace(index=0,datetype='calendar-blank',schedule='',summary='',icon='') -%}
        {%- for state in states.sensor.events_personal_agenda.attributes.events[0:8] -%}
          {%- if ( as_timestamp(state.start) | timestamp_custom("%s") | int <= today ) or ( as_timestamp(state.start) | timestamp_custom("%s") | int < lastdate ) -%}
            {%- set ns.index = ns.index + 1 -%}
            {% if as_timestamp(state.start) == as_timestamp(state.end) - 24*60*60 -%}
              {%- set ns.icon = icon['calendar'] -%}
              {%- set ns.schedule = days[(as_timestamp(state.start) | timestamp_custom("%a"))] -%}
            {%- elif as_timestamp(state.start) | timestamp_custom("%d.%m.%Y") == as_timestamp(state.end) | timestamp_custom("%d.%m.%Y") -%}
              {%- set ns.icon = icon['calendar-clock'] -%}
              {%- set ns.schedule = days[(as_timestamp(state.start) | timestamp_custom("%a"))]~(as_timestamp(state.start) | timestamp_custom(" %H:%M")) ~ '-' ~ (as_timestamp(state.end) | timestamp_custom("%H:%M")) -%}
            {%- else -%}
              {%- set ns.icon = icon['calendar-range'] -%}
              {%- set ns.schedule = days[(as_timestamp(state.start) | timestamp_custom("%a"))]~(as_timestamp(state.start) | timestamp_custom(" %H:%M")) ~ ' - ' ~ days[(as_timestamp(state.end) | timestamp_custom("%a"))]~(as_timestamp(state.end) | timestamp_custom(" %H:%M")) -%}
            {%- endif -%}
            {%- set ns.summary = state.summary | string -%}
            {%- set ns.datetype = 'numeric-'~ns.index~'-box' -%}
            {{ns.index}};{{ ns.schedule }} » "{{ns.summary}}";'';{{icon[ns.datetype]~ns.icon}}#
          {%- endif -%}
        {%- endfor -%}

    # System Status History Detailed string sensor all-in-one - Status ID/Changed
    detailed_system_status_string:
      friendly_name: "Detailed System Status string"
      value_template: >-
        {% set icon = {
        "clock-start": "󰅕",
        "progress-clock": "󰦖",
        "alarm": "󰀠",
        "alarm-bell": "󰞎",
        "power-plug-off": "󰚦",
        "power-plug-off-outline": "󱐤",
        "power": "󰐥",
        "play-circle": "󰐌",
        "play-box-outline": "󰐋",
        "signal-off": "󰞃",
        "network-off": "󰲛",
        "lan-disconnect": "󰌙",
        "car-cruise-control": "󰵠",
        "memory": "󰍛",
        "disk": "󰋊",
        "speedometer": "󰓅",
        } %}
        {% from 'formatter.jinja' import format_historical_datetime_local %}
        {% from 'formatter.jinja' import format_historical_datetime %}
        {%- set today = as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0).replace(microsecond=0)) | timestamp_custom("%s") | int -%}
        {%- set lastdate = (today + (6*24*60*60)) -%}
        {%- set days = {'Mon':'Po','Tue':'Ut','Wed':'St','Thu':'Št','Fri':'Pi','Sat':'So','Sun':'Ne'} -%}
        {%- set ns = namespace(index=0,datetype='calendar-blank',schedule='',summary='',icon='') -%}
        {%- set timesensor = as_timestamp(states.sensor.network_status.last_changed) -%}
        {{-'1'}};{{'Modem'}};-{{format_historical_datetime(timesensor)}};{{icon["signal-off"]}}#
        {%- set timesensor = as_timestamp(states.binary_sensor.internet_online.last_changed) -%}
        {{-'2'}};{{'Internet'}};-{{format_historical_datetime(timesensor)}};{{icon["lan-disconnect"]}}#
        {%- set timesensor = as_timestamp(states.sensor.uptime.state) -%}
        {{-'3'}};{{'HA Start'}};-{{format_historical_datetime(timesensor)}};{{icon["play-box-outline"]}}#
        {%- set timesensor = states.sensor.last_boot.state -%}
        {%- set timestring = relative_time(strptime(timesensor, '%Y-%m-%dT%H:%M:%S%z')) | replace ('days','dni') | replace ('day','deň') | replace ('hours','hodín') | replace ('hour','hodina') | replace ('minutes','minút') | replace ('minute','minúta')-%}
        {{-'4'}};{{'Boot'}};-{{format_historical_datetime(timesensor)}};{{icon["power"]}}#
        {%- set timevalue = strptime(states.var.last_alarm_time.state, '%Y-%m-%d, %H:%M','not_decoded') -%}
        {%- if timevalue != 'not_decoded' -%}{%- set timesensor = format_historical_datetime(as_timestamp(timevalue))-%}
        {%- else-%}{%- set timesensor = '???'-%}{%-endif-%}
        {{-'5'}};{{'Alarm'}};{{'-'~timesensor}};{{icon["alarm-bell"]}}#
        {%- set timevalue = strptime(states.sensor.ups_transfer_to_battery.state,'%Y-%m-%d %H:%M:%S %z','not_decoded') -%}
        {%- if timevalue != 'not_decoded' -%}{%- set timesensor = format_historical_datetime(as_timestamp(timevalue))-%}
        {%- else-%}{%- set timesensor = '???'-%}{%-endif-%}
        {{-'6'}};{{'UPS'}};{{'-'~format_historical_datetime(timesensor)}};{{icon["power-plug-off-outline"]}}#
        {{-'21'}};{{'CPU Load'}};{{states.sensor.load_15m.state}};{{icon["car-cruise-control"]}}#
        {{-'22'}};{{'Memory'}};{{states.sensor.memory_use_percent.state~'%'}};{{icon["memory"]}}#
        {{-'23'}};{{'Storage'}};{{states.sensor.disk_use_percent.state~'%'}};{{icon["disk"]}}#
        {{-'24'}};{{'Download'}};{{states.sensor.internet_download.state~'Mb'}};{{icon["speedometer"]}}#


template:
    # Daily Forecast sensor all-in-one - day/condition/temps/precipitation (use 'weather.home' or 'weather.openweathermap' input sensor)
    # Export forecast "forecast_daily" data to a variable
  - trigger:
      - platform: homeassistant
        event: start
      - platform: time_pattern
        hours: /2
    action:
      - service: weather.get_forecasts
        data:
          type: daily
        target:
          entity_id: weather.openweathermap # use weather.openweathermap, weather.home works for 6 days only
        response_variable: forecast_daily
    sensor:
      # Get forecast data from a variable
    - name: Weather Forecast Daily
      unique_id: weather_forecast_daily
      state: "{{ forecast_daily['weather.openweathermap'].forecast | count() }}"
      attributes:
        forecast: "{{ forecast_daily['weather.openweathermap'].forecast }}"
    # Hourly Forecast sensor all-in-one - hour/condition/temps/precipitation (use 'weather.home' or 'weather.openweathermap' input sensor)
    # Export forecast "forecast_hourly" data to a variable
  - trigger:
      - platform: homeassistant
        event: start
      - platform: time_pattern
        minutes: /30
    action:
      - service: weather.get_forecasts
        data:
          type: hourly
        target:
          entity_id: weather.home # use weather.home, weather.openweathermap doesn't work
        response_variable: forecast_hourly
    sensor:
      # Get forecast data from a variable
    - name: Weather Forecast Hourly
      unique_id: weather_forecast_hourly
      state: "{{ forecast_hourly['weather.home'].forecast | count() }}"
      attributes:
        forecast: "{{ forecast_hourly['weather.home'].forecast }}"
    # Daily Agenda sensor all-in-one - day/event/location
    # Export personal_calendar events to a variable
  - trigger:
      - platform: homeassistant
        event: start
      - platform: time_pattern
        minutes: /15
    action:
      - service: calendar.get_events
        target:
          entity_id: "{{ 'calendar.'~states.input_text.personal_calendar.state }}"
        data:
          duration:
            hours: 672
        response_variable: events_personal_agenda
    sensor:
      # Get events from a variable
    - name: Events Personal Agenda
      unique_id: events_personal_agenda
      state: "{{ events_personal_agenda['calendar.'~states.input_text.personal_calendar.state].events | count() }}"
      icon: mdi:calendar
      attributes:
        events: "{{ events_personal_agenda['calendar.'~states.input_text.personal_calendar.state].events }}"
    # Daily Holidays sensor all-in-one - day/event/location
    # Export Calendar "sviatky_na_slovensku" events to a variable
  - trigger:
      - platform: homeassistant
        event: start
      - platform: time_pattern
        hours: /12
    action:
      - service: calendar.get_events
        target:
          entity_id: calendar.sviatky_na_slovensku
        data:
          duration:
            hours: 168
        response_variable: events_holidays
    sensor:
      # Get events from a variable
    - name: Events Holidays
      unique_id: events_holidays
      state: "{{ events_holidays['calendar.sviatky_na_slovensku'].events | count() }}"
      icon: mdi:calendar
      attributes:
        events: "{{ events_holidays['calendar.sviatky_na_slovensku'].events }}"
    # Daily Namedays sensor all-in-one - day/event/location
    # Export Calendar "slovenske_mena" events to a variable
  - trigger:
      - platform: homeassistant
        event: start
      - platform: time_pattern
        hours: /12
    action:
      - service: calendar.get_events
        target:
          entity_id: calendar.slovenske_mena
        data:
          duration:
            hours: 168
        response_variable: events_namedays
    sensor:
      # Get events from a variable
    - name: Events NameDays
      unique_id: events_namedays
      state: "{{ events_namedays['calendar.slovenske_mena'].events | count() }}"
      icon: mdi:calendar
      attributes:
        events: "{{ events_namedays['calendar.slovenske_mena'].events }}"
  • related ESPhome config part1:
# Thanks a lot to jeroen85, kotope, tolnai, Hellis81 + others
substitutions:
  device_name: inkplate-10
  friendly_name: "Inkplate 10"
  wake_time: 20s #can be as long as needed to get data 
  night_sleep_time: 2h # sleep time after midnight
  sleep_time: 30min # normal sleep time
  short_sleep_time: 120s # sleep time after data timeout
  screen_update_time: 7s
  sensors_count: "17"

globals:
  - id: global_boot_cycle_started
    type: int
    initial_value: '0'
    restore_value: no
  - id: global_boot_cycle_active
    type: bool
    initial_value: 'false'
    restore_value: no
  - id: global_boot_stage_reached
    type: int
    initial_value: '0'
    restore_value: no
  - id: global_sensors_updated
    type: int
    initial_value: '0'
    restore_value: no
  - id: global_display_refresh_count
    type: int
    initial_value: '0'
    restore_value: no
  - id: global_deep_sleep_mode
    type: bool
    initial_value: 'false'
    restore_value: no
  - id: global_data_timeout_occurred
    type: bool
    initial_value: 'false'
    restore_value: no

esphome:
  name: ${device_name}
  friendly_name:  ${friendly_name}
  name_add_mac_suffix: false
  project:
    name: esphome.inkplate-10-dashboard
    version: "1.0"
  includes:
    - eink-common.h
  on_boot:
      priority: -100.0
      then:
      - if:
          condition: # Show a logo when cold boot (0=ColdBoot, 4=WakeUp)
            - lambda: 'return ( esp_sleep_get_wakeup_cause() == 0 );'
          then:
          - component.update: eink_display
      - logger.log: "Booted, waiting for API connection..."
      - lambda: 'id(global_boot_cycle_started) = id(esptime).now().timestamp;'
      - lambda: 'id(global_boot_cycle_active) = true;'
      - lambda: 'id(global_boot_stage_reached) = 1;'
      - script.execute: wakeup_sequence

esp32:
  board: esp-wrover-kit
  framework:
    type: arduino # required for Inkplate display

i2c:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: false
  power_save_mode: light
  domain: .lan
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${device_name}
    password: !secret wifi_ap_password
#captive_portal:

# Enable Home Assistant API & OTA
api:
  encryption:
    key: !secret ESPHome_secret
  reboot_timeout: 0s #default is 15min (reboot if no api connected within limit)
  on_client_connected:
    - logger.log:
        format: "Client %s connected to API with IP %s"
        args: ["client_info.c_str()", "client_address.c_str()"]
  on_client_disconnected:
    - logger.log:
        format: "Client %s disconnected to API with IP %s"
        args: ["client_info.c_str()", "client_address.c_str()"]

ota:
  password: !secret OTA_password

# Enable logging
logger:
  level: verbose
improv_serial:

binary_sensor:
  - platform: status
    name: "Status"
    id: system_status

  - platform: gpio
    id: wake_button
    name: Wake Button
    pin:
      number: GPIO36
      inverted: true
      mode:
        input: true
      allow_other_uses: true
      drive_strength: 5mA
    on_press:
    - script.execute: wakeup_sequence

  - platform: homeassistant
    id: eink_deep_sleep
    name: "Eink Deep Sleep"
    entity_id: input_boolean.eink_deep_sleep
    #entity_id: switch.deep_sleep_enabled
    internal: true
    on_press: # Update global variable on HomeAssistant helper change
      then:
      - lambda: 'id(global_deep_sleep_mode) = true;'
    on_release: # Update global variable on HomeAssistant helper change
      then:
      - lambda: 'id(global_deep_sleep_mode) = false;'

  # All sensors updated
  - platform: template
    id: all_sensors_updates_received
    name: All Sensors Updates Received
    internal: true
    icon: mdi:database-refresh-outline
    # entity_category: diagnostic
    # If all sensors data is received. Will be false even if only one sensor fails.
    lambda: |-
      static int sensors_count = ${sensors_count};
      return
      id(eink_deep_sleep).state &&
      id(eink_display_refresh_count).state &&
      id(display_last_update).state &&
      id(global_sensors_updated) >= sensors_count;
    on_press:
    # Would this work with on_state as well?
      then:
        - logger.log: 
            format: "All sensors updated after %.1f seconds of uptime. Entering deep sleep."
            args: ['id(uptime_seconds).state']
        - delay: 100ms
        #- script.execute: deep_sleep_evaluation

pca6416a:
  - id: pca6416a_hub
    address: 0x20

switch:
  - platform: gpio
    id: battery_read_mosfet
    pin:
      pca6416a: pca6416a_hub
      number: 9
      inverted: true

  - platform: template
    id: greyscale_mode
    name: "Greyscale mode"
    lambda: return id(eink_display).get_greyscale();
    turn_on_action:
      - lambda: id(eink_display).set_greyscale(true);
    turn_off_action:
      - lambda: id(eink_display).set_greyscale(false);

  - platform: template
    id: partial_updating_mode
    name: "Partial Updating mode"
    lambda: return id(eink_display).get_partial_updating();
    turn_on_action:
      - lambda: id(eink_display).set_partial_updating(true);
    turn_off_action:
      - lambda: id(eink_display).set_partial_updating(false);

# HA Text sensors for reporting
text_sensor:
  # sun/moon
  - platform: homeassistant
    id: sun
    entity_id: sun.sun
    internal: true
  #- platform: homeassistant
  #  entity_id: sensor.moon
  #  id: moon
  #  internal: true
  
    # Hourly Forecast Array sensor
  - platform: homeassistant
    id: hourly_forecast_string
    name: "Hourly Forecast string"
    entity_id: sensor.hourly_forecast_string
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
    # Daily Forecast Array sensor
  - platform: homeassistant
    id: daily_forecast_string
    name: "Daily Forecast string"
    entity_id: sensor.daily_forecast_string
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
    # Daily Holidays Array sensor
  - platform: homeassistant
    id: daily_holidays_string
    name: "Daily Holidays string"
    entity_id: sensor.daily_holidays_string
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
    # Daily Namedays Array sensor
  - platform: homeassistant
    id: daily_namedays_string
    name: "Daily Namedays string"
    entity_id: sensor.daily_namedays_string
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
    # Daily Agenda Array sensor
  - platform: homeassistant
    id: daily_agenda_string
    name: "Daily Agenda string"
    entity_id: sensor.daily_agenda_string
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
    # Detailed Agenda Array sensor
  - platform: homeassistant
    id: detailed_agenda_string
    name: "Detailed Agenda string"
    entity_id: sensor.detailed_agenda_string
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
    # Detailed System Status Array sensor
  - platform: homeassistant
    id: detailed_system_status_string
    name: "Detailed System Status string"
    entity_id: sensor.detailed_system_status_string
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
  # calendar
  - platform: homeassistant
    id: date_local
    name: "Date local"
    entity_id: sensor.date_local
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
  # weather
  - platform: homeassistant
    id: weather_condition
    name: "Weather Condition"
    entity_id: sensor.openweathermap_condition
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
  - platform: homeassistant
    id: forecast_condition
    name: "Forecast Condition"
    entity_id: sensor.openweathermap_forecast_condition
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'

sensor:
  # Today's Weather Forecast related sensors
  - platform: homeassistant
    id: todays_forecast_temperature
    name: "Todays Forecast temperature"
    entity_id: sensor.openweathermap_forecast_temperature
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
  - platform: homeassistant
    id: todays_forecast_temperature_low
    name: "Todays Forecast temperature low"
    entity_id: sensor.openweathermap_forecast_temperature_low
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
  - platform: homeassistant
    id: todays_forecast_precipitation
    name: "Todays Forecast precipitation"
    entity_id: sensor.openweathermap_forecast_precipitation
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
  - platform: homeassistant
    id: todays_forecast_precipitation_probability
    name: "Todays Forecast precipitation probability"
    entity_id: sensor.openweathermap_forecast_precipitation_probability
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'
  - platform: homeassistant
    id: todays_forecast_wind_speed
    name: "Todays Forecast wind speed"
    entity_id: sensor.openweathermap_forecast_wind_speed
    internal: true
    on_value: # Update global variable on HomeAssistant sensor update
      then:
      - lambda: 'id(global_sensors_updated)++;'

  # ESPHome boot cause - Cold boot / WakeUp 
  - platform: template
    id: last_boot_cause
    name: "Last Boot Cause"
    accuracy_decimals: 0
    #internal: true
    lambda: |-
      return esp_sleep_get_wakeup_cause();
    entity_category: "diagnostic"

  # Uptime sensor
  - platform: uptime
    id: uptime_seconds
    name: Uptime
    update_interval: 1s
    accuracy_decimals: 1
    unit_of_measurement: s

## WiFi signal diagnostic sensors
  - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
    id: wifi_signal_db
    name: "WiFi Signal dB"
    update_interval: 60s
    entity_category: "diagnostic"

  - platform: copy # Reports the WiFi signal strength in %
    id: wifi_signal_percent
    name: "WiFi Signal Percent"
    source_id: wifi_signal_db
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "%"
    entity_category: "diagnostic"

## Device-specific
# Battery related sensors
  - platform: adc
    id: battery_input
    update_interval: never
    attenuation: 11db
    pin: 35

  - platform: template
    id: battery_voltage
    name: "Battery Voltage"
    device_class: voltage
    accuracy_decimals: 2
    lambda: |-
      id(battery_read_mosfet).turn_off();
      delay(1);
      float adc = id(battery_input).sample();
      id(battery_read_mosfet).turn_on();
      return adc;
    filters:
      - multiply: 2
      - throttle: 30s #Limit values sent to HA

  - platform: copy
    id: battery_percent
    name: "Battery Percent"
    source_id: battery_voltage
    accuracy_decimals: 2
    filters:
      - calibrate_linear:
      # Map from voltage to battery level
          - 3.60 -> 0
          - 4.25 -> 100
      # Handle/cap boundaries
      - lambda: |
          if (x < 0) return 0; 
          else if (x > 100) return 100;
          else return (x);
      - delta: 0.5 #Only send values to HA if they change
      - throttle: 30s #Limit values sent to HA
    device_class: battery
    unit_of_measurement: "%"  

  # Display related sensors
  - platform: homeassistant
    id: eink_display_refresh_count
    name: "Eink Display Refresh Count"
    entity_id: sensor.inkplate_10_display_refresh_count
    internal: true
    on_value: # Update global variable on HomeAssistant sensor change
      then:
      - lambda: 'id(global_sensors_updated)++;'
      - lambda: |-
          if (! isnan(id(eink_display_refresh_count).raw_state)) { 
            if (x > 0) { 
              id(global_display_refresh_count) = x;
            }
          }

  - platform: template
    id: display_last_update
    name: "Display Last Update"
    device_class: timestamp
    entity_category: "diagnostic"
    update_interval: never
    on_value: # Update global variable on HomeAssistant sensor change
      then:
      - lambda: 'id(global_sensors_updated)++;'

  - platform: template
    id: "display_refresh_count"
    name: "Display Refresh Count"
    accuracy_decimals: 0
    state_class: "total_increasing"
    #unit_of_measurement: "Refreshes"
    entity_category: "diagnostic"
    update_interval: never
    #refresh
    on_value: # Actions to perform once data for the last sensor has been received
      then:
        - if: # Boot cycle inactive (WakeUp by Button)
            condition:
            - lambda: 'return !( id(global_boot_cycle_active) );'
            then:
            - if: # DeepSleep from HA received
                condition:
                  - lambda: 'return id(eink_deep_sleep).has_state();'
                then:
                - lambda: 'id(global_deep_sleep_mode) = id(eink_deep_sleep).state;' # Update DeepSleep from HA
        - if: # Data timeout
            condition:
            - lambda: 'return isnan(id(todays_forecast_temperature).raw_state);'
            then:
                - lambda: 'id(global_deep_sleep_mode) = true;' # Enable DeepSleep
        - if: # DeepSleep enabled
            condition:
            - lambda: 'return id(global_deep_sleep_mode);'
            then:
            - delay: ${screen_update_time}
            - wait_until:
                condition: 
                  lambda: return (! isnan(id(eink_display_refresh_count).raw_state) );
                timeout:  ${wake_time}
            - script.execute: deep_sleep_evaluation

# Display buttons 
button:
  - platform: shutdown
    id: shutdown_button
    name: "Shutdown"

  - platform: restart
    name: "Restart"

  - platform: template
    name: "Refresh"
    icon: "mdi:update"
    on_press:
      then:
      - script.execute: update_screen

time:
  - platform: sntp
    id: esptime
    timezone: Europe/Bratislava
    on_time:
      - seconds: 0
        minutes: /1
        then:
          - if:
              condition:
                or:
                # WakeUp sequence reached update_screen
                - lambda: 'return ( id(global_boot_cycle_active) and (id(global_boot_stage_reached) >= 5));'
                # WakeUp sequence incomplete and took too long(20s)
                - lambda: |-
                          auto startuptime = ( id(esptime).now().timestamp - id(global_boot_cycle_started) );
                          return ( id(global_boot_cycle_active) and ( startuptime >= 20 ) and id(global_boot_stage_reached) < 4);
                # DeepSleep is disabled
                - binary_sensor.is_off: eink_deep_sleep
                - lambda: 'return ! id(global_deep_sleep_mode);'
              then:
              - if:
                  condition:
#                    or:
#                    - binary_sensor.is_off: eink_deep_sleep
                    - lambda: 'return ! id(global_deep_sleep_mode);'
                  then:
                    - script.execute: update_screen
                    - lambda: 'id(global_sensors_updated) = 0;'
                  else:
                    - lambda: 'id(global_boot_cycle_active) = true;'
                    - lambda: 'id(global_boot_stage_reached) = 1;'
                    - lambda: 'id(global_boot_cycle_started) = id(esptime).now().timestamp;'
                    - script.execute: wakeup_sequence

deep_sleep:
  id: deep_sleep_control
  sleep_duration: ${sleep_time}
  wakeup_pin:
    number: 36 # Top wake button
    allow_other_uses: true
    drive_strength: 5mA
    inverted: true
  wakeup_pin_mode: IGNORE # INVERT_WAKEUP or KEEP_AWAKE or IGNORE(default)

script:
  - id: check_API_connection
    then:
      - if:
          condition:
            api.connected:
          then:
            - logger.log: API is connected...
          else:
            - logger.log: API is disconnected!

# Script for updating screen - Refresh display and publish refresh count and time. (Thanks @paviro!)
  - id: update_screen
    mode: queued
    then:
      - logger.log: "Refreshing display..."
      - lambda: 'id(global_display_refresh_count)++;'
      - lambda: 'id(display_refresh_count).publish_state(id(global_display_refresh_count));'
      - lambda: 'id(display_last_update).publish_state(id(esptime).now().timestamp);'
      #- script.execute: wait_for_display_sensors # Wait until display sensors states are updated
      - component.update: eink_display
      #- delay: ${screen_update_time}

  - id: deep_sleep_evaluation
    mode: queued
    then:
      #- lambda: 'id(deep_sleep_enabled).publish_state(id(global_deep_sleep_mode));'
      - if:
          condition:
            #or:
            #- binary_sensor.is_on: eink_deep_sleep
            - lambda: return id(global_deep_sleep_mode);
          then:
            - if:
                condition:
                  lambda: |- 
                    return id(all_sensors_updates_received) and !id(global_data_timeout_occurred);
                then:
                  #- delay: ${wake_time} # wait for a specified time before sleep
                  - if:
                      condition:
                        lambda: |- 
                          auto time = id(esptime).now();
                          if (!time.is_valid()) { 
                          return false;
                          }
                          return (time.hour < 5); 
                      then:
                        - logger.log: "It's nightime, entering long sleep for ${night_sleep_time}..."
                        - deep_sleep.enter:
                            id: deep_sleep_control
                            sleep_duration: ${night_sleep_time}
                      else:
                        - logger.log: "It's daytime, entering normal sleep for ${sleep_time}..."
                        - deep_sleep.enter:
                            id: deep_sleep_control 
                            sleep_duration: ${sleep_time}
                else:
                  - logger.log: "Data timeout, entering short sleep for ${short_sleep_time}..."
                  - deep_sleep.enter:
                      id: deep_sleep_control 
                      sleep_duration: ${short_sleep_time}
          else:
            - logger.log: 'Deep Sleep Disabled...'

  - id: wait_for_display_sensors
    mode: queued
    then:
      - script.execute: check_API_connection
      - wait_until:
          condition:
            api.connected: # Wait until API connected
          timeout: 30s
      - wait_until:
          condition: 
            lambda: return (! isnan(id(eink_display_refresh_count).raw_state) );
          timeout:  ${wake_time}

  - id: wakeup_sequence
    mode: queued
    then:
      - wait_until:
          condition:
            api.connected: # Wait until API connected
          timeout: 30s
      - script.execute: check_API_connection
      - lambda: 'id(global_boot_stage_reached) = 2;'
      - logger.log: "Connected, waiting a bit until all sensors are updated..."
      # Wait a bit longer so all HA items are received

      #- delay: ${wake_time}

#      - if:
#          condition:
#            lambda: return (isnan(id(eink_display_refresh_count).raw_state) );
#          then:
      - wait_until:
          condition: 
            lambda: return (! isnan(id(eink_display_refresh_count).raw_state) );
          timeout:  ${wake_time}
      - lambda: |-
          if ( isnan(id(eink_display_refresh_count).raw_state) ) {
            id(display_refresh_count).publish_state(id(global_display_refresh_count));
          }
#          else:
      - lambda: id(global_boot_stage_reached) = 3;
      - logger.log: "HA sensors updated, waiting a bit until HW sensors are updated..."
      # Wait a bit longer so all HW items are received
      - wait_until:
          condition: 
            #lambda: return ( id(battery_percent).has_state() );
            lambda: return ( id(all_sensors_updates_received) );
          timeout:  ${wake_time}

      - lambda: 'id(global_boot_stage_reached) = 4;'
      - logger.log: "HW sensors updated, waiting for screen to refresh before finishing..."

      # Wait a bit longer so all items are received (3s min.)
#      - wait_until:
#          condition: 
#            lambda: return ( ( id(esptime).now().timestamp - id(global_boot_cycle_started) ) >= 5 );
      - if: # If DeepSleep is disabled - update it from HA
          condition:
            - lambda: 'return id(eink_deep_sleep).has_state();'
          then:
          - lambda: 'if ( id(eink_deep_sleep).state == true ) { id(global_deep_sleep_mode) = id(eink_deep_sleep).state; }'

      # Wait a bit longer for screen update
      - script.execute: update_screen
      - lambda: 'id(global_boot_stage_reached) = 5;'
      #- script.execute: check_battery
      - if:
          condition:
            lambda: return ( id(battery_percent).state <= 5 );
          then:
            - logger.log: Battery level is critically low, shutting down the system...
            - button.press: shutdown_button
#          else:
#      - script.execute: deep_sleep_evaluation

font:
  - file: "fonts/Roboto-Regular.ttf"
    id: text_big
    size: 56
    glyphs:
      ['&', '@', '!', '?', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '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', 'å', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', '/', '€', '’','|',
       'á','č','ď','é','í','ľ','ĺ','ň','ó','ř','ŕ','š','ť','ú','ý','ž',
       'Á','Č','Ď','É','Í','Ľ','Ĺ','Ň','Ó','Ř','Ŕ','Š','Ť','Ú','Ý','Ž']
  - file: "fonts/Roboto-Regular.ttf"
    id: text_small
    size: 40
    glyphs:
      ['&', '@', '!', '?', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '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', 'å', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', '/', '€', '’','|',
       'á','č','ď','é','í','ľ','ĺ','ň','ó','ř','ŕ','š','ť','ú','ý','ž',
       'Á','Č','Ď','É','Í','Ľ','Ĺ','Ň','Ó','Ř','Ŕ','Š','Ť','Ú','Ý','Ž']
  - file: "fonts/Roboto-Regular.ttf"
    id: text_smaller
    size: 24
    glyphs:
      ['&', '@', '!', '?', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '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', 'å', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', '/', '€', '’','|','»',
       'á','č','ď','é','í','ľ','ĺ','ň','ó','ř','ŕ','š','ť','ú','ý','ž',
       'Á','Č','Ď','É','Í','Ľ','Ĺ','Ň','Ó','Ř','Ŕ','Š','Ť','Ú','Ý','Ž', 'Ŝ']
    # https://pictogrammers.github.io/@mdi/font/5.3.45/
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: icons_big
    size: 160
    glyphs:
      # weather
      - "\U000F0594" # clear-night
      - "\U000F0590" # cloudy
      - "\U000F0591" # fog      
      - "\U000F0592" # hail
      - "\U000F0593" # lightning
      - "\U000F067E" # lightning-rainy
      - "\U000F0F31" # partlycloudy(night)
      - "\U000F0595" # partlycloudy
      - "\U000F0596" # pouring
      - "\U000F0597" # rainy
      - "\U000F0598" # snowy
      - "\U000F0F36" # snowy-heavy
      - "\U000F067F" # snowy-rainy
      - "\U000F0599" # sunny
      - "\U000F059D" # windy
      - "\U000F059E" # windy-variant
      - "\U000F0F38" # exceptional
      # logos
      - "\U000F07D0" # home-assistant

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: icons_medium
    size: 60
    glyphs:
      # weather
      - "\U000F0594" # clear-night
      - "\U000F0590" # cloudy
      - "\U000F0591" # fog      
      - "\U000F0592" # hail
      - "\U000F0593" # lightning
      - "\U000F067E" # lightning-rainy
      - "\U000F0F31" # partlycloudy(night)
      - "\U000F0595" # partlycloudy
      - "\U000F0596" # pouring
      - "\U000F0597" # rainy
      - "\U000F0598" # snowy
      - "\U000F0F36" # snowy-heavy
      - "\U000F067F" # snowy-rainy
      - "\U000F0599" # sunny
      - "\U000F059D" # windy
      - "\U000F059E" # windy-variant
      - "\U000F0F38" # exceptional
      # holidays
      - "\U000F00D6" # briefcase
      - "\U000F0814" # briefcase-outline
      - "\U000F04B9" # sofa
      - "\U000F0240" # flag-variant
      # calendars
      - "\U000F0E17" # mdi-calendar
      - "\U000F00F6" # mdi-calendar-today
      - "\U000F00EE" # calendar-blank
      - "\U000F0ED7" # calendar-account
      - "\U000F09D2" # calendar-heart
      - "\U000F09D3" # calendar-star
      - "\U000F03A4" # numeric-1-box
      - "\U000F03A7" # numeric-2-box
      - "\U000F03AA" # numeric-3-box
      - "\U000F03AD" # numeric-4-box
      - "\U000F03B1" # numeric-5-box
      - "\U000F03B3" # numeric-6-box
      - "\U000F03B6" # numeric-7-box
      - "\U000F03B9" # numeric-8-box
      - "\U000F03BC" # numeric-9-box
      #others
      - "\U000F012E" # mdi-checkbox-blank
      - "\U000F0155" # clock-start
      - "\U000F0996" # progress-clock
      - "\U000F0020" # alarm
       
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: icons_smaller
    size: 24
    glyphs:
      # home-assistant
      - "\U000F07D0" # home-assistant
      - "\U000F087B" # home-alert
      
      # wifi
      - "\U000F05A9" # wifi
      - "\U000F05AA" # wifi Alert
      - "\U000F092E" # wifi-strength-off-outline
      - "\U000F092F" # wifi-strength-outline
      - "\U000F091F" # wifi-strength-1
      - "\U000F0922" # wifi-strength-2
      - "\U000F0925" # wifi-strength-3
      - "\U000F0928" # wifi-strength-4

      # battery
      - "\U000F0083" # battery alert
      - "\U000F008E" # battery empty
      - "\U000F007A" # battery 10
      - "\U000F007B" # battery 20
      - "\U000F007C" # battery 30
      - "\U000F007D" # battery 40
      - "\U000F007E" # battery 50
      - "\U000F007F" # battery 60
      - "\U000F0080" # battery 70
      - "\U000F0081" # battery 80
      - "\U000F0082" # battery 90
      - "\U000F0079" # battery 100
      # calendar
      - "\U000F0E17" # mdi-calendar
      - "\U000F00F6" # mdi-calendar-today
      - "\U000F00EE" # calendar-blank
      - "\U000F0ED7" # calendar-account
      - "\U000F09D2" # calendar-heart
      - "\U000F09D3" # calendar-star

      - "\U000F00F0" # calendar-clock
      - "\U000F00ED" # calendar
      - "\U000F0679" # calendar-range

      - "\U000F03A4" # numeric-1-box
      - "\U000F03A7" # numeric-2-box
      - "\U000F03AA" # numeric-3-box
      - "\U000F03AD" # numeric-4-box
      - "\U000F03B1" # numeric-5-box
      - "\U000F03B3" # numeric-6-box
      - "\U000F03B6" # numeric-7-box
      - "\U000F03B9" # numeric-8-box
      - "\U000F03BC" # numeric-9-box      
      #others
      - "\U000F12C6" # Monitor-edit
      - "\U000F13B4" # Monitor-eye
      - "\U000F10C2" # Temperature High
      - "\U000F10C3" # Temperature Low
      - "\U000F059B" # Sunset Down
      - "\U000F059C" # Sunset Up
      - "\U000F07E4" # CO2
      - "\U000F054B" # umbrella
      - "\U000F07CA" # fuel
      - "\U000F024A" # flower
      - "\U000F051F" # time-remaining
      - "\U000F140B" # Energy

      - "\U000F078E" # alarm-bell
      - "\U000F06A6" # power-plug-off
      - "\U000F1424" # power-plug-off-outline
      - "\U000F0425" # power
      - "\U000F040C" # play-circle
      - "\U000F040B" # play-box-outline
      - "\U000F0783" # signal-off
      - "\U000F0C9B" # network-off
      - "\U000F0319" # lan-disconnect
      - "\U000F0D60" # car-cruise-control
      - "\U000F035B" # memory
      - "\U000F02CA" # disk
      - "\U000F04C5" # speedometer