Temperature from one hour ago

Dear community,

I have difficulties climbing the steep learning curve in homeassistant.
I’m trying to create a sensor that gives me the temperature from an hour ago.
I’ve tried the following but I cannot seem to get it right, as I always get errors and don’t seem to get anywhere.
I know it must seem easy for you homeassistant wizards present, but it’s difficult for me to understand the logic behind it.

Thank you !

- platform: statistics
  name: "tempcan002_temp_moins_1_heure"
  entity_id: sensor.tempcan002_temperature
  state_characteristic: value_min
  sampling_size: 10
#  start: "{{ now().timestamp() - 3600 }}"
#  end: "{{ now().timestamp() - 3540 }}"

I have very little idea about this sort of template, but my first look says it’s probably not going to work with those lines commented out.

I’d do it this way:

template:
  - trigger:
      - platform: time_pattern
        minutes: "/15"
    sensor:
      - name: "tempcan002 history"
        state: "{{ states('sensor.tempcan002_temperature') }}"
        attributes:
          qtr0: "{{ states('sensor.tempcan002_temperature')|float(0) }}"
          qtr1: "{{ this.attributes['qtr0'] }}"
          qtr2: "{{ this.attributes['qtr1'] }}"
          qtr3: "{{ this.attributes['qtr2'] }}"
          qtr4: "{{ this.attributes['qtr3'] }}"

  - sensor:
      - name: "tempcan002 moins 1 heure"
        state: "{{ state_attr('sensor.tempcan002_history', 'qtr4') }}"
        unit_of_measurement: "°C"
        device_class: temperature
        availability: "{{ state_attr('sensor.tempcan002_history', 'qtr4') is number }}"

Set the time_pattern interval to give you the temporal resolution you care about (I’ve used 15 minutes), and adjust the attributes accordingly. You could trigger every minute and have 60 attributes (like the ten in the link) but that’s probably overkill.

This will only start working at a quarter-hour (x:00, x:15, x:30, x:45): the history sensor will be unknown until that time.

It will then take an hour to fully populate. The moins 1 heure sensor will be unavailable until then.

EDIT: My test sensor (I always like to validate my responses) now it’s fully populated:

15-minute resolution vs ~2 minute on the master:

image

1 Like

Wow ! This is complex !
I’m going to try this.
Thank you for your time !

This ia a really great idea. As I’m looking into comparing the current light level with the same time of day of the past (e.g.) 14 days, I think I could adapt your proposal, defining one sensor attribute per hour and keeping a list of measurements of the past 14 days in that attribute.

For any given moment I could then query these 14 values and compare the current measurement with these values, by e.g. returning the z-score (current-average)/standard_deviation the percentile, the ratio current/max,… (whatever is most useful when implementing it)

Before I attempt to do so (Python/Jinja are still rather new to me), would you agree with that draft or go a different route? Thanks!

It would certainly work. Rather than having 336 separate attributes, though, you could store them in a list object under a single attribute.

Thanks. I‘m not familiar with the nomenclature and syntax of Python data structures, but I was considering an attribute storing a key/value object, with the 24 hours of the day as keys and each storing an array/list of measured values. I would assume that is most efficient and convenient. I‘m unsure how to deal with situations when the source sensor is unavailable and returns a nil/null/… value, probably I would store it as nil and compact the list before calculating statistics.

Something like this should do it. You’ll need to replace the light sensor ID (twice) with your entity ID.

template:
  - trigger:
      - platform: time_pattern
        minutes: "0"
    sensor:
      - name: "light history"
        state: "{{ states('sensor.outside_illuminance')|int(0) }}"
        unit_of_measurement: lx
        device_class: illuminance
        attributes:
          history: >
            {% if this is defined %}
              {% set h = this.attributes.get('history', {}) %}
            {% else %}
              {% set h = {} %}
            {% endif %}
            {% set r = states('sensor.outside_illuminance')|int('nope') %}
            {% if r is number %}
              {% set ns = namespace(d={}) %}
              {% for i in (h.keys()|sort)[-335:] %}
                {% set ns.d = dict(ns.d, **{ i: h[i] }) %}
              {% endfor %}
              {{ dict(ns.d, **{ now().isoformat(): r }) }}
            {% else %}
              {{ h }}
            {% endif %}

Here’s the output on my system triggered every minute instead (minutes: "*") and limited to five items ([-4:]) — as a test, I forced the source sensor to unknown for the 08:15 reading:

Things of note in the template:

  • when you start it up, there won’t be a history attribute, so get() will return the defined default of an empty dictionary, {}. We also allow for this not being defined at all.
  • the default of the int filter is a string, so that we can omit any unknown values by skipping the next section via the is number test.
  • there’s no easy way to remove an item from an existing dictionary (experts, feel free to prove me wrong), so I build a list of keys, trim it to the last 335, and then rebuild it before adding the new item to the end.
  • the namespace is needed to allow variables to survive a change of scope, otherwise what we do in the for loop wouldn’t exist outside that loop.
  • the dict() function: docs.
  • the “list” will limit to 336 items rather than 14 days, so if you have missing readings once it’s “full”, it’ll extend back an hour extra for every missing item. If that’s a problem, you can pull out a list of keys based on date/time rather than length without too much extra effort.
1 Like

Oh wow, thanks for all your work, that was completely unexpected! From my understanding you keep a single attribute that contains a list of 335 tuples (=24 per day for 14 days), each with time and value? I was rather looking into saving one list for each hour of the day, which would allow me to compare the current value with previous days at the same time. Here’s the pseudo-python-code (with some AI help) to show what I’m thinking of:

DAYS_TO_KEEP = 21

history = {}

# Once per hour, do the following:

def update_history(current_measurement):
    if current_measurement is None:
        return
    hour = datetime.now().hour
    if hour not in history:
        history[hour] = []
    history[hour] = history[hour][-DAYS_TO_KEEP:]
    history[hour].append(current_measurement)

# To compare the current measurement with the last weeks, choose one of the following:

def min_max_ratio(current_measurement):
    hour = datetime.now().hour
    old_measurements = history.get(hour, [])
    return (current_measurement - min(old_measurements) / (max(old_measurements) - min(old_measurements)

def percentile(current_measurement):
    hour = datetime.now().hour
    old_measurements = history.get(hour, [])
    return sum(1 for value in old_measurements if value <= current_measurement) / len(old_measurements)

def z_score(current_measurement):
    hour = datetime.now().hour
    old_measurements = history.get(hour, [])
    mean = sum(old_measurements) / len(old_measurements)
    variance = sum((x - mean) ** 2 for x in old_measurements) / len(old_measurements)
    standard_deviation = math.sqrt(variance)
    return (current_measurement - mean) / standard_deviation

Yes.

Can you provide an example data structure that shows what you want? Something like this?

{
  0: [0, 0, 0, ..., 0, 0],
  1: [0, 0, 0, ..., 0, 0],
  ...
  12: [178, 214, 199, ..., 542, 335],
  ...
  23: [0, 0, 0, ..., 0, 0]
}

If so, is history[0][0] the most recent midnight reading, or the oldest?

Please don’t do that. Whilst AI has its place, its use in HA is strongly discouraged as it has learned from old data and forum posts. Much of what it writes relating to HA is simply wrong, and it takes us longer to fix it up than to solve the original problem. [example][example]

doing something similar with my last motion trigger template:

      - unique_id: last_time_motion_triggered
        state: >
          {{as_local(states[trigger.entity_id].last_changed).strftime('%X')}}
        attributes:
          history: >
            {%- set previous = this.attributes.history|default([]) %}
            {%- set new = [this.attributes.last_area|default('nergens') + ': '
                           + this.state] %}
            {{previous[-4:] + new}}
          last_area: >
            {{area_name(trigger.entity_id)}}

must check why I do this, and why it is shorter than yours, what am I missing there:

Because yours is only using lists, which are easy to add and trim. Dictionaries, less so.

Your list items look like dicts, but they’re not really.

1 Like

you’re right…

it suffices for my objectives in this card so thats why I settled for that here.
I do have those dictionaries and pick items from them elsewhere…

Always when I spot a namespace I try to see whether one actually needs that :wink:

1 Like

Totally agree. I always try to do things with filtering and building without loops, but sadly most dictionary operations don’t allow that.

Exactly.

Yes, the first value in the list is the oldest. In my pseudo-code I first remove the oldest value (the first in the list) and then append the current measurement at the end of the list. At least that’s my intention.

Thanks for the note. I wouldn’t ask it to provide Jinja/HA code, all I did was write some Ruby code (a language I’ve been using from time to time over many years) and ask it to translate it to Python. I assumed this was so generic that it shouldn’t cause any issues.

template:
  - trigger:
      - platform: time_pattern
        minutes: "0"
    sensor:
      - name: "light history"
        state: "{{ states('sensor.outside_illuminance')|int(0) }}"
        unit_of_measurement: lx
        device_class: illuminance
        attributes:
          history: >
            {% if this is defined %}
              {% set d = this.attributes.get('history', {}) %}
            {% else %}
              {% set d = {} %}
            {% endif %}

            {% set r = states('sensor.outside_illuminance')|int('n/a') %}
            {% set h = now().hour|string %}
            {% set d = dict(d, **{h: (d.get(h,[]) + [r])[-14:]}) %}
            {{ d }}

My test with seconds as the keys, a 5-second update and a list length of 5 (and a different attribute name to get rid of the prior data):

Then you can do, for example:

(one minute later than first screenshot so the 82 has been punted out)

You’ll have to work out how to deal with the n/a values if applicable.

The only complicated line in that is:

{% set d = dict(d, **{h: (d.get(h,[]) + [r])[-14:]}) %}

which says:

Make d a dictionary that is d plus a new item that has the hour as a key, and for the value it has:

  • either the prior value for that key if it exists ( d.get(h) );
  • or an empty list if it doesn’t ( d.get(h,[]) );
  • with the latest light value r appended
  • and trimmed to the last 14 items ( [-14:] means “the 14th-last item onwards”).

Adding a new key / value pair to a dictionary overwrites any prior value for that key if it already existed:

2 Likes

@Troon, this is awesome. Thanks a lot, for all your work and explanations. They are of great help.

1 Like