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.
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:
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!
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:
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 “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.
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
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]
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):