Selected forecast items to ESP32 & ePaper display?

To get info from home assistant to eshome use one of the documented home assistant sensors in esphome.

So, are you saying that I should try to create (in Home Assistant) a template where I gather needed forecast related values into a string, assign it somehow to a state (?) and then get that state to ESPHome using text sensor, split it and present values on e-Paper?

For each item you want to present in esphome create a template sensor in HA. Then use a home assistant sensor (or HA text sensor or HA binary sensor) in esphome. They do not need to be combined then split.

1 Like

Take a look at these projects:

1 Like

Thanks, @nickrout. It might be a good, straightforward idea. Where I’m really stumbling right now is getting out individual forecast values. I can’t figure out how to refer to Nth temperature, condition, etc…
Tried something like:
{{ state_attr('weather.hakametsa.forecast[1]’, 'temperature')}}
and getting ’None’ out. Most likely an easy nut to crack?

Thank you @nanobra1n for thel links! Those are indeed pretty inspirational implementations! Have to bookmark them for reference.
However, the first seems to use something called MQTT (instead of HA API) to get values from HA to ESP - and that might be a lesson in the future :smiley: And the latter does not seem to present any forecast data, where I currently need some help…

Got a hint that this piece of code will give me the first temperature from FMI weather forecast.

{{ state_attr('weather.hakametsa', 'forecast')[0].temperature}}

Very good. Now I’m able to move on picking values I want to show now…

I have something similar (or exactly) what you want.

First I created a sensor in HA to get the forecast values in to one long string.
Values are separated with ; and days are separated with #.

sensor:
  - platform: template
    sensors:
      weather_fivedays:
        friendly_name: "Five day weather"
        value_template: >-
             # this creates an icon for each weather that can be displayed in ESPhome with the mdi font
             {% set weather = {
                "sunny": "",
                "clear-day": "", 
                "clear-night": "", 
                "cloudy": "", 
                "rainy": "", 
                "sleet": "", 
                "snow": "s", 
                "wind": "", 
                "fog": "", 
                "partlycloudy": "", 
              } %}
             # this is a translation table to get day names in local language
             {% set days = {'Mon':'Mån','Tue':'Tis','Wed':'Ons','Thu':'Tors','Fri':'Fre','Sat':'Lör','Sun':'Sön'} %}

                {% for state in states.weather.smhi_home.attributes.forecast[1:6] -%}
               
                {{ days[as_timestamp(state.datetime)| timestamp_custom("%a")] }};{{state.templow}}-{{ state.temperature }}°C;{{ state.precipitation | replace('.', ',') }}mm;{{ weather[state.condition] }}#
                
                {%- endfor %}

This sensor creates this:

Tors;7-13°C;0,0mm;#Fre;7-14°C;1,5mm;#Lör;8-14°C;2,4mm;#Sön;10-12°C;1,8mm;#Mån;9-11°C;7,8mm;#	

In ESP-Home you add a text sensor:

text_sensor:
  - platform: homeassistant
    id: w_fivedays
    entity_id: sensor.weather_fivedays

The font:

font:
  - file: 'materialdesignicons-webfont.ttf'
    id: weather_font
    size: 49
    glyphs: [
      # Weather
      "", # mdi-weather-sunny
      "", # mdi-weather-night
      "", # mdi-weather-cloudy
      "", # mdi-weather-pouring
      "", # mdi-weather-snowy-rainy
      "s", # mdi-weather-snowy-heavy
      "", # mdi-weather-windy-variant
      "", # mdi-weather-fog
      "n", # mdi-weather-night-partly-cloudy
      "", # mdi-weather-partly-cloudy
      ]

And the display lambda:

display:
  - platform: waveshare_epaper
    cs_pin: 5
    dc_pin: 19
    busy_pin: 4
    reset_pin: 12
    model: 2.90in
    full_update_every: 15
    update_interval: 1s
    lambda: |-
          fivedays = id(w_fivedays).state;
          
          //ESP_LOGD("%s", fivedays.c_str());
          five.clear();
          
          int count = 0;
          int wx = 0; // start positio x
          int wy = 0; // start position y
          token = strtok (&fivedays[0],"#");
          // this while splits the string (I believe, I "found" the code)
          while (token != NULL)
          {
            five.push_back(token);
            token = strtok (NULL, "#");
          }
          // here we loop the days
          for ( std::string fiv : five ) {
            it.rectangle(0, wy, 128, 59);  // adds a border around the "day"
            it.rectangle(1, wy+1, 126, 57);
            
            str = "";
            str = fiv;
            //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, ";");
            }

            // this is the loop for each value in the "day"
            for ( std::string s : v ) {

              if(count == 0){
                // Day (Mon/Tue...)
                it.printf(wx +7, wy, id(my_font_small), "%s", s.c_str());
              }else if(count == 1){
                // Temperature 
                it.printf(wx +7, wy+18, id(my_font_small), "%s", s.c_str());
              }else if(count == 2){
                // Precipitation 
                it.printf(wx +7, wy+35, id(my_font_small), "%s", s.c_str());
              }else if(count == 3){
                // weather icon
                it.printf(123, wy+5, id(weather_font), TextAlign::TOP_RIGHT, "%s", s.c_str());
              }
               
              //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
              count += 1;
            }
            count = 0;
            wy += 59; // move down 59 pixels and output next day
          }

The code is not good in anyways and has lots of room for improvement. But it works.

1 Like

Wow, thank you @Hellis81!

Need to read and get to know your solution a bit better to understand what it does, localise it to FMI Weather service & my 4.2 inch display - but this certainly helps a lot!

Yes it will need some adjustments. But I believe if you just use your weather sensor in HA and change the display then it should output a small version of the weather on your screen.
Then you just need to “up-scale” it.

it seems to not work, i dont’know if something is changed in lambda backend of esphome but now it stuck at the variable declaration:
lambda: |-
fivedays = id(w_fivedays).state;
This produce error.

Hello,
I try to create the sensor in HA to get the forecast values in to one long string.
This is my code:

sensor:
  - platform: template
    sensors:
      weather_forecast_esphome:
        friendly_name: "Weather Forecast 3h x5"
        value_template: >-
             # this creates an icon for each weather that can be displayed in ESPhome with the mdi font
             {% set weather = {
                "sunny": "",
                "clear-day": "", 
                "clear-night": "", 
                "cloudy": "", 
                "rainy": "", 
                "sleet": "", 
                "snow": "s", 
                "wind": "", 
                "fog": "", 
                "partlycloudy": "", 
              } %}
                {% for state in states.weather.openweathermap[1:6] -%}
                {{ as_timestamp(state.datetime) | timestamp_custom("%X") }};{{ state.temperature }}°C;{{ state.precipitation | replace('.', ',') }}mm;{{ weather[state.condition] }}#
                {%- endfor %}

I get this error:


Logger: homeassistant.helpers.event
Source: helpers/template.py:411
First occurred: 16:22:27 (1 occurrences)
Last logged: 16:22:27
Error while processing template: Template("# this creates an icon for each weather that can be displayed in ESPhome with the mdi font {% set weather = { "sunny": "", "clear-day": "", "clear-night": "", "cloudy": "", "rainy": "", "sleet": "", "snow": "s", "wind": "", "fog": "", "partlycloudy": "", } %} {% for state in states.weather.openweathermap[1:6] -%} {{ as_timestamp(state.datetime) | timestamp_custom("%X") }};{{ state.temperature }}°C;{{ state.precipitation | replace('.', ',') }}mm;{{ weather[state.condition] }}# {%- endfor %}")

Traceback (most recent call last):
  File "/usr/local/share/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/template.py", line 409, in async_render
    render_result = _render_with_context(self.template, compiled, **kwargs)
  File "/usr/local/share/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/template.py", line 1842, in _render_with_context
    return template.render(**kwargs)
  File "/usr/local/share/homeassistant/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
    self.environment.handle_exception()
  File "/usr/local/share/homeassistant/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 13, in top-level template code
  File "/usr/local/share/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/template.py", line 745, in __getitem__
    if item in _COLLECTABLE_STATE_ATTRIBUTES:
TypeError: unhashable type: 'slice'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/share/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/template.py", line 525, in async_render_to_info
    render_info._result = self.async_render(variables, strict=strict, **kwargs)
  File "/usr/local/share/homeassistant/lib/python3.9/site-packages/homeassistant/helpers/template.py", line 411, in async_render
    raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: TypeError: unhashable type: 'slice'

There is something missing here.
I have never used open weather but I assume there is an attribute list with forecast values that you should iterate.
As you can see in my code I iterate the attribute ‘forecast’.

1 Like

Thanks, it works with states.weather.openweathermap.attributes.forecast[1:6]

Hi @Hellis81

Great piece of code!
I was trying to implement it but have a couple of questions:

  • where is “five.clear()” defined? What “five” is in this context?
  • where is “v.push_back(token)” defined? What “v” is in this context?

It would be great if you could share the whole YAML code.
Thanks in advance for your help!

I know this code is old, there was some updates to ESP-Home that broke the code.
I’m not sure the lastest code I have works since mine is compiled in an older version of ESP-Home.
Since we moved we have not had somewhere to place this screen so it has been unused the last two years also.
I can upload the yaml I have when I sit at the computer but I don’t believe it will compile on the new versions.

Anyways since the string is split twice, first per day, then per item per day.
So v, five, and fiv becomes arrays with the items, then lastly s is the last string with the actual item.

This is a seriously bad code, I always struggle with data types in C and as soon as I get it to work I just leave it as it is. So the variable naming beyond bad, sorry for that.

Thanks for replying.
From your code I will try to adapt it.
If succeed, will share it.
Thanks again!

For anyone lurking around for inspiration and code sniplets like me, I found two issues in the code:

  1. the yaml code for the HA sensor needs to be corrected at:
    value_template: >-
    to
    value_template: >
    and in the ESPhome section all variables must be definde using “std::”
std::string fivedays = id(w_fivedays).state;
std::vector five;

Perhaps I will share my code later, but at the moment I am struggeling with the icon font and an acceptable layout on my liligo T5…

@seechaot can you please post the complete working code?

I frankensteined the code of Hellis81 a little bit, so five day is now more like three day forecast , I changed the font and the output format as well and I’m sure you will find more differences, if you look for them…
Check with your weather integration, if you find a list of conditions and check against my provided list. It seems some integrations are not using the names of the icons…
Have fun!

So for the template:

weather_fivedays:
        friendly_name: "Five day weather"
        value_template: >
                {% set weather = {

                "cloudy": "󰖐",
                "cloudy-alert": "󰼯",
                "cloudy-arrow-right": "󰹮",
                "cloudy-clock": "󱣶",
                "fog": "󰖑",
                "hail": "󰖒",
                "hazy": "󰼰",
                "hurricane": "󰢘",
                "lightning": "󰖓",
                "lightning-rainy": "󰙾",
                "clear-night": "󰖔",
                "night-partly-cloudy": "󰼱",
                "partlycloudy": "󰖕",
                "partly-lightning": "󰼲",
                "partly-rainy": "󰼳",
                "partly-snowy": "󰼴",
                "partly-snowy-rainy": "󰼵",
                "pouring": "󰖖",
                "rainy": "󰖗",
                "snowy": "󰖘",
                "snowy-heavy": "󰼶",
                "snowy-rainy": "󰙿",
                'sunny': '󰖙',
                "sunny-alert": "󰼷",
                "sunny-off": "󱓤",
                "sunset": "󰖚",
                "sunset-down": "󰖛",
                "sunset-up": "󰖜",
                "tornado": "󰼸",
                "windy": "󰖝",
                "windy-variant": "󰖞",
                } %}

                {% set days = {'Mon':'Mån','Tue':'Tis','Wed':'Ons','Thu':'Tors','Fri':'Fre','Sat':'Lör','Sun':'Sön'} %}

                {% for state in states.weather.##INTEGRATION.OF.YOUR.CHOICE##.attributes.forecast[1:4] -%}
                {{ days[as_timestamp(state.datetime)| timestamp_custom("%a")] }};{{state.templow| round(0)}}|{{ state.temperature| round(0) }}°;{{ state.precipitation | replace('.', ',') }}mm;{{weather[state.condition]}}#
                
                {%- endfor %}

The esp_sensor:

text_sensor:
  - platform: homeassistant
    id: w_fivedays
    entity_id: sensor.weather_fivedays

The font:

  - file: 'fonts/MaterialDesignIconsDesktop.ttf'
    id: weather_s
    size: 60
    glyphs:
        - "\U000F0591"   #fog
        - "\U000F0592"   #hail
        - "\U000F0F30"   #hazy
        - "\U000F0898"   #hurricane
        - "\U000F0593"   #lightning
        - "\U000F067E"   #lightning-rainy
        - "\U000F0594"   #night
        - "\U000F0F31"   #night-partly-cloudy
        - "\U000F0595"   #partly-cloudy
        - "\U000F0F32"   #partly-lightning
        - "\U000F0F33"   #partly-rainy
        - "\U000F0F34"   #partly-snowy
        - "\U000F0F35"   #partly-snowy-rainy
        - "\U000F0596"   #pouring
        - "\U000F0597"   #rainy
        - "\U000F0598"   #snowy
        - "\U000F0F36"   #snowy-heavy
        - "\U000F067F"   #snowy-rainy
        - "\U000F0599"   #sunny
        - "\U000F0F37"   #sunny-alert
        - "\U000F14E4"   #sunny-off
        - "\U000F059A"   #sunset
        - "\U000F059B"   #sunset-down
        - "\U000F059C"   #sunset-up
        - "\U000F0F38"   #tornado
        - "\U000F059D"   #windy
        - "\U000F059E"   #windy-variant
        - "󰖐"            #cloudy
  - file: 'fonts/GoogleSans-Bold.ttf'
    id: roboto_40
    size: 40
    glyphs:
      ['&', '@', '!', '?', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z', 'å', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', '/', '€', '’','|']
  - file: 'fonts/GoogleSans-Bold.ttf'
    id: num_xs
    size: 28
    glyphs:
      ['&', '@', '!', '?', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', '|', 'm']

The display lambda:

          #define xRes 960
          #define yRes 540
          std::string fivedays = id(w_fivedays).state;
          std::vector<std::string> five;
          std::vector<std::string> v;
          #//ESP_LOGD("%s", fivedays.c_str());

          five.clear();
          int count = 0;
          int wx = 160; // start position x
          int wy = 330; // start position y

              char *token = strtok(const_cast<char*>(fivedays.c_str()), "#");
              while (token != nullptr)
             {
               five.push_back(token);
               token = strtok(nullptr, "#");
              }
           
          // here we loop the days
          for ( std::string fiv : five ) {
                    
            std::string str = "";
            str = fiv;
            //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, ";");
            }

            // this is the loop for each value in the "day"
            for ( std::string s : v ) {

              if(count == 0){
                // Day (Mon/Tue...)
                it.printf(wx, wy+60, id(roboto_40), "%s", s.c_str());
              }else if(count == 1){
                // Temperature 
                it.printf(wx-5, wy+110, id(num_xs), "%s", s.c_str());
              }else if(count == 2){
                // Precipitation 
                it.printf(wx-15, wy+148, id(num_xs), "%s", s.c_str());
              }else if(count == 3){
                // weather icon
                it.printf(wx+60, wy, id(weather_s), TextAlign::TOP_RIGHT, "%s", s.c_str());
              }
               
              //ESP_LOGD("test: ", "String to Vector: %s", s.c_str());
              count += 1;
            }
            count = 0;
            wx += 100; // move right 80 pixels and output next day
          }