Real state last_changed

In the moment restarting HA is a common thing for me. :slight_smile:
On every restart the last_changed timestamp gets updated.
Inspired by this topic Last changed/Last updated i made a script to get the real last_changed timestamp.

I need this for:

  • monitoring my old heating control with a sensor and if a temperature not changes in a few minutes, it crashed and i need a notification.
  • monitoring how long the playstation is on and send my son a message when his time is over.
  • …

When i’m fiddling with home assistant it could get cold at home and my son can play forever. :slight_smile:

This works only with recorder on MySQL, but maybe someone can change it for sqlite or something.

Here’s the command_line sensor:

- platform: command_line
  name: "RLC Playstation"
  command: "python3 scripts/real_last_change.py switch.playstation4"
  scan_interval: 60

and the script for it:

import sys
import pymysql.cursors
from datetime import timezone

entity_id = sys.argv[1]

mysql_host = 'mysql_host'
mysql_user = 'mysql_user'
mysql_pass = 'mysql_pass'
mysql_db   = 'mysql_db'

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)

db = pymysql.connect (host=mysql_host, user=mysql_user, passwd=mysql_pass, db=mysql_db)
cursor = db.cursor(pymysql.cursors.DictCursor)

sql = """SELECT state,last_changed FROM states WHERE entity_id = '%s'
         ORDER BY created DESC LIMIT 100""" % (entity_id)

cursor.execute(sql)
result = cursor.fetchall()
db.close()

for idx, row in enumerate(result):
    state_act = row['state']
    date_act = row['last_changed']
    if idx == 0:
        state_last = state_act
        date_last = date_act
    if state_last != state_act:
        real_last_changed = date_last
        break
    else:
        state_last = state_act
        date_last = date_act

print(utc_to_local(real_last_changed).strftime('%Y-%m-%d %H:%M:%S'))

Maybe someone can us it too.

9 Likes

Hi @VDRainer

Thanks for this script.

May I ask what does the script report which entity? and how?

I am trying to use your script as I have mysql to update my location, I have two device trackers and need the most reliable one to update my location and show it on HA as a single entity.

As you can see in the command_line sensor above, the script gets called with one entity_id.
Then it fetches the most recent records for that entity and compares the states.

Oh right I see

Is there a way where you could compare 2 entities and give the result of the latest one?

Thanks.

I guess there is no way to run this in Hass.io correct?

I modified it a little, so it should run in hass.io too.

The sensor example:

sensor:
  - platform: command_line
    name: "RLC MQTT Test Switch"
    command: "python3 scripts/real_last_change.py switch.mqtt_test"
    scan_interval: 60

The script lives in /config/scripts/real_last_change.py

import sys
import MySQLdb
from datetime import timezone

entity_id = sys.argv[1]

## Use the settings from recorder config in configuration.yaml
#recorder:
#  db_url: mysql://user:[email protected]/hadb?charset=utf8
mysql_user = 'user'
mysql_pass = 'pass'
mysql_host = '127.0.0.1'
mysql_db   = 'hadb'

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)

db = MySQLdb.connect (host=mysql_host, user=mysql_user, passwd=mysql_pass, db=mysql_db)
cursor = db.cursor(MySQLdb.cursors.DictCursor)

sql = """SELECT state,last_changed FROM states WHERE entity_id = '%s'
         ORDER BY created DESC LIMIT 100""" % (entity_id)

cursor.execute(sql)
result = cursor.fetchall()
db.close()

if len(result) > 0:
    real_last_changed = None
    for idx, row in enumerate(result):
        state_act = row['state']
        date_act = row['last_changed']
        if idx == 0:
            state_last = state_act
            date_last = date_act
        if state_last != state_act:
            real_last_changed = date_last
            break
        else:
            state_last = state_act
            date_last = date_act

    if real_last_changed:
        print(utc_to_local(real_last_changed).strftime('%d.%m.%Y %H:%M:%S'))
    else:
        print('No state change for: ' + entity_id)
else:
    print('No states for: ' + entity_id)
3 Likes

I can’t believe this isn’t easier to do!

I have implemented this script using the built-in SQLite database (albeit I don’t modify the format or check for UTC):

# todo: error handling, think about utc

import sys
from sqlalchemy import create_engine
import pandas as pd

# define your home assistant db location below:
db = "/home/amadeus/.homeassistant/home-assistant_v2.db"

# specify entity_id as first argument to this script
# entity_id = "sensor.attic_office_temperature"
entity_id = sys.argv[1]

# connect using SQLalchemy
engine = create_engine(f"sqlite:///{db}")
conn = engine.connect()

# how many restart events can we expect?
num_restarts = 100

# build the query
query = f"""
SELECT
    state,
    last_changed
FROM states 
WHERE 
    domain="sensor" AND
    entity_id="{entity_id}" AND
    state !="unknown"
ORDER BY last_updated DESC 
LIMIT {num_restarts}
"""

# execute the query and load into a pandas dataframe:
df = pd.read_sql_query(query, conn)
conn.close()

# compare the results to a dataframe shifted one forward with backfill,
# extract the 1st changed value, 
# and extract the last_changed value

real_last_changed = pd.to_datetime(df.last_changed[df.state.ne(df.state.shift().bfill())].iloc[0])

print(real_last_changed)

This is implemented with yaml example at: https://nbviewer.jupyter.org/github/amadeuspzs/ha-config/blob/master/real_last_change.ipynb

For my use case (automation to switch off heater is sensor goes stale), I may just create a sensor value that is how many hours since last changed (accounting for restarts) to make the comparison easier.

1 Like

I’d much rather this functionality was built into core - if you agree please have a look at this feature request: Retain last state change data of a sensor after reboot

Hi! I tried to do it, but an error comes out. This is my configuration:

entity_id: switch.irrigation_pump

My db file is in directory config/home-assistant_v2.db

I created the scripts folder in /config folder and I created the file named real_last_change.py

real_last_change.py:

# todo: error handling, think about utc

import sys
from sqlalchemy import create_engine
import pandas as pd

# define your home assistant db location below:
db = "/config/home-assistant_v2.db"

# specify entity_id as first argument to this script
entity_id = "switch.irrigation_pump"
# entity_id = sys.argv[1]

# connect using SQLalchemy
engine = create_engine(f"sqlite:///{db}")
conn = engine.connect()

# how many restart events can we expect?
num_restarts = 100

# build the query
query = f"""
SELECT
    state,
    last_changed
FROM states 
WHERE 
    domain="switch" AND
    entity_id="{entity_id}" AND
    state !="unknown"
ORDER BY last_updated DESC 
LIMIT {num_restarts}
"""

# execute the query and load into a pandas dataframe:
df = pd.read_sql_query(query, conn)

# compare the results to a dataframe shifted one forward with backfill,
# extract the 1st changed value, 
# and extract the last_changed value

real_last_changed = pd.to_datetime(df.last_changed[df.state.ne(df.state.shift().bfill())].iloc[0])

In configuration.yaml:

sensor:
  - platform: command_line
    name: "Ultima Irrigazione"
    command: "python3 /config/scripts/real_last_change.py switch.irrigation_pump"
    scan_interval: 60

Unfortunately it doesn’t work, the sensor always says “unknown” and I have this error in the log:

Logger: homeassistant.components.command_line
Source: components/command_line/init.py:44
Integration: command_line (documentation, issues)
First occurred: 21:58:30 (16 occurrences)
Last logged: 22:13:29

Command failed: python3 /config/scripts/real_last_change.py switch.irrigation_pump