Weather data: converting from legacy to service call "weather.get_forecast"

Early this year I was disappointed to update my HA core only to find that I was no longer able to get data to various weather station outputs (I have two epaper displays running through ESP Home and also a custom card within the HA app, and this is repeated with modifications for another instance I run at my holiday home).

I rolled back my HA core to 2024.1.6, which was apparently the last version which allowed the legacy templates to operate. Since then I have been resolutely staying away from further updates, although I looked occasionally at the documentation and at the forums to see if I could plot a path to moving away from my legacy set-up.

I must say, I found the documentation puzzling and confusing, and contributors to the forums seemed to be doing more than just upgrading to the new service call. One of the things I found puzzling was that the service calls were something I was familiar with from automations, but I couldn’t see how and where they were invoked in the HA file system. I was also puzzled by the elements being suggested, such as ‘response variables’ and data type. Many of the examples suggested data type as ‘hourly’ or ‘daily’, and then had ‘hourly’ as a response variable. Did the response variable have to match the data type, I wondered?

Having now spent a couple of days working on this, I have at last reached a position where I have been able to upgrade to the latest HA core, and I thought it might be useful for others in the same position as myself if I shared the way I did it.

I should note that I set up all of the new code and checked that it would run correctly while still using HA core 2024.1.6. Only when I was satisfied that the service calls were working and obtaining the data I needed, did I upgrade to the latest HA core (2024.6.1 at the time of writing).

My legacy installation used templates within ‘configuration.yaml’. For example:

platform: template
    sensors:      
      today_temp:
        friendly_name: "Temp now"
        value_template: "{{ state_attr('weather.met_office_hackney_3_hourly', 'temperature') }} "

      today_temp_max:
        friendly_name: "Temp max"
        value_template: "{{ state_attr('weather.met_office_hackney_daily', 'temperature') }} "      

      today_precip:
        friendly_name: "Today precip"
        value_template: "{{ state_attr('weather.met_office_hackney_3_hourly', 'forecast')[0].precipitation_probability }} " 

The value templates no longer worked after version 2024.1.6, because the data which used to be visible in my weather states seemed to have vanished. If I understand it correctly, the developers of HA felt that it was pointless to be calling in all the data from the provider with every small change, and that it made sense to implement a service call which would bring the data in only when it was needed.

Firstly, I had to figure out how to perform the service call and where to put the code. At this point I was still working within Core version 2024.1.6. Without much help from the forums or from the documentation, I took the plunge and set up a new template with a service call within configuration.yaml. After quite a bit of trial and error, I was able to call the service weather.get_forecasts like this:

template:
 - trigger:
     - platform: time_pattern
       minutes: /1
#       hours: /1
   action:
     - service: weather.get_forecasts
       data:
         type: hourly
       target: 
         entity_id: weather.forecast_home
       response_variable: my_var_hourly
	   
#  sensor:
# [for sensors, see below]
 #   - name: 
 #     unique_id:
 #     state: 

Notes:
minutes: /1 From one of the forums I picked up the idea that for testing purposes it made sense to test every minute rather than waiting for a full hour to go by to see if it worked.
type: daily There are only three options: daily, hourly or (I think) twice daily. I’ll comment further on this below, but it is quite important.
entity_id This is the weather service which you use. In my instance weather.forecast_home is the Norwegian weather service provided by HA. I think it is sometimes called weather.home. My final version uses the weather data provided by the UK’s Met Office. I’ll say a bit more about this below.
response_variable This is the variable which will hold the data which is pulled down from the service provider. Confusingly most of the examples use ‘hourly’ or ‘daily’ and I can now see a reason for this. I chose to call it my_var_hourly to make the point that you can chose any variable name you like.

One of my questions was how you can tell what data is contained within the ‘response_variable’. In the older, deprecated version, this data was visible in Developer tools: States. I solved this by using the ‘Template’ tab within Developer Tools. Paste in what is essentially the first lines of the code above:

service: weather.get_forecasts
data:
  type: daily
target: 
  entity_id: weather.forecast.home

The output tells you exactly what is contained in the ‘response_variable’. This is useful because not all providers will give the same data. It also proved useful for me, because under core 2024.1.6 I had what looked like four ‘weather’ entities: two for ‘home’ (home daily and home three-hourly) and my Met Office provider (daily and three-hourly). This puzzled me because I could not see how these linked with the data types of hourly and daily. Using the Template tool, I was able to change this variable and discovered that the entity_id only needs to be the basic ID for the provider, because the data type then pulls in the correct data set (perhaps confusingly, the ‘hourly’ type pulls in the 3-hourly data ).

I constructed two temporary sensors to make sure that the data was coming through as expected. These were deleted of course in my final code. You will see that this is where the state: command isolates the data needed from the response_variable.

sensor:

     - name: "Weather Forecast MJ scratch_1"
       unique_id: weather_forecast_scratch_temp
       state: "{{ my_var_hourly['weather.forecast_home'].forecast[2].temperature }}"
       unit_of_measurement: °C   
	          
     - name: "Weather Forecast MJ scratch_2"
       unique_id: weather_forecast_scratch_3
       state: "{{ my_var_hourly['weather.forecast_home'].forecast[2].condition }}"

I needed to set up two distinct templates to obtain the data I wanted. One is for the daily forecasts which give me the overviews needed, and one is for the hourly forecasts which give me the more granular data needed for some elements in my displays. I was pleased to discover that a single template can be used to pull in data from more than one entity, so you will see that the code for the daily data addresses two entities. This is because some of the data I need is provided by one of the providers, and not by the other.

Once the code above is pulling in the correct data for the response_variable, then sensors need to be set up to provide the discrete items of information needed by whatever weather displays you are using (cards, epaper displays, etc.). In my case these sensors replace the template sensors in the now deprecated pre-2024.1.6 versions. I chose to retain the names/unique IDs which I used previously because it simplified my upgrade process, but if I were starting from scratch I would use names which were more logical and easier to find as a group within the Developer Tools States section.

This is the final code in my coniguration.yaml file, which I have shortened because there was no point in showing you all the sensors which I needed.

template:
# template for daily data 
 - trigger:
     - platform: time_pattern
       hours: /1
   action:
     - service: weather.get_forecasts
       data:
         type: daily
       target: 
         entity_id: 
           - weather.met_office_hackney_daily
           - weather.forecast_home
       response_variable: my_daily
   sensor:
       
     - name: "Today's maximum"
       unique_id: today_temp_max
       state: "{{ my_daily['weather.met_office_hackney_daily'].forecast[0].temperature }}"
       
     - name: "Temp tomorrow"
       unique_id: temp_tomorrow
       state: "{{ my_daily['weather.met_office_hackney_daily'].forecast[1].temperature }}"   
       
     - name: "Precipitation tomorrow"
       unique_id: precip_tomorrow
       state: "{{ my_daily['weather.met_office_hackney_daily'].forecast[1].precipitation_probability }}"
       
# template for hourly data      
 - trigger:
     - platform: time_pattern
       hours: /1
   action:
     - service: weather.get_forecasts
       data:
         type: hourly
       target: 
         entity_id: 
           - weather.met_office_hackney_daily
           response_variable: my_hourly
   sensor:

     - name: "Temp now"
       unique_id: today_temp
       state: "{{ my_hourly['weather.met_office_hackney_daily'].forecast[0].temperature }}"
       unit_of_measurement: °C
       
     - name: "Today precip"
       unique_id: today_precip
       state: "{{ my_hourly['weather.met_office_hackney_daily'].forecast[0].precipitation_probability }}"       
       
     - name: "Temp plus 3 hours"
       unique_id: temp_now_plus_3
       state: "{{ my_hourly['weather.met_office_hackney_daily'].forecast[1].temperature }}"  

I hope this account may help others who are still contemplating upgrading from the legacy system, and who, like me, may be deterred by what initially feels like an unfamiliar environment.

4 Likes

You can also pull the entire forecast into an attribute:

   sensor:
     - name: "Forecast"
       state: "{{ now() }}"
       attributes:
         forecast: "{{ my_hourly['weather.YOUR_ENTITY'].forecast }}"

Then you can query the sensor attributes for the individual values you need. For example, the minimum temperature:

      - name: "Min temp in forecast"
        state: >
          {{ state_attr('sensor.forecast', 'forecast')
             |map(attribute='temperature')
             |min }}
        unit_of_measurement: '°C'

and you can set these up with UI-generated template sensors rather than using YAML for each one.

I want to add that this code returns the minimum temperature across all of the days that are forecasted. If you want to pull a specific item in the list, use this:

{{ (state_attr('sensor.forecast_home', 'forecast') | map(attribute='temperature') | list)[###] }}

Update: Fix the code format. Thanks - was really frustrated that I hadn’t noticed this as an issue the last couple of months…

Please format code properly — if anyone copies your template, it won’t work due to the forum software altering the quotes.

Use the </> button or surround with three backticks:

```
CODE GOES HERE
```