Calculate number of hours until the temperature in forecast is above/below limit

I’m looking for suggestions on how to check the onecall_hourly forecast of the OpenWeatherMap integration and calculate the number of hours until this forecast is above/below a certain limit. In my specific case, I’d want to generate a notification if it will be freezing over night, including the time period when it will do so. Something like “Tonight it’s going to be cold. Between 3am and 6am the temperature will be below 0° C, with a minimum of -5° C at 5am.”. (I’m using this warning to decide if I move some plants from my terrace to a room indoor over night when it’s going to be very cold outside to prevent them from freezing)

The state attributes of weather.openweathermap in the states inspector in developer tools shows that there is a yaml array named forecast and each item of the array contains the key/value pair for both temperature and datetime.

Please excuse if “array” and “key/value” is incorrect lingo in python/yaml. I’m very new to YAML and can’t yet find something appropriate in the Home AssistantUI. I was able to set the logic up in a homekit automation, but I’m currently moving everything away from homekit.

In Homekit automation I’d first create an array of only the temperature values, then count through the array until the first time the value is below 0, save that counter in a variable, continue counting until it rises above zero again, save that in another variable, and also compare each value to the minimum of the array and store the index in a third variable.

But how would I do that in a value template? If anyone could point me in the right direction, I’d very much appreciate it. Thanks!

I do this for a number of things using the forecast. Here’s an example:

sensor:
  - name: "Next Precipitation"
    unique_id: "9d693c5a-5ea7-4ca2-88d1-cadc3563172f"
    icon: mdi:weather-rainy
    state: >-
      {% set ns = namespace(raintime = "") %}
      {% if int(state_attr('weather.smart_weather_daily', 'forecast')[0].precipitation_probability | round(0), 0) > 10 %}
        {% for s in state_attr('weather.smart_weather', 'forecast') | list %}
          {% if int(s['precipitation_probability'], 0) > 19 %}
            {% set ns.raintime = int(s['precipitation_probability'], 0) | string + '% Chance on ' + as_timestamp(s.datetime) | timestamp_custom('%A %b %-d at %-I:00 %p ') %}
            {% break %}
          {% endif %}
        {% endfor %}
      {% endif %}

      {% if ns.raintime != "" %}
        {{ ns.raintime }}
      {% else %}
        {{ "Nothing 20% or more forecast for the next 4 days" }}
      {% endif %}

That is so I have a sensor of the next time rain is forecast for 20% or more chance using the hourly weather forecast. You can modify this easily to see lowtemp or just what the forecast temperature is at that time. If you want to limit it, you can just limit the loop to X hours into the future or use a date comparison like I do to find out the humidity and temp at a particular time like this:

      {% set ns = namespace(high_temp = 0, humidity = 0) %}
      {% set current_date = (now() | as_timestamp) | timestamp_custom("%d.%m.%Y") %}
      {% for forecast in state_attr('weather.smart_weather_detailed_forecast','forecast') %}
        {% if (forecast.datetime | as_timestamp) | timestamp_custom("%d.%m.%Y") == current_date %}
          {% if forecast.temperature > ns.high_temp %}
            {% set ns.high_temp = forecast.temperature %}
            {% set ns.humidity = forecast.humidity %}
          {% endif %}
        {% endif %}
      {% endfor %}

The “smart_weather_detailed_forecast” is a template weather device I created to use whatever is available in my 4 different weather integrations (in case my primary is down).

1 Like
sequence:
  - service: weather.get_forecast
    data:
      type: hourly
    target:
      entity_id: weather.openweathermap
    response_variable: w
  - variables:      
      forecast: "{{ w.forecast[0:24] }}"
      threshold: 0
      first_low: |
        {{ forecast|selectattr('temperature', 'le', threshold)
        |sort(attribute='datetime')
        |map(attribute='datetime')|first|default('No Values',1) }}
      message: |
        {% if first_low != 'No Values' %}
          {% set last_low = forecast|selectattr('temperature', 'le', threshold)
          | selectattr('datetime', 'gt', first_low)
          | sort(attribute='datetime', reverse=1)
          | map(attribute='datetime') | first %}
          {% set lowest = forecast|sort(attribute='temperature')|first %}
          
          Tonight it’s going to be cold. Between 
          {{ (first_low|as_datetime|as_local).strftime('%-I %p') }}
          and {{ (last_low|as_datetime|as_local).strftime('%-I %p') }} the temperature will be below
          {{ threshold }}° C, with a minimum of {{ lowest.temperature }}° C at      
          {{ (lowest.datetime|as_datetime|as_local).strftime('%-I %p') }}.
        
        {% else %}
          It will not be below {{threshold}} over the next 24 hours
        {% endif %}
  - service: notify.notify
    data:
      message: "{{ message }}" 
2 Likes

Thank you very much, this is helpful in learning where Python, YAML and HA meet.

Oh wow, this is an impressive script. Thank you very, very much. I will have to find some basic Python tutorials to understand everything, I’m currently only familiar with Ruby and it is a transition to a different language. And the difficult part is partly the “short” syntax, such as the | pipe seems to be passing the result of a method to the next, which would translate to dots in Ruby. And these things are much harder to google than method names. :slight_smile:

One additional question though: From what I’ve read about Python, the block scalar | turns every newline within the string into a literal newline. As I’d not want these newlines in the message variable, I changed it to:

message: >
  {% if first_low != 'No Values' %}
  {% set last_low = forecast|selectattr('temperature', 'le', threshold)

But as soon as I save the script and reopen it, the > is replaced by a ‘|’ again. Is there something preventing string variables in scripts using the folded style? Or is it an issue with the UI, that even when editing it in YAML mode, it will somehow parse everything and replace parts of it?

It’s mostly Jinja2.

For example, this is all Jinja2:

        {{ forecast|selectattr('temperature', 'le', threshold)
        |sort(attribute='datetime')
        |map(attribute='datetime')|first|default('No Values',1) }}

You will need a Jinja2 tutorial.

Start here: Templating

While Python might help with some aspects of Jinja templating in HA, check out the following before you do a deep dive:

Various Jinja Templating Documents

Jinja Docs: This document describes the syntax and semantics of the template engine.

Home Assistant Templating : This is the the most comprehensive and up-to-date source for HA-specific functions.

Jinja for Ninjas: This is a well organized tutorial and group of examples. It’s been a while since this was updated, so there are better/easier ways to do some of the things shown, but it’s still a good resource.

The UI editor does weird and annoying things to multi-line quotes. No matter whether you use > or | the actual yaml loses the block scalar header. If you use the folding with chomping header >-, it will persist in the UI editor’s “Edit in YAML”, but I haven’t bothered experimenting if the actual text output of the template… I don’t really have any templates where it matters.

1 Like

Thanks for the resources to Jinja, I’m going to spend some time making myself familiar with it.

>- was replaced with |- when saving it. I therefor ended up opening scripts.yaml in VS editor and removing all \n manually. Thanks for the help.

Don’t waste your time removing them manually unless your going to move the scripts out of the UI-manged scripts.yaml file, they will just be changed back at some, seemingly random, point in the future… that’s the “annoying” part of my earlier statement.