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

  • related ESPhome config part2:
image:
  - file: 'graphics/ESPHome_dark_logo.png'
    id: ESPHome_dark_logo
    resize: 825x150
    type: TRANSPARENT_BINARY

  - file: 'graphics/homeassistant-outline.png'
    id: HA_logo_outline
    resize: 136x121 #128x117
    type: TRANSPARENT_BINARY

  - file: mdi:home-assistant
    id: home_assistant_logo_big
    resize: 400x400
    type: TRANSPARENT_BINARY

  - file: mdi:home-assistant
    id: home_assistant_online
    resize: 27x27
    type: TRANSPARENT_BINARY

  - file: mdi:home-alert
    id: home_assistant_offline
    resize: 25x25
    type: TRANSPARENT_BINARY



display:
- platform: inkplate6
  model: inkplate_10
  id: eink_display
  greyscale: false
  rotation: 270°
  auto_clear_enabled: false
  update_interval: never
  ckv_pin: 32
  sph_pin: 33
  gmod_pin:
    pca6416a: pca6416a_hub
    number: 1
  gpio0_enable_pin:
    pca6416a: pca6416a_hub
    number: 8
  oe_pin:
    pca6416a: pca6416a_hub
    number: 0
  spv_pin:
    pca6416a: pca6416a_hub
    number: 2
  powerup_pin:
    pca6416a: pca6416a_hub
    number: 4
  wakeup_pin:
    pca6416a: pca6416a_hub
    number: 3
  vcom_pin:
    pca6416a: pca6416a_hub
    number: 5
  lambda: |-
    // helper variables
    int y = 0;
    auto time = id(esptime).now();
    auto dayTime = id(sun).state == "above_horizon";

    if ( esp_sleep_get_wakeup_cause() == 0 and id(global_boot_stage_reached) == 0 ) {
      it.fill(COLOR_OFF);
      it.print(412, 100, id(text_big), COLOR_ON, TextAlign::TOP_CENTER, "System booting...");
      // Aligned on left by default
      it.image(212, 350, id(home_assistant_logo_big), ImageAlign::TOP_LEFT);
      it.image(10, 925, id(ESPHome_dark_logo), ImageAlign::TOP_LEFT);
      //it.printf(0, 1150, id(text_smaller), COLOR_ON, TextAlign::TOP_LEFT, "%i", id(global_display_refresh_count));
      //it.printf(300, 1150, id(text_smaller), COLOR_ON, TextAlign::TOP_LEFT, "%3.0f", id(eink_display_refresh_count).raw_state);
    }
    else {
      it.fill(COLOR_ON);
      // it.line(0, 0, 825, 1200, COLOR_OFF);
      // it.line(825, 0, 0, 1200, COLOR_OFF);

      //it.print(0, 0, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "Status:");
      if (id(system_status).state) {
        it.printf(0, 2, id(icons_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "\U000F07D0");
        //it.image(0, 0, id(home_assistant_online), ImageAlign::TOP_LEFT, COLOR_OFF);
        it.print(28, 0, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "online");
      } else {
        it.printf(0, 2, id(icons_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "\U000F087B");
        //it.image(0, 0, id(home_assistant_offline), ImageAlign::TOP_LEFT, COLOR_OFF);
        it.filled_rectangle(28, 0, 70, 25, COLOR_OFF);
        it.print(28, 0, id(text_smaller), COLOR_ON, TextAlign::TOP_LEFT, "offline");
      }
      
      // Screen Updates icon + value
      it.printf(130, 2, id(icons_smaller), COLOR_OFF, TextAlign::TOP_RIGHT, "\U000F13B4");
      //it.print(240, 0, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "Screen updated:");
      it.printf(135, 0, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "%i", id(global_display_refresh_count) );

      // Screen Last Update icon + value
      it.printf(355, 2, id(icons_smaller), COLOR_OFF, TextAlign::TOP_RIGHT, "\U000F12C6");
      it.strftime(360, 0, id(text_smaller), COLOR_OFF, TextAlign::LEFT, "%d.%m.", id(esptime).now());
      it.filled_rectangle(425, 0, 60, 25, COLOR_OFF);
      it.strftime(425, 0, id(text_smaller), COLOR_ON, TextAlign::LEFT, "%H:%M", id(esptime).now());

      // Wifi Signal icon + value
      if (isnan(id(wifi_signal_percent).raw_state)) {
        it.printf(695, 2, id(icons_smaller), COLOR_OFF, TextAlign::TOP_RIGHT, "\U000F05AA");
      }
      else {
        auto wifiIcon = wifiToIcon(id(wifi_signal_percent).state);
        it.printf(695, 2, id(icons_smaller), COLOR_OFF, TextAlign::TOP_RIGHT, wifiIcon.c_str());
        it.printf(695, 0, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "%3.0f%%", id(wifi_signal_percent).state);
      }

      // Battery Level icon + value
      auto batteryIcon = batteryToIcon(id(battery_percent).state);
      it.printf(775, 2, id(icons_smaller), COLOR_OFF, TextAlign::TOP_RIGHT, batteryIcon.c_str());
      it.printf(775, 0, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "%3.0f%%", id(battery_percent).state);

      it.line(0, 25, 825, 25, COLOR_OFF);

      // Battery Charging note before shutdown
      if (id(battery_percent).state <= 10) {
        it.print(412, 190, id(text_big), COLOR_OFF, TextAlign::TOP_CENTER, "BATTERY VERY LOW, PLEASE CHARGE ME!");
        id(shutdown_button).press();
      }

      // Weather Condition icon + value
      //auto wconditionIcon = conditionToIcon(id(weather_condition).state, dayTime);
      //it.printf(50, 40, id(icons_big), COLOR_OFF, TextAlign::TOP_LEFT, wconditionIcon.c_str());
      //it.printf(100, 50, id(text_small), COLOR_OFF, TextAlign::TOP_LEFT, "%s", id(weather_condition).state.c_str());
      
      // Date icon + value
      it.printf(250, 80, id(text_big), COLOR_OFF, TextAlign::TOP_LEFT, "%s", id(date_local).state.c_str());
      //it.printf(0, 100, id(icons_big), COLOR_OFF, TextAlign::TOP_LEFT, "\U000F00F6");

      // Weather Forecast icon + value
      if (isnan(id(todays_forecast_temperature).raw_state)) {
        it.print(412, 190, id(text_big), COLOR_OFF, TextAlign::TOP_CENTER, "DATA TIMEOUT !");
        it.print(412, 290, id(text_small), COLOR_OFF, TextAlign::TOP_CENTER, "Please wait 2 minutes for next update...");
        id(global_data_timeout_occurred) = true;
      }
      else {
        auto fconditionIcon = conditionToIcon(id(forecast_condition).state, dayTime);
        it.printf(50, 40, id(icons_big), COLOR_OFF, TextAlign::TOP_LEFT, fconditionIcon.c_str());
        it.printf(130, 190, id(text_big), COLOR_OFF, TextAlign::TOP_CENTER, "%.0f/%.0f°C", id(todays_forecast_temperature_low).state, id(todays_forecast_temperature).state);
        it.printf(130, 250, id(text_small), COLOR_OFF, TextAlign::TOP_CENTER, "%.0fmm", id(todays_forecast_precipitation).state);
        it.printf(130, 290, id(text_small), COLOR_OFF, TextAlign::TOP_CENTER, "(%3.0f%%)", id(todays_forecast_precipitation_probability).state);
      }
      it.filled_rectangle(0,990,825,210, COLOR_OFF);
      //it.filled_rectangle(3,993,819,204, COLOR_ON);
      it.filled_rectangle(5,995,815,200, COLOR_ON);
      //it.printf(412, 1200, id(icons_big), COLOR_OFF, TextAlign::BASELINE_CENTER, "\U000F07D0");
      it.image(412, 1200, id(HA_logo_outline), ImageAlign::BOTTOM_CENTER, COLOR_OFF);

      #define xRes 960
      #define yRes 540
      std::string txt_array;
      int wx = 0; // start position x
      int wy = 400; // start position y
      int column_width = 0; // shift x position before new record
      int row_height = 59; // shift y position before new record
      int skip_height = 0; // skip y pixes before new record
      int count = 0;
      int row_id = 0;
      for (int i_sensor = 1; i_sensor <= 7; i_sensor++) {
        switch (i_sensor){
          case 1:
            txt_array = id(daily_forecast_string).state; //forecast daily
            break;
          case 2:
            txt_array = id(daily_namedays_string).state; //namedays
            wx = 0; // start position x
            wy = 400; // start position y
            column_width = 0; // shift x position before new record
            row_height = 59; // shift y position before new record
            break;
          case 3:
            txt_array = id(daily_holidays_string).state; //holidays
            wx = 0; // start position x
            wy = 400; // start position y
            column_width = 0; // shift x position before new record
            row_height = 59; // shift y position before new record
            break;
          case 4:
            txt_array = id(daily_agenda_string).state; //agenda icons
            wx = 0; // start position x
            wy = 400; // start position y
            column_width = 0; // shift x position before new record
            row_height = 59; // shift y position before new record
            break;
          case 5:
            txt_array = id(detailed_agenda_string).state; //agenda details
            wx = 0; // start position x
            wy = 820; // start position y
            column_width = 0; // shift x position before new record
            row_height = 25; // shift y position before new record
            break;
          case 6:
            txt_array = id(hourly_forecast_string).state; //forecast hourly
            wx = 250; // start position x
            wy = 200; // start position y
            column_width = 100; // shift x position before new record
            row_height = 0; // shift y position before new record
            break;
          case 7:
            txt_array = id(detailed_system_status_string).state; //system status
            wx = 7; // start position x
            wy = 1012; // start position y
            column_width = 560; // shift x position before new record
            row_height = 28; // shift y position before new record
            break;
        }
        
        std::vector<std::string> array;
        std::vector<std::string> v;
        //ESP_LOGD("%s", txt_array.c_str());
        array.clear();

        char *token = strtok(const_cast<char*>(txt_array.c_str()), "#");
        // this while splits the string (I believe, I "found" the code)
        while (token != nullptr)
        {
          array.push_back(token);
          token = strtok(nullptr, "#");
        }
        count = 0;
        // here we loop the "records"
        for ( std::string record : array ) {
          std::string str = "";
          str = record;
          //ESP_LOGD("test: ", "String to Vector: %s", str.c_str());
          v.clear();
          token = strtok (&str[0],";");
          while (token != NULL)
          {
            v.push_back(token);
            token = strtok (NULL, ";");
          }

          switch (i_sensor){
            case 1:
              it.rectangle(1, wy, 825, 60, COLOR_OFF);  // adds a border around the "record"
              //it.rectangle(2, wy+1, 823, 57, COLOR_OFF);
              // this is the loop for each value in the "record" Forecast Daily
              for ( std::string s : v ) {
                if(count == 0){
                  // Day (Mon/Tue/...)
                  it.filled_rectangle(wx, wy, 38, 56, COLOR_OFF);
                  it.filled_rectangle(wx+40, wy, 27, 26, COLOR_OFF);
                  it.filled_circle(wx+38, wy+27, 27, COLOR_OFF);
                  it.printf(wx+7, wy, id(text_small), COLOR_ON, "%s", s.c_str());
                }else if(count == 1){
                  // Temperature(s)
                  it.printf(wx+150, wy, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 2){
                  // Precipitation
                  it.printf(wx+150, wy+25, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 3){
                  // weather icon
                  it.printf(wx+87, wy, id(icons_medium), COLOR_OFF, TextAlign::TOP_LEFT, "%s", s.c_str());
                }
                //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
                count += 1;
              }
              break;
            case 2:
              // this is the loop for each value in the "record" Namedays
              for ( std::string s : v ) {
                if(count == 0){
                  // Day (Mon/Tue/...)
                  it.printf(wx+7, wy, id(text_small), COLOR_ON, "%s", s.c_str());
                }else if(count == 1){
                  // Name(s)
                   it.printf(wx+347, wy, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 3){
                  // no data
                  // it.printf(wx+87, wy+25, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 2){
                  // calendar icon
                  //it.printf(wx+750, wy, id(icons_medium), COLOR_OFF, TextAlign::TOP_RIGHT, "%s", s.c_str());
                }
                //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
                count += 1;
              }
              break;
            case 3:
              // this is the loop for each value in the "record" Holidays
              for ( std::string s : v ) {
                if(count == 0){
                  // Day (Mon/Tue/...)
                  it.printf(wx+7, wy, id(text_small), COLOR_ON, "%s", s.c_str());
                }else if(count == 1){
                  // Name(s)
                  if (s=="workday" or s=="weekend") { s=""; }
                  //it.start_clipping(wx+347,wy,wx+347+478,wy+118);
                  //it.filled_rectangle(0,wy,433,59, COLOR_OFF);
                  it.printf(wx+347, wy+25, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                  //it.end_clipping();
                }else if(count == 3){
                  // no data
                  //it.printf(wx+87, wy+25, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 2){
                  // Icon
                  it.printf(wx+310, wy, id(icons_medium), COLOR_OFF, TextAlign::TOP_RIGHT, "%s", s.c_str());
                }
                //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
                count += 1;
              }
              break;
            case 4:
              // this is the loop for each value in the "record" Agenda Icon
              for ( std::string s : v ) {
                if(count == 0){
                  // Day (Mon/Tue/...)
                  it.printf(wx+7, wy, id(text_small), COLOR_ON, "%s", s.c_str());
                }else if(count == 1){
                  // Counter
                  //it.printf(wx+357, wy, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 2){
                  // EventName
                  //it.printf(wx+87, wy+25, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 3){
                  // Icon
                  it.printf(wx+310+513, wy, id(icons_smaller), COLOR_OFF, TextAlign::TOP_RIGHT, "%s", s.c_str());
                }
                //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
                count += 1;
              }
              break;
            case 5:
              //it.rectangle(1, wy, 825, 40, COLOR_OFF);  // adds a border around the "record"
              //it.rectangle(2, wy+1, 823, 38, COLOR_OFF);
              // this is the loop for each value in the "record" Agenda Details
              for ( std::string s : v ) {
                if(count == 0){
                  // Counter
                  //it.printf(wx+7, wy, id(text_small), COLOR_ON, "%s", s.c_str());
                }else if(count == 1){
                  // Timing
                  if (s == "0") { skip_height = row_height; }
                  else {it.printf(wx+60, wy, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "%s", s.c_str());}
                }else if(count == 2){
                  // EventName
                  //if (skip_height == 0) {it.printf(wx+330, wy, id(text_smaller), COLOR_OFF, TextAlign::CENTER_LEFT, "%s", s.c_str());}
                }else if(count == 3){
                  // Event Icons
                  //std::string icon_first = s.substr(0, 1);
                  //std::string icon_second = s.c_str();
                  // Remove the first character from string
                  //icon_second.erase(0, 1);
                  if (skip_height == 0) {
                    it.printf(wx+5, wy, id(icons_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "%s", s.c_str());
                    //it.printf(wx+35, wy+3, id(icons_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "%s", icon_second.c_str());
                    }
                }
                //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
                count += 1;
              }
              break;
            case 6:
              // this is the loop for each value in the "record" Forecast Hourly
              for ( std::string s : v ) {
                if(count == 0){
                  // Hour (22/23/00/01/...)
                  it.printf(wx+40, wy, id(text_small), COLOR_OFF, TextAlign::TOP_RIGHT, "%s", s.c_str());
                  it.printf(wx+40, wy, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "00");
                }else if(count == 1){
                  // Temperature(s)
                  it.printf(wx, wy+100, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 2){
                  // Precipitation
                  it.printf(wx, wy+120, id(text_smaller), COLOR_OFF, "%s", s.c_str());
                }else if(count == 3){
                  // weather icon
                  it.printf(wx, wy+45, id(icons_medium), COLOR_OFF, TextAlign::TOP_LEFT, "%s", s.c_str());
                }
                //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
                count += 1;
              }
              break;
            case 7:
              // this is the loop for each value in the "record" System Status Details
              for ( std::string s : v ) {
                if(count == 0){
                  // Counter
                  row_id = atoi(s.c_str());
                  //it.printf(wx+200, wy, id(text_smaller), COLOR_OFF, TextAlign::CENTER_LEFT, "%s", s.c_str());
                  //it.printf(wx+200, wy, id(text_smaller), COLOR_OFF, TextAlign::CENTER_LEFT, " %d", row_id);
                }else if(count == 1){
                  // Timing
                  if (s == "0") { skip_height = 0; }
                  else {it.printf(wx+30, wy, id(text_smaller), COLOR_OFF, TextAlign::CENTER_LEFT, "%s", s.c_str());}
                }else if(count == 2){
                  // Status Name
                  if (skip_height == 0) {it.printf(wx+250, wy, id(text_smaller), COLOR_OFF, TextAlign::CENTER_RIGHT, "%s", s.c_str());}
                }else if(count == 3){
                  // Icon
                  if (skip_height == 0) {it.printf(wx+0, wy, id(icons_smaller), COLOR_OFF, TextAlign::CENTER_LEFT, "%s", s.c_str());}
                }
                //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
                count += 1;
              }
              break;
          }
          wy = wy + row_height - skip_height; // move down to next row
          if (row_height == 0) { wx = wx + column_width; } // move to next column
          if (i_sensor == 7 and row_id == 6) { wy = 1012; wx = wx + column_width; } // reset position y + move to next column
          count = 0; skip_height = 0;
        }
      }
      


      //it.start_clipping(0,990,400,1200);
      //it.printf(0, 1000, id(text_smaller), COLOR_ON, TextAlign::TOP_LEFT, "abcdefghijklmnopqrstuvwxyz1234567890");
      //it.printf(0, 1000, id(text_smaller), id(my_red), "State: %s", id(my_binary_sensor).state ? "ON" : "OFF");
      // Aligned on left by default
      //it.image(600, 1000, id(home_assistant_logo_big), ImageAlign::TOP_LEFT, COLOR_OFF, COLOR_ON);
      //it.image(5, 025, id(ESPHome_dark_logo), ImageAlign::TOP_LEFT, COLOR_OFF, COLOR_ON);
      //it.printf(412, 1200, id(icons_big), COLOR_OFF, TextAlign::BASELINE_CENTER, "\U000F07D0");
      //it.printf(600, 1000, id(icons_big), COLOR_OFF, TextAlign::TOP_CENTER, "\U000F07D0");
      //it.end_clipping();


      auto timediff = id(esptime).now().timestamp - id(global_boot_cycle_started);
      static int debug_number = timediff;
      it.printf(7, 1170, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "LoadTime %i", debug_number );
      //it.printf(200, 1170, id(text_smaller), COLOR_OFF, TextAlign::TOP_LEFT, "%i", id(eink_display_refresh_count).raw_state);
      it.printf(815, 1170, id(text_smaller), COLOR_OFF, TextAlign::TOP_RIGHT, "SensorsUpdated %i", id(global_sensors_updated));
    }

  - name: Weatherman Data
    state: "OK"
    attributes:
      energy_prices: >
        {% set graph_h = states('input_number.weatherman_el_hourly_graph_h')|default(50) %}

        {% set h_before = 23 %}
        {% set h_after = 23 %}

        {% set today = namespace(items=[]) %}
        {% set tomorrow = namespace(items=[]) %}
        {% set p_high = namespace(v=0) %}
        {% set p_low = namespace(v=4000) %}

        {% for ph in state_attr('sensor.nordpool_mwh_ee', 'raw_today') %}
          {% if (utcnow().hour - h_before <= ph.start.hour <= utcnow().hour + h_after) %}
            {% set price = (ph.value|round(0))|int %}
            {% set today.items = today.items + [{'hour': ph.start.hour, 'price': price }] %}
            {% if ph.value >= p_high.v %}
              {% set p_high.v = price %}
            {% endif %}
            {% if ph.value <= p_low.v %}
              {% set p_low.v = price %}
            {% endif %}
          {% endif %}
        {% endfor %}

        {% for ph in state_attr('sensor.nordpool_mwh_ee', 'raw_tomorrow') %}
          {% if (utcnow().hour - h_before <= ph.start.hour <= utcnow().hour + h_after) %}
            {% set price = (ph.value|round(0))|int %}
            {% set tomorrow.items = tomorrow.items + [{'hour': ph.start.hour, 'price': price }] %}
            {% if ph.value >= p_high.v %}
              {% set p_high.v = price %}
            {% endif %}
            {% if ph.value <= p_low.v %}
              {% set p_low.v = price %}
            {% endif %}
          {% endif %}
        {% endfor %}

        {% set results = {
          'attributes': [
            {'high': p_high.v},
            {'low': p_low.v},
            {'graph': graph_h},
            {'datapoints': h_before + h_after + 2 },
            {'today': today.items|sort(attribute='hour')},
            {'tomorrow': tomorrow.items|sort(attribute='hour')}
          ]} %}
        {{ results.attributes }}

      //# Energy price forecast
        if (id(energy_prices).has_state()) {
          int x_offset = 24;
          int y_offset = 730;
          DynamicJsonDocument doc(3072);
          deserializeJson(doc, (id(energy_prices).state.c_str()));
          int high = doc[0]["high"];
          int low = doc[1]["low"];
          int graph = doc[2]["graph"];
          int datapoints = doc[3]["datapoints"];
          int current_hour = id(homeassistant_time).now().hour;

          float h_ratio = float(graph)/float(high);
          int bar_size = float(xres-(2*x_offset))/float(datapoints);

          for (JsonObject root_4_today_item : doc[4]["today"].as<JsonArray>()) {
            int hour = root_4_today_item["hour"];
            int price = root_4_today_item["price"];
            float line_y = price*h_ratio;

            if ( price == high || price == low ) {
              it.printf(x_offset+(bar_size*hour)+(bar_size/2), y_offset-graph-3, id(font_smaller_bold), TextAlign::BOTTOM_CENTER, "%d", price);
            }
            it.rectangle(x_offset+(bar_size*hour), y_offset-graph, bar_size, graph);
            if (hour == current_hour) {
              it.printf(x_offset+(bar_size*hour)+(bar_size/2), y_offset-graph-3, id(font_smaller_bold), TextAlign::BOTTOM_CENTER, "%d", price);
              it.rectangle(x_offset+(bar_size*hour), y_offset-line_y, bar_size, line_y);
            } else {
              it.filled_rectangle(x_offset+(bar_size*hour), y_offset-line_y, bar_size, line_y);
            }
            if (hour == 0 || hour == 3 || hour == 6 || hour == 9 || hour == 12 || hour == 15 || hour == 18 || hour == 21) {
              it.printf(x_offset+(bar_size*hour)+(bar_size/2), y_offset+17, id(font_smaller_bold), TextAlign::BOTTOM_CENTER, "%d", hour);
            }
          }
          x_offset = x_offset + (bar_size * (datapoints/2));
          for (JsonObject root_5_tomorrow_item : doc[5]["tomorrow"].as<JsonArray>()) {
            int hour = root_5_tomorrow_item["hour"];
            int price = root_5_tomorrow_item["price"];
            float line_y = price*h_ratio;

            if ( price == high || price == low ) {
              it.printf(x_offset+(bar_size*hour)+(bar_size/2), y_offset-graph-3, id(font_smaller_bold), TextAlign::BOTTOM_CENTER, "%d", price);
            }
            it.rectangle(x_offset+(bar_size*hour), y_offset-graph, bar_size, graph);
            it.filled_rectangle(x_offset+(bar_size*hour), y_offset-line_y, bar_size, line_y);
            if (hour == 0 || hour == 3 || hour == 6 || hour == 9 || hour == 12 || hour == 15 || hour == 18 || hour == 21) {
              it.printf(x_offset+(bar_size*hour)+(bar_size/2), y_offset+17, id(font_smaller_bold), TextAlign::BOTTOM_CENTER, "%d", hour);
            }
          }
          it.printf(24, y_offset+36, id(font_smaller_bold), TextAlign::BOTTOM_LEFT, "HOURLY ELECTRICITY PRICES €/MWh");
        }

2 Likes

Thanks a lot. will try to implement that this weekend !

Have you not managed to get anything on the display yet? Next to the USB-C port there is a toggle switch which should be set to ON (now set to PWR). The Waveshare Wiki is pretty extensive, I would try and upload one of the demo’s to see if you can get something to show up.

hi @brot123 can i ask you how you fixed the hourly forecast?

It´s a breaking change in the 2024.3

This will give you a sensor with hourly forecast

template:
  - trigger:
      - platform: time_pattern
        hours: /1
      - platform: homeassistant
        event: start
    action:
      - service: weather.get_forecasts
        data:
          type: hourly
        target:
          entity_id: weather.home
        response_variable: hourly
    sensor:
      - name: Weather Hourly
        state: "{{ states('weather.home') }}"
        attributes:
          temperature: "{{ state_attr('weather.home', 'temperature') }}"
          dew_point: "{{ state_attr('weather.home', 'dew_point') }}"
          temperature_unit: "{{ state_attr('weather.home', 'temperature_unit') }}"
          humidity: "{{ state_attr('weather.home', 'humidity') }}"
          cloud_coverage: "{{ state_attr('weather.home', 'cloud_coverage') }}"
          pressure: "{{ state_attr('weather.home', 'pressure') }}"
          pressure_unit: "{{ state_attr('weather.home', 'pressure_unit') }}"
          wind_bearing: "{{ state_attr('weather.home', 'wind_bearing') }}"
          wind_speed: "{{ state_attr('weather.home', 'wind_speed') }}"
          wind_speed_unit: "{{ state_attr('weather.home', 'wind_speed_unit') }}"
          visibility_unit: "{{ state_attr('weather.home', 'visibility_unit') }}"
          precipitation_unit: "{{ state_attr('weather.home', 'precipitation_unit') }}"
          forecast: "{{ hourly['weather.home'].forecast }}"
1 Like

@madelena : Thanks to your inspirational post and the work of others here I managed to put this together. Very dense on information so a bit less stylish (unfortunately I’m not as good a designer as you are), but I’m still very happy with the result:

Things that are here:

  • Weather forecast
  • Rain forecast graph
  • Inside and outside temperature
  • Home schematic:
    • Windows and doors that are open
    • Blinds that are closed
    • Windows that need opening due to bad air quality
    • Windows that need opening or closing due to rain or temperature difference
    • Blinds that cannot close due to high wind
    • Lock state of the front door
  • Battery state and range of my car (the separation line doubles as a percentage bar chart)
  • Car battery preservation mode (charge to 80% or 100%, the lines on the bar)
  • Car plugged in or charging
  • Next appointment for today for all family members
  • Travel time to bring my son to school
  • Solar production and prediction for today (not a good day today) :frowning:
  • The next evening meal
  • I have a bit of space left for a train departure time, when I get the integration to provide me the right data.
8 Likes

Magnificent!
I’d love to see the code behind this…

1 Like

That is quite a lot, not just in the esp but also preparing all kinds of sensors for window open and close advice and rain graph data. Not the prettiest of code, it kind of organically grew from an experiment to what it is now. This is the adjusted template sensor I use:

- binary_sensor:
    - name: E-ink display 1 Occupancy
      unique_id: "e_ink_display_1_occupancy"
      device_class: "occupancy"
      delay_off:
        minutes: 1
      state: "{{ states('binary_sensor.benedenverdieping_aanwezigheid') }}"
- trigger:
    platform: time_pattern
    minutes: "/1"
  action:
    - service: weather.get_forecasts
      data:
        type: hourly
      target:
        entity_id: weather.heemraadstraat
      response_variable: hourly
  sensor:
    - name: E-ink display 1 Data
      state: "OK"
      attributes:
        room_temperature: >-
          {{ states('sensor.woonkamer_temperatuur_gem') | float(0) | round(1) }}
        weather_condition_now: >-
          {% set cond_now = states('weather.heemraadstraat') %}
          {% if states('sensor.heemraadstraat_zon_fase') == 'night' %}
              {% if cond_now == 'sunny' %} night {% elif cond_now == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond_now }} {% endif %}
          {% else %}
              {{ cond_now }}
          {% endif %}
        weather_temperature: >-
          {{ states('sensor.gemeten_buitentemperatuur') | float(0) | round(1) }}
        weather_condition_0: >-
          {% set cond0 = hourly["weather.heemraadstraat"].forecast[2].condition %}
          {% set next_setting = as_timestamp(states('sensor.heemraadstraat_zon_zonsondergang')) %}
          {% set next_rising = as_timestamp(states('sensor.heemraadstraat_zon_zonsopkomst')) %}
          {% set cond0_time = as_timestamp(hourly["weather.heemraadstraat"].forecast[2].datetime) %}
          {% if cond0_time < next_rising and next_rising < next_setting %}
              {% if cond0 == 'sunny' %} night {% elif cond0 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond0 }} {% endif %}
          {% else %}
              {{ cond0 }}
          {% endif %}
        weather_temperature_0: >-
          {{ hourly["weather.heemraadstraat"].forecast[2].temperature | round(1) }}
        weather_timestamp_0: >-
          {{ as_timestamp(hourly["weather.heemraadstraat"].forecast[2].datetime) | timestamp_custom('%H:%M') }}
        weather_condition_1: >-
          {% set cond1 = hourly["weather.heemraadstraat"].forecast[4].condition %}
          {% set next_setting = as_timestamp(states('sensor.heemraadstraat_zon_zonsondergang')) %}
          {% set next_rising = as_timestamp(states('sensor.heemraadstraat_zon_zonsopkomst')) %}
          {% set cond1_time = as_timestamp(hourly["weather.heemraadstraat"].forecast[4].datetime) %}
          {% if cond1_time < next_rising and next_rising < next_setting %}
              {% if cond1 == 'sunny' %} night {% elif cond1 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond1 }} {% endif %}
          {% else %}
              {{ cond1 }}
          {% endif %}
        weather_temperature_1: >-
          {{ hourly["weather.heemraadstraat"].forecast[4].temperature | round(1) }}
        weather_timestamp_1: >-
          {{ as_timestamp(hourly["weather.heemraadstraat"].forecast[4].datetime) | timestamp_custom('%H:%M') }}
        weather_condition_2: >-
          {% set cond2 = hourly["weather.heemraadstraat"].forecast[6].condition %}
          {% set next_setting = as_timestamp(states('sensor.heemraadstraat_zon_zonsondergang')) %}
          {% set next_rising = as_timestamp(states('sensor.heemraadstraat_zon_zonsopkomst')) %}
          {% set cond2_time = as_timestamp(hourly["weather.heemraadstraat"].forecast[6].datetime) %}
          {% if cond2_time < next_rising and next_rising < next_setting %}
              {% if cond2 == 'sunny' %} night {% elif cond2 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond2 }} {% endif %}
          {% else %}
              {{ cond2 }}
          {% endif %}
        weather_temperature_2: >-
          {{ hourly["weather.heemraadstraat"].forecast[6].temperature | round(1) }}
        weather_timestamp_2: >-
          {{ as_timestamp(hourly["weather.heemraadstraat"].forecast[6].datetime) | timestamp_custom('%H:%M') }}
        weather_condition_3: >-
          {% set cond3 = hourly["weather.heemraadstraat"].forecast[8].condition %}
          {% set next_setting = as_timestamp(states('sensor.heemraadstraat_zon_zonsondergang')) %}
          {% set next_rising = as_timestamp(states('sensor.heemraadstraat_zon_zonsopkomst')) %}
          {% set cond3_time = as_timestamp(hourly["weather.heemraadstraat"].forecast[8].datetime) %}
          {% if cond3_time < next_rising and next_rising < next_setting %}
              {% if cond3 == 'sunny' %} night {% elif cond3 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond3 }} {% endif %}
          {% else %}
              {{ cond3 }}
          {% endif %}
        weather_temperature_3: >-
          {{ hourly["weather.heemraadstraat"].forecast[8].temperature | round(1) }}
        weather_timestamp_3: >-
          {{ as_timestamp(hourly["weather.heemraadstraat"].forecast[8].datetime) | timestamp_custom('%H:%M') }}
        weather_rain_graph: >-
          {% set r = state_attr('sensor.neerslag_buienalarm_regen_data','data') %}
          {% set p = r.precip %}
          {% set d = r.delta | int(300) %}
          {% set c = p | count %}
          {% set s = r.start | int(0) %}
          {% set f = 5 %}
          {{ s | timestamp_custom('%H:%M')
          }};{{ (s + d * (c - 1) * 0.25) | timestamp_custom('%H:%M')
          }};{{ (s + d * (c - 1) * 0.50) | timestamp_custom('%H:%M')
          }};{{ (s + d * (c - 1) * 0.75) | timestamp_custom('%H:%M')
          }};{{ (s + d * (c - 1)) | timestamp_custom('%H:%M')
          }};{{ (r.levels.light | float(0) / f) | round(2)
          }};{{ (r.levels.moderate | float(0) / f) | round(2)
          }};{{ (r.levels.heavy | float(0) / f) | round(2)
          }};{{ (p + [
          r.levels.light | float(0) / f,
          r.levels.moderate | float(0) / f,
          r.levels.heavy | float(0) / f
          ]) | max | float(0) | round(2) }};{{ p | join(';')
          }}
        solar_forecast_today: >-
          {{ (states('sensor.solcast_pv_forecast_forecast_today') | float(0)) | 
            round(iif(states('sensor.solcast_pv_forecast_forecast_today') | float(0) < 1,2,1))  }}
        solar_production_today: >-
          {{ (states('sensor.envoy_122311033724_today_s_energy_production') | float(0) / 1000) | 
            round(iif(states('sensor.envoy_122311033724_today_s_energy_production') | float(0) < 1000,2,1)) }}
        reistijd_leo_kanner: >-
          {{ states('sensor.maximale_reistijd_leo_kanner') | float(0) | round(1) }}
        next_meal: >-
          {% if states('calendar.maaltijden') != 'unknown' and state_attr('calendar.maaltijden', 'message') != None %}
          {{ as_timestamp(state_attr('calendar.maaltijden', 'start_time')) | timestamp_custom('%a') 
              | replace('Mon','Ma') | replace('Tue','Di') | replace('Wed','Wo') | replace('Thu','Do') | replace('Fri','Vr') | replace('Sat','Za') | replace('Sun','Zo') 
          }}: {{ state_attr('calendar.maaltijden', 'message') }}
          {% else %}
          {% endif %}
        apt_gezin: >-
          {% set cal = 'calendar.gezin' %}
          {% if states(cal) != 'unknown' and states(cal) != 'unavailable' %}
            {% set start = as_timestamp(state_attr(cal, 'start_time')) %}
            {% set msg = state_attr(cal, 'message') %}
            {% if msg != None and (as_timestamp(now()) | timestamp_custom('%d-%m-%Y')) == (start | timestamp_custom('%d-%m-%Y')) %}
            G: {{ start | timestamp_custom('%H:%M') | replace('00:00','     ') 
            }} - {{ msg }}
          {% endif %}
          {% endif %}
        apt_edwin: >-
          {% set cal = 'calendar.edwin_delsman_gmail_com' %}
          {% if states(cal) != 'unknown' and states(cal) != 'unavailable' %}
          {% set start = as_timestamp(state_attr(cal, 'start_time')) %}
          {% set msg = state_attr(cal, 'message') %}
          {% if msg != None and (as_timestamp(now()) | timestamp_custom('%d-%m-%Y')) == (start | timestamp_custom('%d-%m-%Y')) %}
          E: {{ start | timestamp_custom('%H:%M') | replace('00:00','     ') 
          }} - {{ msg }}
          {% endif %}
          {% endif %}
        apt_fleur: >-
          {% set cal = 'calendar.fleur' %}
          {% if states(cal) != 'unknown' and states(cal) != 'unavailable' %}
          {% set start = as_timestamp(state_attr(cal, 'start_time')) %}
          {% set msg = state_attr(cal, 'message') %}
          {% if msg != None and (as_timestamp(now()) | timestamp_custom('%d-%m-%Y')) == (start | timestamp_custom('%d-%m-%Y')) %}
          F: {{ start | timestamp_custom('%H:%M') | replace('00:00','     ') 
          }} - {{ msg }}
          {% endif %}
          {% endif %}
        apt_rianne: >-
          {% set cal = 'calendar.rianne' %}
          {% if states(cal) != 'unknown' and states(cal) != 'unavailable' %}
          {% set start = as_timestamp(state_attr(cal, 'start_time')) %}
          {% set msg = state_attr(cal, 'message') %}
          {% if msg != None and (as_timestamp(now()) | timestamp_custom('%d-%m-%Y')) == (start | timestamp_custom('%d-%m-%Y')) %}
          R: {{ start | timestamp_custom('%H:%M') | replace('00:00','     ') 
          }} - {{ msg }}
          {% endif %}
          {% endif %}
        apt_jasper: >-
          {% set cal = 'calendar.jasper' %}
          {% if states(cal) != 'unknown' and states(cal) != 'unavailable' %}
          {% set start = as_timestamp(state_attr(cal, 'start_time')) %}
          {% set msg = state_attr(cal, 'message') %}
          {% if msg != None and (as_timestamp(now()) | timestamp_custom('%d-%m-%Y')) == (start | timestamp_custom('%d-%m-%Y')) %}
          J: {{ start | timestamp_custom('%H:%M') | replace('00:00','     ') 
          }} - {{ msg }}
          {% endif %}
          {% endif %}
        windows_and_screens: >-
          {{ iif(is_state('binary_sensor.openclose_10','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_12','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_6','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_11','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_49','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_51','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_37','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_82','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_55','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_62','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_64','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_48','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_50','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_61','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.openclose_63','on'),'O','-','?') 
          }}{{ iif(state_attr('cover.zolderscherm_links','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(state_attr('cover.zolderscherm_rechts','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(state_attr('cover.slaapkamerscherm_links','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(state_attr('cover.slaapkamerscherm_midden','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(state_attr('cover.slaapkamerscherm_rechts','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(state_attr('cover.keukenscherm','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(is_state('binary_sensor.schuifpui_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.keuken_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.slaapkamer_links_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.slaapkamer_rechts_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.rianne_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.jasper_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.zolder_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.dakkapel_links_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.dakkapel_rechts_openen_advies','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.schuifpui_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.keuken_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.slaapkamer_links_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.slaapkamer_rechts_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.rianne_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.jasper_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.zolder_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.dakkapel_links_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.dakkapel_rechts_sluiten_advies','on'),'S','-','?') 
          }}{{ iif(is_state('binary_sensor.raam_open_achter_linker_slaapkamerscherm','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.raam_open_achter_rechter_slaapkamerscherm','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.raam_open_achter_zolderscherm','on'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.rolschermen_wind_grenswaarde','on'),'W','-','?') 
          }}{{ iif(is_state('binary_sensor.knikarmschermen_wind_grenswaarde','on'),'W','-','?') 
          }}{{ iif(state_attr('cover.dakscherm_links','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(state_attr('cover.dakscherm_rechts','current_position') | float(0) == 100,'-','C','?') 
          }}{{ iif(is_state('lock.enyaq_door_locked','unlocked'),'O','-','?') 
          }}{{ iif(is_state('binary_sensor.enyaq_ramen','on'),'O','-','?') 
          }}{{ iif(is_state('lock.voordeur_nachtslot','locked'),'L','-','?') 
          }}
        car_km_range: "{{ states('sensor.enyaq_electric_range') | float(0) }}"
        car_battery_perc: "{{ states('sensor.enyaq_battery_level') | float(0) }}"
        car_target_perc: "{{ states('input_number.enyaq_eenmalig_laadniveau') | float(0) }}"
        car_charge_status: >-
          {% if states('switch.enyaq_charging') in ['unavailable','unknown'] %}
          ?
          {% elif states('sensor.wallbox_portal_status_description') == 'Disconnected' %}
            Verbroken
          {% elif states('switch.enyaq_charging') == 'on' %}
            Laden
          {% elif states('binary_sensor.enyaq_vehicle_moving') == 'on' %}
            Rijden
          {% elif (states('sensor.enyaq_battery_level') | int(0)) >= (states('input_number.enyaq_eenmalig_laadniveau') | int(0)) %}
            {% if states('binary_sensor.enyaq_charging_cable_connected') == 'on' %}
              Opgeladen
            {% elif states('binary_sensor.enyaq_parking_light') == 'on' %}
              Verlicht
            {% else %}
              Rijklaar
            {% endif %}
          {% elif states('binary_sensor.enyaq_charging_cable_connected') == 'on' %}
            Aangesloten
          {% elif states('binary_sensor.enyaq_parking_light') == 'on' %}
            Verlicht
          {% else %}
            Losgekoppeld
          {% endif %}

The ESP code is too big to post here, it got rejected. :slight_smile:

But this is the display part:

display:
  - platform: waveshare_epaper
    model: 7.50in-bv2-rb
    id: eink_display
    cs_pin: GPIO15
    dc_pin: GPIO27
    busy_pin: 
      number: GPIO25
      inverted: True
    reset_pin: GPIO26
    reset_duration: 2ms
    update_interval: never
    rotation: 0°
    lambda: |-
      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"},
          {"sunset", "\U000F059A"},
          {"sunset-down", "\U000F059B"},
          {"sunset-up", "\U000F059C"},
          {"tornado", "\U000F0F38"},
          {"windy", "\U000F059D"},
          {"windy-variant", "\U000F059E"}
        };

      std::map<std::string, std::string> car_icon_map
        {
          { "power-plug", "\U000F06A5" },
          { "power-plug-outline", "\U000F1425" },
          { "power-plug-off-outline", "\U000F1424" },
          { "connection", "\U000F1616" }, 
          { "car-off", "\U000F0E1C" },
          { "battery", "\U000F0079" },
          { "battery-high", "\U000F12A3" },
          { "battery-medium", "\U000F12A2" }, 
          { "battery-low", "\U000F12A1" },
          { "battery-outline", "\U000F008E" },
          { "battery-charging-100", "\U000F0085" },
          { "battery-charging-high", "\U000F12A6" },
          { "battery-charging-medium", "\U000F12A5" },
          { "battery-charging-low", "\U000F12A4" },
          { "battery-charging-outline", "\U000F089F" },
          { "car-outline", "\U000F14ED" },
          { "lock-open-outline", "\U000F0340" },
          { "car-wash", "\U000F010E" },
          { "solar-power", "\U000F1A73" },
          { "car-clock", "\U000F1974" },
          { "bus-clock", "\U000F08CA" },
          { "train", "\U000F052C" }
        };

      std::map<std::string, int> d_w_s { 
          { "Pui",             0 }, 
          { "Voord",           1 }, 
          { "Schuur",          2 }, 
          { "KeukenRm",        3 }, 
          { "L SKRm",          4 }, 
          { "R SKRm",          5 }, 
          { "JasRm",           6 }, 
          { "RiaRm",           7 }, 
          { "ZDRm",            8 }, 
          { "L DKRm",          9 }, 
          { "R DKRm",         10 }, 
          { "L SKRm W",       11 }, 
          { "R SKRm W",       12 }, 
          { "L DKRm W",       13 }, 
          { "R DKRm W",       14 }, 
          { "ZDSch L",        15 }, 
          { "ZDSch R",        16 }, 
          { "SKSch L",        17 }, 
          { "SKSch M",        18 }, 
          { "SKSch R",        19 }, 
          { "KeukenSch",      20 }, 
          { "Pui O",          21 }, 
          { "KeukenRm O",     22 }, 
          { "SKRm L O",       23 }, 
          { "SKRm R O",       24 }, 
          { "RiaRm O",        25 }, 
          { "JasRm O",        26 }, 
          { "ZDRm O",         27 }, 
          { "DKRm L O",       28 }, 
          { "DKRm R O",       29 }, 
          { "Pui S",          30 }, 
          { "KeukenRm S",     31 }, 
          { "SKRm L S",       32 }, 
          { "SKRm R S",       33 }, 
          { "RiaRm S",        34 }, 
          { "JasRm S",        35 }, 
          { "ZDRm S",         36 }, 
          { "DKRm L S",       37 }, 
          { "DKRm R S",       38 }, 
          { "Rm OA L SKSch",  39 }, 
          { "Rm OA R SKSch",  40 }, 
          { "Rm OA ZDSch",    41 }, 
          { "RolSch WGrens",  42 }, 
          { "KnikSch WGrens", 43 },
          { "L DkSch",        44 },
          { "R DkSch",        45 },
          { "Car D",          46 },
          { "Car W",          47 },
          { "FLock",          48 }
        };

      it.line(0, 468, 799, 468); // bottom line
      if (id(initial_data_received) == false) {
        id(refreshes_skipped) = 100;
        it.printf(390, 234, id(font_small_bold), C_RED, TextAlign::TOP_CENTER, "WACHT OP GEGEVENS...");
      } else {
        std::string stats = (id(statussen).state + "???????????????????????????????????????????????????????").c_str(); // make sure at least as many ? as needed

        // windows and shutters, house outline
        int hx = 400, hy = 20, w=22, h=22, g=6, s=4;

        it.line(hx + 1 * w + 1 * g, hy + 7 * h + 4 * g, hx + 5 * w + 6 * g, hy + 7 * h + 4 * g); // vloer
        it.line(hx + 1 * w + 1 * g, hy + 1 * h + 1 * g, hx + 1 * w + 1 * g, hy + 7 * h + 4 * g); // achtergevel
        it.line(hx + 5 * w + 6 * g, hy + 0 * h + 0 * g, hx + 5 * w + 6 * g, hy + 7 * h + 4 * g); // voorgevel
        it.line(hx + 1 * w + 1 * g, hy + 1 * h + 1 * g, hx + 5 * w + 6 * g, hy + 0 * h + 0 * g); // dak
        it.line(hx + 1 * w + 1 * g - 1, hy + 7 * h + 4 * g - 1, hx + 5 * w + 6 * g + 1, hy + 7 * h + 4 * g - 1); // vloer
        it.line(hx + 1 * w + 1 * g - 1, hy + 1 * h + 1 * g - 1, hx + 1 * w + 1 * g - 1, hy + 7 * h + 4 * g + 1); // achtergevel
        it.line(hx + 5 * w + 6 * g + 1, hy + 0 * h + 0 * g - 1, hx + 5 * w + 6 * g + 1, hy + 7 * h + 4 * g + 1); // voorgevel
        it.line(hx + 1 * w + 1 * g - 1, hy + 1 * h + 1 * g - 1, hx + 5 * w + 6 * g + 1, hy + 0 * h + 0 * g - 1); // dak
        it.rectangle(hx + 1 * w + 2 * g + 0, hy + 1 * h + 2 * g, w, 1.5 * h, stats[d_w_s["DKRm L O"]] == 'O' ? C_RED : COLOR_ON); // linker dakkapel
        it.rectangle(hx + 2 * w + 3 * g + 0, hy + 1 * h + 2 * g, w, 1.5 * h, stats[d_w_s["DKRm R O"]] == 'O' ? C_RED : COLOR_ON); // rechter dakkapel
        it.rectangle(hx + 4 * w + 5 * g + 0, hy + 1 * h + 2 * g, w, 1.5 * h, stats[d_w_s["ZDRm O"]] == 'O' ? C_RED : COLOR_ON); // studeerkamer
        it.rectangle(hx + 1 * w + 2 * g + 0, hy + 3 * h + 3 * g, w, 1.5 * h, stats[d_w_s["JasRm O"]] == 'O' ? C_RED : COLOR_ON); // jasper
        it.rectangle(hx + 2 * w + 3 * g + 0, hy + 3 * h + 3 * g, w, 1.5 * h, stats[d_w_s["RiaRm O"]] == 'O' ? C_RED : COLOR_ON); // rianne
        it.rectangle(hx + 3 * w + 4 * g + 0, hy + 3 * h + 3 * g, w, 1.5 * h, stats[d_w_s["SKRm L O"]] == 'O' ? C_RED : COLOR_ON); // slaapkamer links
        it.rectangle(hx + 4 * w + 5 * g + 0, hy + 3 * h + 3 * g, w, 1.5 * h, stats[d_w_s["SKRm R O"]] == 'O' ? C_RED : COLOR_ON); // slaapkamer rechts
        it.rectangle(hx + 1 * w + 2 * g + 0, hy + 5 * h + 4 * g, w, 2.0 * h, stats[d_w_s["Pui O"]] == 'O' ? C_RED : COLOR_ON); // schuifpui
        it.rectangle(hx + 3 * w + 4 * g + 0, hy + 5 * h + 4 * g, w, 1.0 * h, stats[d_w_s["KeukenRm O"]] == 'O' ? C_RED : COLOR_ON); // keukenraam
        it.rectangle(hx + 4 * w + 5 * g + 0, hy + 5 * h + 4 * g, w, 2.0 * h); // voordeur
        it.rectangle(hx + 0 * w + 0 * g - 1, hy + 5 * h + 4 * g, w, 2.0 * h); // schuurdeur
        it.circle(hx + 0.5 * w - 0 * g - 0.5 * s, hy + 1 * h + 2 * g, s); // linker dakscherm
        it.circle(hx + 1.0 * w - 0 * g - 1.0 * s, hy + 1 * h + 2 * g, s); // rechter dakscherm
        it.circle(hx + 5.0 * w + 7 * g + 1.0 * s, hy + 1 * h + 2 * g, s); // studeerkamerscherm links
        it.circle(hx + 5.5 * w + 7 * g + 0.5 * s, hy + 1 * h + 2 * g, s); // studeerkamerscherm rechts
        it.circle(hx + 5.0 * w + 7 * g + 1.0 * s, hy + 3 * h + 3 * g, s); // slaapkamerscherm links
        it.circle(hx + 5.5 * w + 7 * g + 0.5 * s, hy + 3 * h + 3 * g, s); // slaapkamerscherm midden
        it.circle(hx + 6.0 * w + 7 * g + 0.0 * s, hy + 3 * h + 3 * g, s); // slaapkamerscherm rechts
        it.circle(hx + 5.0 * w + 7 * g + 1.0 * s, hy + 5 * h + 4 * g, s); // keukenscherm
        if (stats[d_w_s["RolSch WGrens"]] == 'W') {
          it.filled_circle(hx + 0.5 * w - 0 * g - 0.5 * s, hy + 1 * h + 2 * g, s, C_RED); // linker dakscherm
          it.filled_circle(hx + 1.0 * w - 0 * g - 1.0 * s, hy + 1 * h + 2 * g, s, C_RED); // rechter dakscherm
          it.filled_circle(hx + 5.0 * w + 7 * g + 1.0 * s, hy + 1 * h + 2 * g, s, C_RED); // studeerkamerscherm links
          it.filled_circle(hx + 5.5 * w + 7 * g + 0.5 * s, hy + 1 * h + 2 * g, s, C_RED); // studeerkamerscherm rechts
          it.filled_circle(hx + 5.0 * w + 7 * g + 1.0 * s, hy + 3 * h + 3 * g, s, C_RED); // slaapkamerscherm links
          it.filled_circle(hx + 5.5 * w + 7 * g + 0.5 * s, hy + 3 * h + 3 * g, s, C_RED); // slaapkamerscherm midden
          it.filled_circle(hx + 6.0 * w + 7 * g + 0.0 * s, hy + 3 * h + 3 * g, s, C_RED); // slaapkamerscherm rechts
        }
        if (stats[d_w_s["KnikSch WGrens"]] == 'W') 
          it.filled_circle(hx + 5.0 * w + 7 * g + 1.0 * s, hy + 5 * h + 4 * g, s, C_RED); // keukenscherm
        if (stats[d_w_s["L DKRm W"]] == 'O' || stats[d_w_s["L DKRm"]] == 'O') // linker dakkapel
          it.filled_rectangle(hx + 1 * w + 2 * g + 0, hy + 1 * h + 2 * g, w, (stats[d_w_s["L DKRm W"]] == 'O' ? 1.5 : 0.5) * h, stats[d_w_s["DKRm L S"]] == 'S' ? C_RED : COLOR_ON); 
        if (stats[d_w_s["DKRm L O"]] == 'O')
          it.filled_circle(hx + 1.5 * w + 2 * g + 0, hy + 1.75 * h + 2 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["R DKRm W"]] == 'O' || stats[d_w_s["R DKRm"]] == 'O') // rechter dakkapel
          it.filled_rectangle(hx + 2 * w + 3 * g + 0, hy + 1 * h + 2 * g, w, (stats[d_w_s["R DKRm W"]] == 'O' ? 1.5 : 0.5) * h, stats[d_w_s["DKRm R S"]] == 'S' ? C_RED : COLOR_ON); 
        if (stats[d_w_s["DKRm R O"]] == 'O')
          it.filled_circle(hx + 2.5 * w + 3 * g + 0, hy + 1.75 * h + 2 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["ZDRm"]] == 'O') // studeerkamer
          it.filled_rectangle(hx + 4 * w + 5 * g + 0, hy + 1 * h + 2 * g, w, 1.5 * h, stats[d_w_s["ZDRm S"]] == 'S' ? C_RED : COLOR_ON);
        if (stats[d_w_s["ZDRm O"]] == 'O')
          it.filled_circle(hx + 4.5 * w + 5 * g + 0, hy + 1.75 * h + 2 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["JasRm"]] == 'O') // jasper
          it.filled_rectangle(hx + 1 * w + 2 * g + 0, hy + 3 * h + 3 * g, w, 1.5 * h, stats[d_w_s["JasRm S"]]  == 'S' ? C_RED : COLOR_ON);
        if (stats[d_w_s["JasRm O"]] == 'O')
          it.filled_circle(hx + 1.5 * w + 2 * g + 0, hy + 3.75 * h + 3 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["RiaRm"]] == 'O') // rianne
          it.filled_rectangle(hx + 2 * w + 3 * g + 0, hy + 3 * h + 3 * g, w, 1.5 * h, stats[d_w_s["RiaRm S"]]  == 'S' ? C_RED : COLOR_ON);
        if (stats[d_w_s["RiaRm O"]] == 'O')
          it.filled_circle(hx + 2.5 * w + 3 * g + 0, hy + 3.75 * h + 3 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["L SKRm W"]] == 'O' || stats[d_w_s["L SKRm"]] == 'O') // slaapkamer links
          it.filled_rectangle(hx + 3 * w + 4 * g + 0, hy + 3 * h + 3 * g, w, (stats[d_w_s["L SKRm W"]] == 'O' ? 1.5 : 0.5) * h, stats[d_w_s["SKRm L S"]] == 'S' ? C_RED : COLOR_ON); 
        if (stats[d_w_s["SKRm L O"]] == 'O')
          it.filled_circle(hx + 3.5 * w + 4 * g + 0, hy + 3.75 * h + 3 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["R SKRm W"]] == 'O' || stats[d_w_s["R SKRm"]] == 'O') // slaapkamer rechts
          it.filled_rectangle(hx + 4 * w + 5 * g + 0, hy + 3 * h + 3 * g, w, (stats[d_w_s["R SKRm W"]] == 'O' ? 1.5 : 0.5) * h, stats[d_w_s["SKRm R S"]] == 'S' ? C_RED : COLOR_ON);
        if (stats[d_w_s["SKRm R O"]] == 'O')
          it.filled_circle(hx + 4.5 * w + 5 * g + 0, hy + 3.75 * h + 3 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["Pui"]] == 'O') // schuifpui
          it.filled_rectangle(hx + 1 * w + 2 * g + 0, hy + 5 * h + 4 * g, w, 2.0 * h, stats[d_w_s["Pui S"]] == 'S' ? C_RED : COLOR_ON);
        if (stats[d_w_s["Pui O"]] == 'O')
          it.filled_circle(hx + 1.5 * w + 2 * g + 0, hy + 5.75 * h + 4 * g, w / 2 - s, C_RED);
        if (stats[d_w_s["KeukenRm"]] == 'O') // keukenraam
          it.filled_rectangle(hx + 3 * w + 4 * g + 0, hy + 5 * h + 4 * g, w, 1.0 * h, stats[d_w_s["KeukenRm S"]] == 'S' ? C_RED : COLOR_ON); 
        if (stats[d_w_s["KeukenRm O"]] == 'O')
          it.filled_circle(hx + 3.5 * w + 4 * g + 0, hy + 5.5 * h + 4 * g, w / 2 - s, C_RED); 
        if (stats[d_w_s["Voord"]] == 'O') // voordeur
          it.filled_rectangle(hx + 4 * w + 5 * g + 0, hy + 5 * h + 4 * g, w, 2.0 * h);
        if (stats[d_w_s["FLock"]] != '-') { // voordeur
          it.filled_circle(hx + 4.5 * w + 5 * g + 0, hy + 6 * h + 4 * g, w / 2 - s, stats[d_w_s["FLock"]] == 'L' ? (stats[d_w_s["Voord"]] == 'O' ? C_WHT : COLOR_ON) : C_RED);
          it.filled_circle(hx + 4.5 * w + 5 * g + 0, hy + 6 * h + 4 * g, w / 2 - s * 2, stats[d_w_s["Voord"]] == 'O' ? C_RED : C_WHT);
        }
        if (stats[d_w_s["Schuur"]] == 'O') // schuurdeur
          it.filled_rectangle(hx + 0 * w + 0 * g - 1, hy + 5 * h + 4 * g, w, 2.0 * h);
        if (stats[d_w_s["L DkSch"]] != '-') // linker dakscherm
          it.filled_rectangle(hx + 0.5 * w - 0 * g - 0.5 * s - 1, hy + 1 * h + 2 * g, s, 1.5 * h, stats[d_w_s["L DkSch"]] == 'C' ? COLOR_ON : C_RED);
        if (stats[d_w_s["R DkSch"]] != '-') // rechter dakscherm
          it.filled_rectangle(hx + 1.0 * w - 0 * g - 1.0 * s - 1, hy + 1 * h + 2 * g, s, 1.5 * h, stats[d_w_s["R DkSch"]] == 'C' ? COLOR_ON : C_RED);
        if (stats[d_w_s["ZDSch L"]] != '-') // studeerkamerscherm links
          it.filled_rectangle(hx + 5.0 * w + 7 * g + 0.0 * s + 1, hy + 1 * h + 2 * g, s, 1.5 * h, stats[d_w_s["ZDSch L"]] == 'C' ? COLOR_ON : C_RED);
        if (stats[d_w_s["ZDSch R"]] != '-') // studeerkamerscherm rechts
          it.filled_rectangle(hx + 5.5 * w + 7 * g - 0.0 * s + 1, hy + 1 * h + 2 * g, s, 1.5 * h, stats[d_w_s["ZDSch R"]] == 'C' ? COLOR_ON : C_RED);
        if (stats[d_w_s["SKSch L"]] != '-') // slaapkamerscherm links
          it.filled_rectangle(hx + 5.0 * w + 7 * g + 0.0 * s + 1, hy + 3 * h + 3 * g, s, 1.5 * h, stats[d_w_s["SKSch L"]] == 'C' ? COLOR_ON : C_RED);
        if (stats[d_w_s["SKSch M"]] != '-') // slaapkamerscherm midden
          it.filled_rectangle(hx + 5.5 * w + 7 * g - 0.5 * s + 1, hy + 3 * h + 3 * g, s, 1.5 * h, stats[d_w_s["SKSch M"]] == 'C' ? COLOR_ON : C_RED);
        if (stats[d_w_s["SKSch R"]] != '-') // slaapkamerscherm rechts
          it.filled_rectangle(hx + 6.0 * w + 7 * g - 1.0 * s + 1, hy + 3 * h + 3 * g, s, 1.5 * h, stats[d_w_s["SKSch R"]] == 'C' ? COLOR_ON : C_RED);
        if (stats[d_w_s["KeukenSch"]] != '-') { // keukenscherm
          it.filled_rectangle(hx + 5.0 * w + 7 * g + 0.5 * s + 0.0 * h + 1, hy + 5 * h + 4 * g + 0 + 1, 0.5 * h, s, stats[d_w_s["KeukenSch"]] == 'C' ? COLOR_ON : C_RED);
          it.filled_rectangle(hx + 5.0 * w + 7 * g + 0.5 * s + 0.5 * h + 1, hy + 5 * h + 4 * g + 1 + 1, 0.5 * h, s, stats[d_w_s["KeukenSch"]] == 'C' ? COLOR_ON : C_RED); 
          it.filled_rectangle(hx + 5.0 * w + 7 * g + 0.5 * s + 1.0 * h + 1, hy + 5 * h + 4 * g + 2 + 1, 0.5 * h, s, stats[d_w_s["KeukenSch"]] == 'C' ? COLOR_ON : C_RED);
        }

        // Weather Section
        int wo_x = 50, wo_y = 50, ho_x = 790, hsp = 100, vsp1 = 60, vsp2 = 97, vsp3 = 140;
        it.printf(wo_x + hsp * 0.50, wo_y, id(font_mdi_large), C_RED, TextAlign::CENTER,  "%s", weather_icon_map[id(weather_condition_now).state.c_str()].c_str());
        it.printf(wo_x + hsp * 2.85, wo_y, id(font_large_bold), id(weather_temperature).state < 0 ? C_RED : COLOR_ON, TextAlign::CENTER_RIGHT, "%1.1f", id(weather_temperature).state);
        it.printf(wo_x + hsp * 3.30, wo_y + 8, id(font_medium_bold), id(weather_temperature).state < 0 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "°C");

        it.printf(wo_x, wo_y + vsp1, id(font_smaller_bold), TextAlign::CENTER, "%s", id(weather_timestamp_0).state.c_str());
        it.printf(wo_x, wo_y + vsp2, id(font_mdi_medium), C_RED, TextAlign::CENTER, "%s", weather_icon_map[id(weather_condition_0).state.c_str()].c_str());
        it.printf(wo_x, wo_y + vsp3, id(font_small_bold), TextAlign::CENTER, "%1.0f°C", id(weather_temperature_0).state);

        it.printf(wo_x + hsp, wo_y + vsp1, id(font_smaller_bold), TextAlign::CENTER, "%s", id(weather_timestamp_1).state.c_str());
        it.printf(wo_x + hsp, wo_y + vsp2, id(font_mdi_medium), C_RED, TextAlign::CENTER, "%s", weather_icon_map[id(weather_condition_1).state.c_str()].c_str());
        it.printf(wo_x + hsp, wo_y + vsp3, id(font_small_bold), TextAlign::CENTER, "%1.0f°C", id(weather_temperature_1).state);

        it.printf(wo_x + hsp * 2, wo_y + vsp1, id(font_smaller_bold), TextAlign::CENTER, "%s", id(weather_timestamp_2).state.c_str());
        it.printf(wo_x + hsp * 2, wo_y + vsp2, id(font_mdi_medium), C_RED, TextAlign::CENTER, "%s", weather_icon_map[id(weather_condition_2).state.c_str()].c_str());
        it.printf(wo_x + hsp * 2, wo_y + vsp3, id(font_small_bold), TextAlign::CENTER, "%1.0f°C", id(weather_temperature_2).state);

        it.printf(wo_x + hsp * 3, wo_y + vsp1, id(font_smaller_bold), TextAlign::CENTER, "%s", id(weather_timestamp_3).state.c_str());
        it.printf(wo_x + hsp * 3, wo_y + vsp2, id(font_mdi_medium), C_RED, TextAlign::CENTER, "%s", weather_icon_map[id(weather_condition_3).state.c_str()].c_str());
        it.printf(wo_x + hsp * 3, wo_y + vsp3, id(font_small_bold), TextAlign::CENTER, "%1.0f°C", id(weather_temperature_3).state);

        // rain graph
        std::string gdata = id(rain_graph).state;
        char *token = strtok (&gdata[0],";");
        std::string t1 = str_until(token == NULL ? "??:??" : token,';'); token = strtok(NULL, ";");
        std::string t2 = str_until(token == NULL ? "??:??" : token,';'); token = strtok(NULL, ";");
        std::string t3 = str_until(token == NULL ? "??:??" : token,';'); token = strtok(NULL, ";");
        std::string t4 = str_until(token == NULL ? "??:??" : token,';'); token = strtok(NULL, ";");
        std::string t5 = str_until(token == NULL ? "??:??" : token,';'); token = strtok(NULL, ";");
        auto g_low = parse_number<float>(str_until(token == NULL ? "0.1" : token,';')).value_or(0.0f); token = strtok(NULL, ";");
        auto g_med = parse_number<float>(str_until(token == NULL ? "0.1" : token,';')).value_or(0.0f); token = strtok(NULL, ";");
        auto g_hgh = parse_number<float>(str_until(token == NULL ? "0.1" : token,';')).value_or(0.0f); token = strtok(NULL, ";");
        auto g_max = parse_number<float>(str_until(token == NULL ? "0.1" : token,';')).value_or(0.0f) * 1.1f; token = strtok(NULL, ";");
        int g_x = 11, g_y = 250, g_w = 5, g_h = 80, g_c = 0, g_t = 5;
        it.filled_rectangle(g_x, g_y + g_h - 1, 73 * g_w, 2, COLOR_ON); // x-axis
        it.filled_rectangle(g_x - 1, g_y, 2, g_h, COLOR_ON); // y-axis
        float prev = parse_number<float>(str_until(token == NULL ? "0.1" : token,';')).value_or(0.0f) * g_h / g_max; token = strtok(NULL, ";");
        if (prev != 0) it.filled_rectangle(g_x + g_c, g_y + g_h - prev, g_w, prev, C_RED);
        g_c += g_w; 
        while (token != NULL) {
          auto g_r = parse_number<float>(str_until(token == NULL ? "0.1" : token,';')).value_or(0.0f) * g_h / g_max; token = strtok(NULL, ";");
          float itv = (2 * prev + g_r) / 3;
          if (itv != 0) it.filled_rectangle(g_x + g_c, g_y + g_h - itv, g_w, itv, C_RED);
          g_c += g_w;
          itv = (prev + 2 * g_r) / 3;
          if (itv != 0) it.filled_rectangle(g_x + g_c, g_y + g_h - itv, g_w, itv, C_RED);
          g_c += g_w;
          if (g_r != 0) it.filled_rectangle(g_x + g_c, g_y + g_h - g_r, g_w, g_r, C_RED);
          g_c += g_w;
          prev = g_r;
        }
        it.rectangle(g_x - 1, g_y + g_h - g_hgh * g_h / g_max, g_c, 1); // high
        it.rectangle(g_x - 1, g_y + g_h - g_med * g_h / g_max, g_c, 1); // medium
        it.rectangle(g_x - 1, g_y + g_h - g_low * g_h / g_max, g_c, 1);  // low
        it.rectangle(g_x - 1, g_y + g_h, g_c, 1); // bottom
        it.printf(g_x + 0.00 * g_c + 0, g_y + g_h + g_t, id(font_tiny_book), COLOR_ON, TextAlign::TOP_LEFT, "%s", t1.c_str()); 
        it.printf(g_x + 0.25 * g_c + 7, g_y + g_h + g_t, id(font_tiny_book), COLOR_ON, TextAlign::TOP_CENTER, "%s", t2.c_str()); // smuggle bit more right because first time differs
        it.printf(g_x + 0.50 * g_c + 0, g_y + g_h + g_t, id(font_tiny_book), COLOR_ON, TextAlign::TOP_CENTER, "%s", t3.c_str());
        it.printf(g_x + 0.75 * g_c - 7, g_y + g_h + g_t, id(font_tiny_book), COLOR_ON, TextAlign::TOP_CENTER, "%s", t4.c_str());
        it.printf(g_x + 1.00 * g_c + 0, g_y + g_h + g_t, id(font_tiny_book), COLOR_ON, TextAlign::TOP_RIGHT, "%s", t5.c_str()); // smuggle bit more left because last time differs

        // Car Section
        it.printf(ho_x - hsp * 0.45, wo_y, id(font_large_bold), id(room_temperature).state < 0 ? C_RED : COLOR_ON, TextAlign::CENTER_RIGHT, "%1.1f", id(room_temperature).state);
        it.printf(ho_x - hsp * 0.00, wo_y + 8, id(font_medium_bold), id(room_temperature).state < 0 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "°C");
        it.printf(ho_x - hsp * 0.45, wo_y + vsp2 + 0, id(font_large_book), id(car_battery_perc).state <= 45 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "%1.0f", id(car_battery_perc).state);
        it.printf(ho_x - hsp * 0.00, wo_y + vsp2 - 5, id(font_medium_bold), id(car_battery_perc).state <= 45 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "%%");
        it.printf(ho_x - hsp * 0.45, wo_y + vsp3 + 19, id(font_large_book), id(car_km_range).state < 100 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "%1.0f", id(car_km_range).state);
        it.printf(ho_x - hsp * 0.00, wo_y + vsp3 + 14, id(font_small_bold), id(car_km_range).state < 100 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "km");

        int b_y = 225, b_x = 10, b_h = 8, l_h = 2, b_l = 800 - 2 * b_x, t_h = 12,
            b_f = b_l * id(car_battery_perc).state / 100, b_t = b_l * id(car_target_perc).state / 100;
        it.rectangle(b_x, b_y + (b_h - l_h) / 2, b_l, l_h, id(car_battery_perc).state <= 45 ? COLOR_ON : C_RED); // thin line
        it.filled_rectangle(b_x + (b_l - b_f) / 2, b_y, b_f, b_h, id(car_battery_perc).state <= 45 ? C_RED : COLOR_ON); // battery percentage bar
        it.filled_rectangle(b_x + (b_l - b_t) / 2, b_y + (b_h - t_h) / 2, l_h, t_h, id(car_battery_perc).state <= 45 ? COLOR_ON : C_RED); // target perc markers
        it.filled_rectangle(b_x + (b_l + b_t) / 2, b_y + (b_h - t_h) / 2, l_h, t_h, id(car_battery_perc).state <= 45 ? COLOR_ON : C_RED);

        std::string cstate = id(car_charge_status).state.c_str(), bat = "", car = "";
        Color c_col = COLOR_ON, b_col = COLOR_ON;
        if (cstate == "?") { bat = "connection"; b_col = C_RED; }
        else if (cstate == "Laden") {
          if (id(car_battery_perc).state <= 35) { bat = "battery-charging-outline"; b_col = C_RED;  }
          else if (id(car_battery_perc).state <= 45) { bat = "battery-charging-low"; b_col = C_RED;  }
          else if (id(car_battery_perc).state <= 55) { bat = "battery-charging-low"; }
          else if (id(car_battery_perc).state <= 75) { bat = "battery-charging-medium"; }
          else if (id(car_battery_perc).state <= 95) { bat = "battery-charging-high"; }
          else bat = "battery-charging-100";
        } else if (cstate == "Opgeladen") {
          bat = "power-plug";
          // if (id(car_battery_perc).state <= 35) { bat = "battery-outline"; b_col = C_RED; }
          // else if (id(car_battery_perc).state <= 45) { bat = "battery-low"; b_col = C_RED;  }
          // else if (id(car_battery_perc).state <= 55) { bat = "battery-low"; }
          // else if (id(car_battery_perc).state <= 75) { bat = "battery-medium"; }
          // else if (id(car_battery_perc).state <= 95) { bat = "battery-high"; }
          // else bat = "battery";
        } else if (cstate == "Verbroken") { bat = "connection"; b_col = C_RED; }
        else if (cstate == "Aangesloten") { bat = "power-plug-outline"; }
        if (stats[d_w_s["Car D"]] == 'O') { car = "lock-open-outline"; c_col = C_RED; }
        else if (stats[d_w_s["Car D"]] == 'O') { car = "car-wash"; c_col = C_RED; }
        if (bat != "")
          it.printf(ho_x - hsp * 1.5, wo_y + vsp2 - 5, id(font_mdi_title), b_col, TextAlign::BOTTOM_RIGHT, "%s", car_icon_map[bat].c_str());
        if (car != "")
          it.printf(ho_x - hsp * 1.5, wo_y + vsp3 + 14, id(font_mdi_title), c_col, TextAlign::BOTTOM_RIGHT, "%s", car_icon_map[car].c_str());

        // calendar
        int cal_x = 405, cal_y = 255, cal_dx = 30, cl = 0;
        if (id(apt_gezin).state != "")
          it.printf(cal_x, cal_y + cl++ * cal_dx, id(font_smaller_bold), TextAlign::TOP_LEFT, "%s", id(apt_gezin).state.c_str());
        if (id(apt_edwin).state != "")
          it.printf(cal_x, cal_y + cl++ * cal_dx, id(font_smaller_bold), TextAlign::TOP_LEFT, "%s", id(apt_edwin).state.c_str());
        if (id(apt_fleur).state != "")
          it.printf(cal_x, cal_y + cl++ * cal_dx, id(font_smaller_bold), TextAlign::TOP_LEFT, "%s", id(apt_fleur).state.c_str());
        if (id(apt_rianne).state != "")
          it.printf(cal_x, cal_y + cl++ * cal_dx, id(font_smaller_bold), TextAlign::TOP_LEFT, "%s", id(apt_rianne).state.c_str());
        if (id(apt_jasper).state != "")
          it.printf(cal_x, cal_y + cl++ * cal_dx, id(font_smaller_bold), TextAlign::TOP_LEFT, "%s", id(apt_jasper).state.c_str());
        it.filled_rectangle(790, cal_y, 10, 5 * cal_dx, C_WHT); // replace by clipping

        // Solar Section
        int sol_y = (cal_y  + cl * cal_dx) > 350 ? 430 : 420;
        float prod = id(solar_production_today).state, fcast = id(solar_forecast_today).state;
        it.printf(prod >= 20 ? 385 : 390, sol_y - 2, id(font_mdi_title), C_RED, TextAlign::BOTTOM_LEFT, "%s", car_icon_map["solar-power"].c_str());
        it.printf(520, sol_y + 10, id(font_large_bold), COLOR_ON, TextAlign::BOTTOM_CENTER, prod > 0.005 && prod < 0.1 ? "%1.2f" : "%1.1f", prod);
        it.printf(598, sol_y + 5, id(font_large_book), COLOR_ON, TextAlign::BOTTOM_LEFT, "/");
        it.printf(ho_x - hsp * 0.60, sol_y + 5, id(font_large_book), COLOR_ON, TextAlign::BOTTOM_RIGHT, "%1.1f", fcast);
        it.printf(ho_x + hsp * 0.05, sol_y, id(font_small_bold), COLOR_ON, TextAlign::BOTTOM_RIGHT, "kWh");

        // travel section
        it.printf(10, sol_y - 2, id(font_mdi_title), C_RED, TextAlign::BOTTOM_LEFT, "%s", car_icon_map["car-clock"].c_str());
        it.printf(145, sol_y + 5, id(font_large_book), id(reistijd_leo_kanner).state >= 40 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "%1.0f", id(reistijd_leo_kanner).state);
        it.printf(150, sol_y, id(font_small_bold), COLOR_ON, TextAlign::BOTTOM_LEFT, "min");

        it.printf(10, 458, id(font_small_bold), TextAlign::BOTTOM_LEFT, "%s", id(next_meal).state.c_str());
        char str[17];
        time_t currTime = id(homeassistant_time).now().timestamp;
        strftime(str, sizeof(str), "%H:%M", localtime(&currTime));
        it.printf(798, 463, id(font_tiny_book), id(refreshes_skipped) >= 15 ? C_RED : COLOR_ON, TextAlign::BOTTOM_RIGHT, "Laatste update: %s", str);
      }
4 Likes

Dear Kurmu, I am interested how you solved to display the electricity prices for the next hours. YAML snippets are welcome. Many thanks

Hey! Sorry I haven’t replied. Anyway, here is my yaml configuration.

esphome:
  name: "epaper-display-01"
  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: "Antureiden alustavat tiedot vastaanotettu: Päivitetään näyttö..."
        - lambda: 'id(initial_data_received) = true;'
        - script.execute: update_screen

esp32:
  board: esp32dev
  framework:
    type: arduino


# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

# Enable sun integration
sun:
  latitude: 60.1
  longitude: 24.4

# Buttons for controlling display
button:
  - platform: shutdown
    name: "ePaper 01 - Shutdown"
  - platform: restart
    name: "ePaper 01 - Restart"
  - platform: template
    name: "ePaper 01 - Refresh Screen"
    entity_category: config
    on_press:
      - script.execute: update_screen
      
      
# 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

# Wifi information
wifi:
  ssid: "WiFi"
  password: "5CDH4ZrN141I"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Epaper-Display-01"
    password: "5CDH4ZrN141I"


# Include custom fonts
font:
  - file: 'fonts/GothamRnd-Book.ttf'
    id: font_small_book
    size: 18
    glyphs: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z', 'Å', 'Ä', 'Ö', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '-', ' ', '°', '.','m', '%']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_large_bold
    size: 108
    glyphs: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z', 'Å', 'Ä', 'Ö', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '-', ' ', '°', '.']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_title
    size: 54
    glyphs: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z', 'Å', 'Ä', 'Ö', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '-', ' ', '°', '.']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_medium_bold
    size: 30
    glyphs: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z', 'Å', 'Ä', 'Ö', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '-', ' ', '°', '.']
  - file: 'fonts/GothamRnd-Bold.ttf'
    id: font_small_bold
    size: 18
    glyphs: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', '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', 'x', 'y', 'z', 'ä', 'ö', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '-', ' ', '°', '.', '%', '/', ',', 'W']

  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_large
    size: 96
    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
      - "\U000F059B" # mdi-weather-sunset-down
      - "\U000F059C" # mdi-weather-sunset-up
      - "\U000F0F38" # mdi-weather-tornado
      - "\U000F059D" # mdi-weather-windy
      - "\U000F059E" # mdi-weather-windy-variant
      - "\U000F0F61" # mdi-moon-first-quarter
      - "\U000F0F62" # mdi-moon-full
      - "\U000F0F63" # mdi-moon-last-quarter
      - "\U000F0F64" # mdi-moon-new
      - "\U000F0F65" # mdi-moon-waning-crescent
      - "\U000F0F66" # mdi-moon-waning-gibbous
      - "\U000F0F67" # mdi-moon-waxing-crescent
      - "\U000F0F68" # mdi-moon-waxing-gibbous
      - "\U000F050F" # lämpötila
      - "\U000F029A" # ilmanpaine
      - "\U000F058E" # kosteus
      - "\U000F156D" # sofa
      - "\U000F0FD1" # bed
      - "\U000F181D" # countertop
      - "\U000F09A0" # shower
      - "\U000F140C" # lightning-bolt-outlin
      - "\U000F1904" # home-lightning-bolt-outline
      - "\U000F1A58" # meter-electric-outline
      - "\U000F0E02" # thermometer-chevron-down
      - "\U000F0E03" # thermometer-chevron-up
  - file: 'fonts/materialdesignicons-webfont.ttf'
    id: font_mdi_medium
    size: 36
    glyphs: *mdi-weather-glyphs

# Include Images
image:
  - file: "images/majakka_s.png"
    id: image_majakka
   # type: BINARY

# Check if motion is detected in the living room.
binary_sensor:
  - platform: homeassistant
    entity_id: binary_sensor.epaper_display_01_motion_detected
    id: motion_detected

# SENSORS
sensor:
  # Create sensors for monitoring Weatherman remotely.
  - platform: template
    name: "ePaper_01 - Display Last Update"
    device_class: timestamp
    entity_category: "diagnostic"
    id: display_last_update
    
  - platform: template
    name: "ePaper_01 - 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: "ePaper_01 - WiFi Signal Strength"
    id: wifisignal
    unit_of_measurement: "dBm"
    entity_category: "diagnostic"
    update_interval: 60s

  - platform: sun
    name: Sun Elevation
    type: elevation
    id: solar_angle  
#Valoisuusanturi P34
  - platform: adc
    pin: 34
    name: "ePaper valoisuus"
    update_interval: 30s
    device_class: illuminance
    unit_of_measurement: lx
    accuracy_decimals: 0
    filters:
      - calibrate_linear:
        - 0 -> 0
        - 0.14 -> 0
        - 1.1 -> 1.1
      - lambda: |-
          if (id(solar_angle).state > 2) {
            return (x / 10000.0) * 2000000.0;
          } else {
            return 0.0;
          }
      - sliding_window_moving_average:
          window_size: 20
          send_every: 3 
#DHT22 P32
  - platform: dht
    pin: 32
    model: DHT22
    temperature:
      name: "ePaper lämpötila"
      filters:
          - multiply: 1
    humidity:
      name: "ePaper kosteus"
    update_interval: 60s
  # Sensor from HA
  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_temp_low
    id: weather_temp_low
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_temp_high
    id: weather_temp_high
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_temperature_0
    id: weather_temperature_0
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_temperature_1
    id: weather_temperature_1
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_temperature_2
    id: weather_temperature_2
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_temperature_3
    id: weather_temperature_3
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_temperature_4
    id: weather_temperature_4
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: ulkolampotila
    id: ulkolampotila
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: kosteus
    id: kosteus
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: ilmanpaine
    id: ilmanpaine
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: tuuli
    id: tuuli
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
  # Room sensors
  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: mh_lampotila
    id: mh_lampotila
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: mh_kosteus
    id: mh_kosteus
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
  
  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: mh_valoisuus
    id: mh_valoisuus
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: mh_pm25
    id: mh_pm25
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
        
  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: oh_lampotila
    id: oh_lampotila
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: oh_kosteus
    id: oh_kosteus
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: kph_lampotila
    id: kph_lampotila
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: kph_kosteus
    id: kph_kosteus
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: keittio_lampotila
    id: keittio_lampotila
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: keittio_valoisuus
    id: keittio_valoisuus
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
  #Nordpool sensors
  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_current
    id: nordpool_current
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_min
    id: nordpool_min
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_avg
    id: nordpool_avg
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_max
    id: nordpool_max
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_1h
    id: nordpool_1h
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_2h
    id: nordpool_2h
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_3h
    id: nordpool_3h
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_4h
    id: nordpool_4h
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_5h
    id: nordpool_5h
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_6h
    id: nordpool_6h
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_7h
    id: nordpool_7h
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: total_hourly_energy
    id: total_hourly_energy
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: total_daily_energy
    id: total_daily_energy
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

text_sensor:
  - platform: homeassistant
    entity_id: weather.kotkanpesa_hourly
    id: weather_state
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_condition_now
    id: weather_condition_now
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_condition_0
    id: weather_condition_0
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_timestamp_0
    id: weather_timestamp_0
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_condition_1
    id: weather_condition_1
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_timestamp_1
    id: weather_timestamp_1
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_condition_2
    id: weather_condition_2
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_timestamp_2
    id: weather_timestamp_2
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_condition_3
    id: weather_condition_3
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_timestamp_3
    id: weather_timestamp_3
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_condition_4
    id: weather_condition_4
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: weather_timestamp_4
    id: weather_timestamp_4
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: tuuli_suunta
    id: tuuli_suunta
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_0
    id: nordpool_timestamp_0
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_1
    id: nordpool_timestamp_1
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_2
    id: nordpool_timestamp_2
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_3
    id: nordpool_timestamp_3
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_4
    id: nordpool_timestamp_4
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_5
    id: nordpool_timestamp_5
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_6
    id: nordpool_timestamp_6
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_timestamp_7
    id: nordpool_timestamp_7
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'
        
  - platform: homeassistant
    entity_id: sensor.epaper_data
    attribute: nordpool_text
    id: nordpool_text
    on_value:
      then:
        - lambda: 'id(data_updated) = true;'

## Sunrise
  - platform: sun
    type: sunrise
    id: sun_sunrise
    format: "%H:%M"
## Sunset
  - platform: sun
    type: sunset
    id: sun_sunset
    format: "%H:%M"

# Define colors
# This design is white on black so this is necessary.
color:
  - id: color_bg
    red: 0%
    green: 0%
    blue: 0%
    white: 50%
  - id: color_text
    red: 0%
    green: 0%
    blue: 0%
    white: 0%


# 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: 15
    dc_pin: 27
    busy_pin: 
      number: 25
      inverted: True
    reset_pin: 26
    reset_duration: 2ms
    model: 7.50inV2
    update_interval: never
#    auto_clear_enabled: false
    rotation: 90°
    lambda: |-
      // Map weather states to MDI characters.
      std::map<std::string, std::string> weather_icon_map
        {
          {"cloudy", "\U000F0590"},
          {"cloudy-alert", "\U000F0F2F"},
          {"cloudy-arrow-right", "\U000F0E6E"},
          {"fog", "\U000F0591"},
          {"hail", "\U000F0592"},
          {"hazy", "\U000F0F30"},
          {"hurricane", "\U000F0898"},
          {"lightning", "\U000F0593"},
          {"lightning-rainy", "\U000F067E"},
          {"night", "\U000F0594"},
          {"clear-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"},
          {"sunset", "\U000F059A"},
          {"sunset-down", "\U000F059B"},
          {"sunset-up", "\U000F059C"},
          {"tornado", "\U000F0F38"},
          {"windy", "\U000F059D"},
          {"windy-variant", "\U000F059E"},
        };

      // Fill background.
      // it.fill(color_bg);

      // # Show loading screen before data is received.
      if (id(initial_data_received) == false) {
        it.printf(240, 390, id(font_small_bold), TextAlign::TOP_CENTER, "ODOTETAAN TIETOJA...");
      } else {

        // # Topic section, x (vaaka), y (pysty)
        // it.image(0, 88, id(title_weather)); 
        it.printf(240, 15, id(font_title), TextAlign::TOP_CENTER, "KIRKKONUMMI");
        // #Print day of the month
        it.strftime(240, 75, id(font_small_bold), TextAlign::TOP_CENTER, "%d.%m.%Y", id(homeassistant_time).now());
        //# Print sunrise and sunset
        it.printf(60, 65, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F059C");
        it.printf(110, 75, id(font_small_bold), TextAlign::TOP_CENTER, "%s", id(sun_sunrise).state.c_str());
        it.printf(360, 65, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F059B");
        it.printf(410, 75, id(font_small_bold), TextAlign::TOP_CENTER, "%s", id(sun_sunset).state.c_str());
        // # line
        it.rectangle(5, 100, 700, 1);
        // # Weather now
        it.printf(60, 100, id(font_mdi_large), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_now).state.c_str()].c_str());
        it.printf(270, 100, id(font_large_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(ulkolampotila).state);
        //# Temp high and low, moisture
        it.printf(220, 207, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0E02");
        it.printf(265, 217, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temp_low).state);
        it.printf(330, 207, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0E03");
        it.printf(375, 217, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temp_high).state);
        // # Wind and pressure
        it.printf(220, 252, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F059D");
        it.printf(270, 262, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0fm/s", id(tuuli).state);
        it.printf(355, 262, id(font_small_bold), TextAlign::TOP_CENTER, "%s", id(tuuli_suunta).state.c_str());
        // # Pressure and moisture
        it.printf(220, 297, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F029A");
        it.printf(280, 307, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f hPa", id(ilmanpaine).state);
        it.printf(345, 297, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F058E");
        it.printf(375, 307, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f", id(kosteus).state);
        it.printf(397, 307, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "%");
        // # Weather next hour
        it.printf(175, 342, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(weather_timestamp_0).state.c_str());
        it.printf(175, 366, id(font_mdi_medium), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_0).state.c_str()].c_str());
        it.printf(175, 414, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_0).state);
        // # Weather +2h
        it.printf(240, 342, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(weather_timestamp_1).state.c_str());
        it.printf(240, 366, id(font_mdi_medium), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_1).state.c_str()].c_str());
        it.printf(240, 414, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_1).state);
        // # Weather +3h
        it.printf(305, 342, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(weather_timestamp_2).state.c_str());
        it.printf(305, 366, id(font_mdi_medium), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_2).state.c_str()].c_str());
        it.printf(305, 414, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_2).state);
        // # Weather +4h
        it.printf(370, 342, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(weather_timestamp_3).state.c_str());
        it.printf(370, 366, id(font_mdi_medium), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_3).state.c_str()].c_str());
        it.printf(370, 414, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_3).state);
        // # Weather +5h
        it.printf(435, 342, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(weather_timestamp_4).state.c_str());
        it.printf(435, 366, id(font_mdi_medium), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(weather_condition_4).state.c_str()].c_str());
        it.printf(435, 414, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C", id(weather_temperature_4).state);
        // # line
        it.rectangle(5, 440, 700, 1);
        // # Living room (450,458)
        it.printf(175, 450, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F156D");
        it.printf(230, 458, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C,", id(oh_lampotila).state);
        it.printf(271, 458, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f", id(oh_kosteus).state);
        it.printf(292, 458, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "%");
        // # Bedroom (490,498)
        it.printf(175, 490, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0FD1");
        it.printf(230, 498, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C,", id(mh_lampotila).state);
        it.printf(271, 498, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f", id(mh_kosteus).state);
        it.printf(292, 498, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "%,");
        it.printf(327, 498, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0flx,", id(mh_valoisuus).state);
        it.printf(395, 498, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0fug/m3", id(mh_pm25).state);
        // # Kitchen, toilet and hallway (530,538)
        it.printf(175, 530, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F181D");
        it.printf(230, 538, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C,", id(keittio_lampotila).state);
        it.printf(275, 530, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F09A0");
        it.printf(325, 538, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C,", id(kph_lampotila).state);
        it.printf(366, 538, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f", id(kph_kosteus).state);
        it.printf(407, 538, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "%");
        // # Some stuff
        it.printf(175, 570, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F0FD1");
        it.printf(230, 578, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f°C,", id(mh_lampotila).state);
        it.printf(271, 578, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0f", id(mh_kosteus).state);
        it.printf(292, 578, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "%,");
        it.printf(327, 578, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0flx,", id(mh_valoisuus).state);
        it.printf(395, 578, id(font_small_bold), TextAlign::TOP_CENTER, "%2.0fug/m3", id(mh_pm25).state);
        // # line
        it.rectangle(5, 610, 700, 1);
        // # Electricity
        it.printf(45, 635, id(font_mdi_large), TextAlign::TOP_CENTER, "\U000F1904");
        it.printf(240, 620, id(font_small_bold), TextAlign::TOP_CENTER, "%s", id(nordpool_text).state.c_str());
        // # Nordpool min/avg/max
        it.printf(144, 650, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "KALLEIN:");
        it.printf(280, 650, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc/kWh", id(nordpool_max).state);
        it.printf(158, 675, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "KESKIARVO:");
        it.printf(280, 675, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc/kWh", id(nordpool_avg).state);
        it.printf(140, 700, id(font_small_bold), TextAlign::TOP_CENTER, "%s", "HALVIN:");
        it.printf(280, 700, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc/kWh", id(nordpool_min).state);
        // # Nordpool hour
        it.printf(39, 730, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(nordpool_timestamp_1).state.c_str());
        it.printf(39, 753, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc", id(nordpool_1h).state);
        it.printf(106, 730, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(nordpool_timestamp_2).state.c_str());
        it.printf(106, 753, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc", id(nordpool_2h).state);
        it.printf(173, 730, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(nordpool_timestamp_3).state.c_str());
        it.printf(173, 753, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc", id(nordpool_3h).state);
        it.printf(240, 730, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(nordpool_timestamp_4).state.c_str());
        it.printf(240, 753, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc", id(nordpool_4h).state);
        it.printf(307, 730, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(nordpool_timestamp_5).state.c_str());
        it.printf(307, 753, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc", id(nordpool_5h).state);
        it.printf(374, 730, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(nordpool_timestamp_6).state.c_str());
        it.printf(374, 753, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc", id(nordpool_6h).state);
        it.printf(441, 730, id(font_small_book), TextAlign::TOP_CENTER, "%s", id(nordpool_timestamp_7).state.c_str());
        it.printf(441, 753, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fc", id(nordpool_7h).state);
        // # Consumption
        it.printf(365, 642, id(font_mdi_medium), TextAlign::TOP_CENTER, "\U000F1A58");
        it.printf(424, 650, id(font_small_bold), TextAlign::TOP_CENTER, "%.1fkWh", id(total_daily_energy).state);
        // # IMAGES
        it.image(3, 200, id(image_majakka)); //MAJAKKA IMAGE
        
        // Refresh Timestamp
        // Code by EnsconcE from https://community.home-assistant.io/t/esphome-show-time/348903
        char str[17];
        time_t currTime = id(homeassistant_time).now().timestamp;
        strftime(str, sizeof(str), "%H:%M", localtime(&currTime));
        it.printf(240, 780, id(font_small_book), TextAlign::TOP_CENTER, "PÄIVITETTY %s", str);
      }

captive_portal:
1 Like

And here is rest of my conf.

Sensors from templates.yaml

#================================
#=== ePaper display sensors
#================================
#template:
  # Bundle up all the data to send over to ePaper (ESPHome device).
  - trigger:
      platform: time_pattern
      minutes: "/1"
    sensor:
      - name: ePaper Data
        state: "OK"
        attributes:
          weather_condition_now: >
            {% set cond_now = states('sensor.weather_hourly') %}
            {% if states('sun.sun') == 'below_horizon' %}
                {% if cond_now == 'sunny' %} night {% elif cond_now == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond_now }} {% endif %}
            {% else %}
                {{ cond_now }}
            {% endif %}

          weather_condition_0: >
            {% set cond0 = state_attr('sensor.weather_hourly', 'forecast')[0].condition %}
            {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
            {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
            {% set cond0_time = as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[0].datetime) %}
            {% if cond0_time < next_rising and next_rising < next_setting %}
                {% if cond0 == 'sunny' %} night {% elif cond0 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond0 }} {% endif %}
            {% else %}
                {{ cond0 }}
            {% endif %}
          weather_temperature_0: >
            {{ state_attr('sensor.weather_hourly', 'forecast')[0].temperature | round }}
          weather_timestamp_0: >
            {{ as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[0].datetime) | timestamp_custom('%H:%M') }}

          weather_condition_1: >
            {% set cond1 = state_attr('sensor.weather_hourly', 'forecast')[1].condition %}
            {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
            {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
            {% set cond1_time = as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[1].datetime) %}
            {% if cond1_time < next_rising and next_rising < next_setting %}
                {% if cond1 == 'sunny' %} night {% elif cond1 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond1 }} {% endif %}
            {% else %}
                {{ cond1 }}
            {% endif %}
          weather_temperature_1: >
            {{ state_attr('sensor.weather_hourly', 'forecast')[1].temperature | round }}
          weather_timestamp_1: >
            {{ as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[1].datetime) | timestamp_custom('%H:%M') }}

          weather_condition_2: >
            {% set cond2 = state_attr('sensor.weather_hourly', 'forecast')[2].condition %}
            {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
            {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
            {% set cond2_time = as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[2].datetime) %}
            {% if cond2_time < next_rising and next_rising < next_setting %}
                {% if cond2 == 'sunny' %} night {% elif cond2 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond2 }} {% endif %}
            {% else %}
                {{ cond2 }}
            {% endif %}
          weather_temperature_2: >
            {{ state_attr('sensor.weather_hourly', 'forecast')[2].temperature | round }}
          weather_timestamp_2: >
            {{ as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[2].datetime) | timestamp_custom('%H:%M') }}

          weather_condition_3: >
            {% set cond3 = state_attr('sensor.weather_hourly', 'forecast')[3].condition %}
            {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
            {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
            {% set cond3_time = as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[3].datetime) %}
            {% if cond3_time < next_rising and next_rising < next_setting %}
                {% if cond3 == 'sunny' %} night {% elif cond3 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond3 }} {% endif %}
            {% else %}
                {{ cond3 }}
            {% endif %}
          weather_temperature_3: >
            {{ state_attr('sensor.weather_hourly', 'forecast')[3].temperature | round }}
          weather_timestamp_3: >
            {{ as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[3].datetime) | timestamp_custom('%H:%M') }}
          weather_condition_4: >
            {% set cond4 = state_attr('sensor.weather_hourly', 'forecast')[4].condition %}
            {% set next_setting = as_timestamp(state_attr('sun.sun', 'next_setting')) %}
            {% set next_rising = as_timestamp(state_attr('sun.sun', 'next_rising')) %}
            {% set cond4_time = as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[4].datetime) %}
            {% if cond4_time < next_rising and next_rising < next_setting %}
                {% if cond4 == 'sunny' %} night {% elif cond4 == 'partlycloudy' %} night-partly-cloudy {% else %} {{ cond4 }} {% endif %}
            {% else %}
                {{ cond4 }}
            {% endif %}
          weather_temperature_4: >
            {{ state_attr('sensor.weather_hourly', 'forecast')[4].temperature | round }}
          weather_timestamp_4: >
            {{ as_timestamp(state_attr('sensor.weather_hourly', 'forecast')[4].datetime) | timestamp_custom('%H:%M') }}
          weather_temp_low: >
            {%set lowof = state_attr('sensor.weather_hourly','forecast') | selectattr('datetime','search', now().timestamp() | timestamp_custom('%Y-%m-%d')) |  map(attribute='temperature') | min %}{% if lowof is defined and lowof %}{{lowof}}{%endif%}
          weather_temp_high: >
            {%set highof = state_attr('sensor.weather_hourly','forecast') | selectattr('datetime','search', now().timestamp() | timestamp_custom('%Y-%m-%d')) |  map(attribute='temperature') | max %}{% if highof is defined and highof %}{{highof}}{%endif%}
          ulkolampotila: >
            {{ states('sensor.sensor_2_2_temperature') }}
          kosteus: >
            {{ states('sensor.lumi_lumi_weather_humidity') }}
          ilmanpaine: >
            {{ states('sensor.lumi_lumi_weather_pressure') }}
          tuuli: >
            {{ states('sensor.saa_tuulen_nopeus') }}
          tuuli_suunta: >
            {{ states('sensor.saa_tuulen_suunta') }}
          mh_lampotila: >
            {{ states('sensor.makuuhuone_lampotila') }}
          mh_kosteus: >
            {{ states('sensor.makuuhuone_kosteus') }}
          mh_valoisuus: >
            {{ states('sensor.makuuhuone_valoisuus') }}
          mh_pm25: >
            {{ states('sensor.makuuhuone_pm25') }}
          oh_lampotila: >
            {{ states('sensor.sensor_4_temperature') }}
          oh_kosteus: >
            {{ states('sensor.sensor_4_humidity') }}
          keittio_lampotila: >
            {{ states('sensor.keittio_zb_motion_temperature') }}
          keittio_valoisuus: >
            {{ states('sensor.keittio_zb_motion_illuminance') }}
          kph_lampotila: >
            {{ states('sensor.sensor_3_temperature') }}
          kph_kosteus: >
            {{ states('sensor.sensor_3_humidity') }}
          nordpool_current: >
            {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour])) }}
          nordpool_min: >
            {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024',  'min')|float / 0.05) | round() * 0.05) }}
          nordpool_avg: >
            {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024',  'average')|float / 0.05) | round() * 0.05) }}
          nordpool_max: >
            {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024',  'max')|float / 0.05) | round() * 0.05) }}
          nordpool_1h: >
            {% if now().hour+1 >= 24 %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','tomorrow')[now().hour+1-24])) }}
            {% else %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour+1])) }}
            {% endif %}
          nordpool_2h: >
            {% if now().hour+2 >= 24 %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','tomorrow')[now().hour+2-24])) }}
            {% else %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour+2])) }}
            {% endif %}
          nordpool_3h: >
            {% if now().hour+3 >= 24 %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','tomorrow')[now().hour+3-24])) }}
            {% else %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour+3])) }}
            {% endif %}
          nordpool_4h: >
            {% if now().hour+4 >= 24 %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','tomorrow')[now().hour+4-24])) }}
            {% else %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour+4])) }}
            {% endif %}
          nordpool_5h: >
            {% if now().hour+5 >= 24 %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','tomorrow')[now().hour+5-24])) }}
            {% else %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour+5])) }}
            {% endif %}
          nordpool_6h: >
            {% if now().hour+6 >= 24 %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','tomorrow')[now().hour+6-24])) }}
            {% else %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour+6])) }}
            {% endif %}
          nordpool_7h: >
            {% if now().hour+7 >= 24 %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','tomorrow')[now().hour+7-24])) }}
            {% else %}
              {{ ((state_attr('sensor.nordpool_kwh_fi_eur_2_10_024','today')[now().hour+7])) }}
            {% endif %}
          nordpool_timestamp_1: >
            {{ (now()|as_timestamp + (60*60))|timestamp_custom("%H:00", True) }}
          nordpool_timestamp_2: >
            {{ (now()|as_timestamp + (60*120))|timestamp_custom("%H:00", True) }}
          nordpool_timestamp_3: >
            {{ (now()|as_timestamp + (60*180))|timestamp_custom("%H:00", True) }}
          nordpool_timestamp_4: >
            {{ (now()|as_timestamp + (60*240))|timestamp_custom("%H:00", True) }}
          nordpool_timestamp_5: >
            {{ (now()|as_timestamp + (60*320))|timestamp_custom("%H:00", True) }}
          nordpool_timestamp_6: >
            {{ (now()|as_timestamp + (60*360))|timestamp_custom("%H:00", True) }}
          nordpool_timestamp_7: >
            {{ (now()|as_timestamp + (60*420))|timestamp_custom("%H:00", True) }}
          total_hourly_energy: >
            {{ states('sensor.total_hourly_energy') }}
          total_daily_energy: >
            {{ states('sensor.total_energy_daily') }}
          nordpool_text: >
            {% set min_price = 10 |
              float %} {% set max_price = 20 | float %} {% set current_price =
              state_attr('sensor.nordpool_kwh_fi_eur_2_10_024', 'current_price') | float %}
            {% if current_price < min_price %}
              Sähkö on nyt halpaa, hinta on {{ states('sensor.nordpool_kwh_fi_eur_2_10_024', 'current_price') }} c/kWh
            {% elif current_price > min_price and current_price < max_price %} 
              Sähkö on nyt keskihintaista, hinta on {{ states('sensor.nordpool_kwh_fi_eur_2_10_024', 'current_price') }} c/kWh
            {% elif current_price > max_price %}
              Sähkö on nyt kallista, hinta on {{ states('sensor.nordpool_kwh_fi_eur_2_10_024', 'current_price') }} c/kWh
            {% endif %}

I update the display via automation twice an hour. At the second minute of every hour and when the weather forecast is updated. The weather forecast is updated at a random time approximately every hour, which is why it is updated when the latest information is available. In addition, someone has to be at home and awake for the display to be updated.

2 Likes

Hi All,
Ive been playing with this for the last few days, and i cant get this to work at all. I get a flashing noise screen about every second which alternates between black and white. I have tried using other people code straight as with no alterations… Just to see if i can get something on the screen… If i use Waveshare demo code that uploads a picture thats fine with no issues. so im going to guess there is something wrong with my pin set up

I have a Waveshare ESP32 and a 7.5 075bn-t7 V2 dispay screen, below is the output from the serial monitor

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:1184
load:0x40078000,len:13132
load:0x40080400,len:3036
entry 0x400805e4
[I][logger:158]: Log initialized
[C][ota:483]: There have been 2 suspected unsuccessful boot attempts.
[D][esp32.preferences:114]: Saving 1 preferences to flash...
[D][esp32.preferences:143]: Saving 1 preferences to flash: 0 cached, 1 written, 0 failed
[I][app:029]: Running through setup()...
[D][spi:039]: Setting up SPI bus...
[D][spi_device:379]: mode 0, data_rate 2000kHz
[C][wifi:038]: Setting up WiFi...
[C][wifi:051]: Starting WiFi...
[C][wifi:052]:   Local MAC: 94:B5:55:19:EA:40
[D][wifi:462]: Starting scan...
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[W][wifi:152]: Warning set: unspecified
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2451 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[D][wifi:477]: Found networks:
[I][wifi:521]: - 'TailWagNet' [redacted]▂▄▆█
[D][wifi:522]:     Channel: 1
[D][wifi:523]:     RSSI: -63 dB
[I][wifi:521]: - 'TailWagNet' [redacted]▂▄▆█
[D][wifi:522]:     Channel: 6
[D][wifi:523]:     RSSI: -71 dB
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[D][wifi:526]: - [redacted] [redacted]▂▄▆█
[I][wifi:303]: WiFi Connecting to 'TailWagNet'...
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2293 ms).
[W][component:233]: Components should block for at most 30 ms.
[I][wifi:594]: WiFi Connected!
[C][wifi:408]:   Local MAC: 94:B5:55:19:EA:40
[C][wifi:413]:   SSID: [redacted]
[C][wifi:416]:   IP Address: 192.168.1.163
[C][wifi:420]:   BSSID: [redacted]
[C][wifi:421]:   Hostname: 'e-paper-dashboard'
[C][wifi:423]:   Signal strength: -66 dB ▂▄▆█
[C][wifi:427]:   Channel: 1
[C][wifi:428]:   Subnet: 255.255.255.0
[C][wifi:429]:   Gateway: 192.168.1.1
[C][wifi:430]:   DNS1: 192.168.1.249
[C][wifi:431]:   DNS2: 0.0.0.0
[D][wifi:603]: Disabling AP...
[C][ota:096]: Over-The-Air Updates:
[C][ota:097]:   Address: e-paper-dashboard.local:3232
[C][ota:100]:   Using Password.
[C][ota:103]:   OTA version: 2.
[W][ota:107]: Last Boot was an unhandled reset, will proceed to safe mode in 8 restarts
[C][api:025]: Setting up Home Assistant API server...
[I][app:062]: setup() finished successfully!
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[W][wifi:165]: Warning cleared
[D][api:102]: Accepted 192.168.1.185
[W][component:232]: Component api took a long time for an operation (56 ms).
[W][component:233]: Components should block for at most 30 ms.
[I][app:102]: ESPHome version 2024.3.0 compiled on Mar 23 2024, 09:12:44
[C][wifi:580]: WiFi:
[C][wifi:408]:   Local MAC: 94:B5:55:19:EA:40
[C][wifi:413]:   SSID: [redacted]
[C][wifi:416]:   IP Address: 192.168.1.163
[C][wifi:420]:   BSSID: [redacted]
[C][wifi:421]:   Hostname: 'e-paper-dashboard'
[C][wifi:423]:   Signal strength: -65 dB ▂▄▆█
[C][wifi:427]:   Channel: 1
[C][wifi:428]:   Subnet: 255.255.255.0
[C][wifi:429]:   Gateway: 192.168.1.1
[C][wifi:430]:   DNS1: 192.168.1.249
[C][wifi:431]:   DNS2: 0.0.0.0
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2320 ms).
[W][component:233]: Components should block for at most 30 ms.
[C][logger:166]: Logger:
[C][logger:167]:   Level: DEBUG
[C][logger:169]:   Log Baud Rate: 115200
[C][logger:170]:   Hardware UART: UART0
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[D][api.connection:1159]: Home Assistant 2024.3.3 (192.168.1.185): Connected successfully
[C][spi:068]: SPI bus:
[C][spi:069]:   CLK Pin: GPIO13
[C][spi:070]:   SDI Pin: 
[C][spi:071]:   SDO Pin: GPIO14
[C][spi:076]:   Using HW SPI: SPI
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2426 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[C][waveshare_epaper:2033]: Waveshare E-Paper
[C][waveshare_epaper:2033]:   Rotations: 90 °
[C][waveshare_epaper:2033]:   Dimensions: 480px x 800px
[C][waveshare_epaper:2034]:   Model: 7.5in-bv2
[C][waveshare_epaper:2035]:   Reset Pin: GPIO26
[C][waveshare_epaper:2036]:   DC Pin: GPIO17
[C][waveshare_epaper:2037]:   Busy Pin: GPIO25
[C][waveshare_epaper:2038]:   Update Interval: 1.0s
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2419 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[C][captive_portal:088]: Captive Portal:
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2449 ms).
[W][component:233]: Components should block for at most 30 ms.
[C][mdns:115]: mDNS:
[C][mdns:116]:   Hostname: e-paper-dashboard
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[C][ota:096]: Over-The-Air Updates:
[C][ota:097]:   Address: e-paper-dashboard.local:3232
[C][ota:100]:   Using Password.
[C][ota:103]:   OTA version: 2.
[W][ota:107]: Last Boot was an unhandled reset, will proceed to safe mode in 8 restarts
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2432 ms).
[W][component:233]: Components should block for at most 30 ms.
[C][api:139]: API Server:
[C][api:140]:   Address: e-paper-dashboard.local:6053
[C][api:142]:   Using noise encryption: YES
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2455 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2455 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2455 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!
[E][waveshare_epaper:159]: Timeout while displaying image!
[W][component:232]: Component display took a long time for an operation (2606 ms).
[W][component:233]: Components should block for at most 30 ms.
[E][waveshare_epaper:159]: Timeout while displaying image!

does anyone have any pointers, as already mentioned i know the screen works ok

Hi!

I updated to HA 2024.4 and got notified that I had to remove the “beat” part of “display_option” in the “time_date” platform.
Screenshot 2024-04-07 at 07.54.20

Now the weatherman data is not working, se log below. How to resolve?

Logger: homeassistant.helpers.sensor
Source: helpers/trigger_template_entity.py:209
First occurred: April 5, 2024 at 15:54:00 (2398 occurrences)
Last logged: 07:51:00

Error rendering state template for sensor.weatherman_data: UndefinedError: None has no element 0

Logger: homeassistant.helpers.template
Source: helpers/template.py:2558
First occurred: April 5, 2024 at 15:54:00 (2398 occurrences)
Last logged: 07:51:00

Template variable error: None has no element 0 when rendering ‘{% set cond0 = state_attr(‘weather.home’, ‘forecast’)[0].condition %} {% set next_setting = as_timestamp(state_attr(‘sun.sun’, ‘next_setting’)) %} {% set next_rising = as_timestamp(state_attr(‘sun.sun’, ‘next_rising’)) %} {% set cond0_time = as_timestamp(state_attr(‘weather.home’, ‘forecast’)[0].datetime) %} {% if cond0_time < next_rising and next_rising < next_setting %} {% if cond0 == ‘sunny’ %} night {% elif cond0 == ‘partlycloudy’ %} night-partly-cloudy {% else %} {{ cond0 }} {% endif %} {% else %} {{ cond0 }} {% endif %}’

It´s a change in the weather integration “The previously deprecated forecast attribute of weather entities, has now been removed. Use the weather.get_forecasts service to get the forecast data instead.” There is no attributes anymore, this thread will give you some more information on how to use weather.get_forcasts

I haven’t found a way to call the weather.get_forcasts from ESPHome, don´t know if you can, so I have created a template sensor with all attributes

template:
  - trigger:
      - platform: time_pattern
        hours: /1
      - platform: homeassistant
        event: start
    action:
      - service: weather.get_forecasts
        data:
          type: hourly
        target:
          entity_id: weather.forecast_home
        response_variable: hourly
    sensor:
      - name: Weather Hourly
        state: "{{ states('weather.forecast_home') }}"
        attributes:
          temperature: "{{ state_attr('weather.forecast_home', 'temperature') }}"
          dew_point: "{{ state_attr('weather.forecast_home', 'dew_point') }}"
          temperature_unit: "{{ state_attr('weather.forecast_home', 'temperature_unit') }}"
          humidity: "{{ state_attr('weather.forecast_home', 'humidity') }}"
          cloud_coverage: "{{ state_attr('weather.forecast_home', 'cloud_coverage') }}"
          pressure: "{{ state_attr('weather.forecast_homee', 'pressure') }}"
          pressure_unit: "{{ state_attr('weather.forecast_home', 'pressure_unit') }}"
          wind_bearing: "{{ state_attr('weather.forecast_home', 'wind_bearing') }}"
          wind_speed: "{{ state_attr('weather.forecast_home', 'wind_speed') }}"
          wind_speed_unit: "{{ state_attr('weather.forecast_home', 'wind_speed_unit') }}"
          visibility_unit: "{{ state_attr('weather.forecast_home', 'visibility_unit') }}"
          precipitation_unit: "{{ state_attr('weather.forecast_home', 'precipitation_unit') }}"
          forecast: "{{ hourly['weather.forecast_home'].forecast }}"

If someone have a better way of doing this, please let me know :slight_smile:

@Stimo I get this error when I test in template editor: UndefinedError: ‘hourly’ is undefined. Same with Daily.
Did you ever get this problem?

In a previous post I posted my template. The top part shows how you can use the weather service directly, without first creating an extra template sensor in between like @Stimo suggested.
You can see how I changed the weather bits from the original Weatherman post in this example:

2 Likes

You can’t create that one in the template editor, you need to be able to get the respons_varible, so it needs to be inside an automation, script, or something similar. If you want to test it use it as a script and send it to notification or similar

1 Like