Last time a sensor is updated in frontend (through AppDaemon)

sometimes a sensor stops updating.
it could be that the sensor is in error and should be resetted.
in the frontend there is no easy way to see if how long ago a sensor was updated.
off course you could search in the logbook, but then you could search a long time.

through appdaemon there is an easy way.

i made an app like this:

###########################################################################################
#                                                                                         #
#  Rene Tode ( [email protected] )                                                            #
#  ( with a lot off help from andrew cockburn (aimc) )                                    #
#  2016/08/16 Germany                                                                     #
#                                                                                         #
###########################################################################################

import appapi
import datetime

class sensorcontrole(appapi.AppDaemon):

  def initialize(self):
    self.listen_state(self.sensor_controle, "sensor")
  
  def sensor_controle(self, entity, attribute, old, new, kwargs):
    set_sensor_time = open("C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\laatste_controle.py", "w")
    runtime = datetime.datetime.now().strftime("%H:%M:%S")
    set_sensor_time.write('print("' + runtime+'")')
    set_sensor_time.close()
    for counter in range(1,int(self.args["total_sensors"])+1):
      device, entity_name = self.split_entity(entity)
      sensor_name=self.args["sensor" + str(counter)]
      if entity_name == sensor_name:         
        set_sensor_time = open("C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\" + sensor_name + ".py", "w")
        runtime = datetime.datetime.now().strftime("%H:%M:%S")
        set_sensor_time.write('print("' + runtime+'")')
        set_sensor_time.close()

this creates a file with the name ā€˜entity_id.pyā€™ in the dir controle.
it also creates a file laatste_controle.py (last_control)

in de config i give the sensors i like to track like this:

[controle1]
module = sensorcontrole
class = sensorcontrole
total_sensors = 8
sensor1 = huiskamer_box_4_1
sensor2 = temperature_sensor_1_0
sensor3 = temperature_sensor_1_1
sensor4 = arduino2_3_0
sensor5 = vijver_repeater_7_0
sensor6 = vijver_repeater_7_1
sensor7 = moestuin_8_25
sensor8 = moestuin_8_26

and then in the configuration.yaml:

sensor 7:
  platform: command_line
  name: Laatste controle
  command: "py C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\laatste_controle.py"
sensor 8:
  platform: command_line
  name: Temp. op marmerplaat
  command: "py C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\temperature_sensor_1_0.py"
sensor 9:
  platform: command_line
  name: Temp. voor haard
  command: "py C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\temperature_sensor_1_1.py"
sensor 10:
  platform: command_line
  name: Temp. vijverwater
  command: "py C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\vijver_repeater_7_0.py"
sensor 11:
  platform: command_line
  name: Temp. onder tegels
  command: "py C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\vijver_repeater_7_1.py"
sensor 12:
  platform: command_line
  name: Temp. raamkozijn (legohuisje)
  command: "py C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\arduino2_3_0.py"
sensor 13:
  platform: command_line
  name: Temp. Houten box
  command: "py C:\\Users\\rene\\AppData\\Roaming\\.homeassistant\\controle\\huiskamer_box_4_1.py"
groups:
  controle:
    name: Controle
    view: yes
    entities:
      - group.controlebox
  controlebox:
    name: Sensors
    entities:
      - sensor.laatste_controle
      - sensor.temp_op_marmerplaat
      - sensor.temp_voor_haard
      - sensor.temp_vijverwater
      - sensor.temp_onder_tegels
      - sensor.temp_raamkozijn_legohuisje

5 Likes

Very cool!

1 Like

next step:
in the app a control function for how long the sensor not has been updated.
and if the time is longer then the amount off time given in the config, it gives a notify that the sensor is in error.

Nice, Rene! If I wanted the format of the time to be AM/PM instead of 24hr, what would I need to change?

only the both lines:

runtime = datetime.datetime.now().strftime("%H:%M:%S")

i have chosen to calculate the timestring 2 times, to be able to manipulate the control sensor different then the others,
but the second 1 also could be deleted.

to get 13 hour clock you need:

runtime = datetime.datetime.now().strftime("%I:%M:%S %p")
1 Like

by the way, i also am going to make a group with sensors for controlling when automations have fired the last time (with date) and when switches are switched for the last time, thats why the group is called sensors.

1 Like

This is really cool!
Would you consider making a minimal one thatā€™s more generalized and using English variables?

1 Like

yeah, thats no problem.
i am also gonna add 1 more option:
instead of last time the sensor is updated, time gone by since the sensor is updated.

1 Like

That could open up some interesting possibilities.

Imma think Iā€™ll wait a bitā€¦ LOL Iā€™d like to see what you come up with.

1 Like

it took me some figuring out, but its going well.
right now i have:

1 app which can be reused to make checksensors for:

  • sensors (last change time for any sensor you like)
  • switches (last change time for any switch you like)
  • devices (last change time for any device you like)
  • input_booleans (last change time for any boolean you like)
    actually you can track everything you like in HA and give the last change at the frontend.

i think i already have parts from my wished automation fired functions.
for those who have hoped to check if an yaml automation is fired, i must say that that is not coming directly.
for the simple reason that i dont have yaml automations anymore.
all my automations are through apps and what i will create is a general function which updates a commandline sensor.

i would have released my updates now i am so far, but in appdaemon there is a small bug.
@aimc has found a workaround for me, but i dont want to release untill i have taken out the workaround.

that also gives me the time to see if it keeps satisfying me.

already build in option:

  • setting time format in the configuration file
  • windows and Linux dir formats automatic (only windows tested)
  • setting name from controle object in the configuration file
  • chosing if you like last updated time or time gone by since in the confuguration file
  • setting dir for saving the commandline sensors in the configuration file
  • chosing the object_type in the configuration file (sensor, switch, input_boolean, etc.)

a preview:

1 Like

Great progress Rene! The fix will be available probably at the weekend, as I have some other big changes I am testing.

1 Like

Wow! I love seeing things like this grow!

@aimc May I suggest a new guest blog post highlighting some om the ā€œappsā€ that have come out?

2 Likes

Definitely - lets gather a few more first, but I think thatā€™s a great idea :slight_smile:

1 Like

I had a similar need, wanting to look for sensors that stopped updating. Iā€™m not sure if last_updated is the last time the value changed or the last time it was polled/return value. I think this problem may arise with either solution. The app will create a new sensor in HA with the number past a given age with the details in the attributes.

All you need is this app:

sensorcheck:
  module: sensorage
  class: SensorAge

Code

import appdaemon.appapi as appapi
from datetime import timezone

#  {'entity_id': 'sensor.dark_sky_precip_probability', 'last_updated': '2017-11-28T16:20:39.397961+00:00', 'attributes': {'attribution': 'Powered by Dark Sky', 'friendly_name': 'Precip Probability', 'unit_of_measurement': '%', 'icon': 'mdi:water-percent'}, 'state': '0', 'last_changed': '2017-11-28T16:20:39.397961+00:00'},

# Length of time that is too long...20 minutes
error_time = 60 * 20
# Exclude sensors
black_list = ['sensor.dark_sky_daily_high_temperature', 'sensor.dark_sky_daily_low_temperature', 'sensor.feels_like_temp', 'sensor.temp']
#Broad whitelist / String that must appear in sensor name.  Use '' to disable
must_contain = 'temp'

#Return attributes
attributes = {'icon': 'mdi:alarm', 'friendly_name': 'Sensor Age', 'unit_of_measurement': 'sensors'}


class SensorAge(appapi.AppDaemon):
    def initialize(self):
        self.handle = self.run_every(self.age_check, self.datetime(), 5 * 60)

    def age_check(self, kwargs):
        state = self.get_state("sensor")
        # self.log('Dump: {}'.format(state))
        problems = []
        for name, attrs in state.items():
            if name in black_list:
                continue
            if (len(must_contain)) and (must_contain not in name):
                continue
            age = (self.datetime().replace(tzinfo=timezone.utc) - self.convert_utc(attrs['last_updated'])).seconds
            if age > error_time:
                try:
                    self.log("Sensor {}({}) is {}m behind".format(attrs['attributes']['friendly_name'], name, round(age/60)))
                    problems.append(attrs['attributes']['friendly_name'] + " ({}m)".format(round(age/60)))
                except KeyError:
                    self.log("Sensor {} is {}m behind".format(name, round(age/60)))
                    problems.append(name + " ({}m)".format(round(age/60)))

        attributes['problem_sensors'] = problems
        self.set_state('sensor.sensorages', state=len(problems), attributes=attributes)
2 Likes

very nice use off set_state! :wink:

1 Like

Seems this needs ā€œimport appdaemon.appapi as appapiā€ added to work. Unsure why your example lacks this line?

It was there, but the markup hid it. Corrected.

Iā€™m trying to use thisā€¦ but the delta is always greater then 7200 secondsā€¦ (Iā€™m UTC+2). So somehow both are not UTCā€¦ any ideas?

it also depends on how you have configured your device.
a good start would be to log the last updated and see what output that gives.
it will probably give a local time string and not an ISO 8601 UTC string.
so it will probably convert to an utc time without the timedifference.

so i think you want this:
age = self.datetime() - datetime.datetime.strptime(attrs[ā€˜last_updatedā€™],"%Y/%m/%d %H:%M:%S")
where i did a guess for the timeformat you get from last updated, so you might need to change that.