ObjectTracker 2.0 (trough AppDaemon)

completely rewritten and a compact name.

ObjectTracker gives you the option to see if youre sensors are up and running and how long ago they were last updated.
and that all trough the normal frontend.
but you can also check all other object you would like. (for example: see how long ago a switch was used)

there are 2 apps. 1 app with some general functions (which i will use in other apps) and 1 app which listens to HA.

###########################################################################################
#                                                                                         #
#  some general function which can be called from other Apps                              #
#  update_object_time: sets the state from objects in HA. object will look like:          #
#                      'controle.friendly_name_from_entity'                               #
#                      state can be last time updated or time gone by since last update   #
#  check_last_update_time: checks when the last time was that a sensor was updated        #
#  save_last_update_time: saves the time that an object is updated to a file in a dir     #
#                         you can define. the file wil have the name: lut_entity_id.py    #
#  reformat_time: changed a timeformat from H:M:S to %H:%M:%S                             #
#                                                                                         #
#  Rene Tode ( [email protected] )                                                            #
#  version 2.0                                                                            #
#  2016/09/04 Germany                                                                     #
#                                                                                         #
###########################################################################################

import appdaemon.appapi as appapi
import datetime as datetime
import platform

class general_fnc(appapi.AppDaemon):

    def initialize(self):
        return
      
    def update_object_time(self, object_name, object_friendly_name, dir_name, time_gone_by, time_format, object_type):
        update_time = datetime.datetime.now()
        new_time_format = self.reformat_time(time_format)
        str_complete_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        if time_gone_by == "true" or time_gone_by == "True":
            str_old_time = self.check_last_update_time(dir_name, object_name)
            try:
                old_time = datetime.datetime.strptime(str_old_time, "%Y-%m-%d %H:%M:%S")
            except:
                self.log( "strptime gives error on: " + str_old_time, level = "INFO")
                return
            gone_by_time = update_time - old_time
            str_update_time = str(gone_by_time)[:-7] + " ago"
        else:
            str_update_time = update_time.strftime(new_time_format)
                   
        new_entity=object_friendly_name.replace(" ","_")
        new_entity=new_entity.replace(".","")
        new_entity=new_entity.replace("(","")
        new_entity=new_entity.replace(")","")
        self.set_state("controle." + new_entity, state = str_update_time) 

    def check_last_update_time(self, objects_dir, object_name):
        if platform.system()=="windows":
            complete_file_name = objects_dir + "\\lut_" + object_name + ".py"
        else:
            complete_file_name = objects_dir + "/lut_" + object_name + ".py"
        try:
            set_object = open(complete_file_name, "r")
            old_time= set_object.readline()
            set_object.close()
            return old_time
        except:
            return "2000-01-01 00:00:00"

    def save_last_update_time(self, objects_dir, object_name):
        if platform.system()=="windows":
            complete_file_name = objects_dir + "\\lut_" + object_name + ".py"
        else:
            complete_file_name = objects_dir + "/lut_" + object_name + ".py"
            
        str_complete_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        try:
            set_object = open(complete_file_name, "w")
            set_object.write(str_complete_time)
            set_object.close()
        except:
            self.log( "couldnt save the time from: " + object_name + " in " + complete_file_name, level = "INFO")
            

    def reformat_time(self, time_format):
        new_time_format = ""
        for counter in range(0,len(time_format)):
            if time_format[counter]!=" " and time_format[counter]!="-" and time_format[counter]!=":" and time_format[counter]!="\\":
                new_time_format=new_time_format + "%" + time_format[counter]
            else:
                new_time_format=new_time_format + time_format[counter]       
        return new_time_format

save this one as general_app_functions.py in your app diectrory

###########################################################################################
#                                                                                         #
#  ObjectTracker 2.0                                                                      #
#                                                                                         #
###########################################################################################
#                                                                                         #
#  with ObjectTracker you can track the last updated time from any object in HA           #
#  options are to give the last time an object was updated or the time that has gone by   #
#  you have to set the following options in the appdaemon.cfg:                            #
#                                                                                         #
#  object_type = the type you like to track (switch, input_boolean, sensor, etc)          #
#  time_gone_by = True or False (false for showing last updated time)                     #
#  dir_name = the name of the directory you want the files with times saved               #
#  time_format = any timeformat you like (python strftime type) without %                 #
#                H:M gives 01:27, Y-m-d H:M:S gives 2016-09-04 01:27:25, etc.             #
#  total_objects = the amount off object you want to track                                #
#  object1 = HA entity_ID without the platform part. (for switch.light1 use light1)       #
#  object2 = ...                                                                          #
#  object3 = untill you reached youre total_object amount                                 #
#                                                                                         #
#  note that you need to set a new sections in the cfg for each type of object you like   #
#  to track. if you want to track 1 switch and 1 sensor you need to make to sections.     #
#                                                                                         #
#  ObjectTracker depends on general_app_functions.py set as app and set in the cfg as     #
#  [generalvars]                                                                          #
#                                                                                         #
#  Rene Tode ( [email protected] )                                                            #
#  version 2.0                                                                            #
#  2016/09/04 Germany                                                                     #
#                                                                                         #
###########################################################################################

import appdaemon.appapi as appapi
import datetime

class objectcontrole(appapi.AppDaemon):

  def initialize(self):
    self.listen_state(self.object_controle, self.args["object_type"])
    if self.args["time_gone_by"] == "true" or self.args["time_gone_by"] == "True":
      time = datetime.time(0, 0, 0)
      self.run_minutely(self.object_controle_minutely, time)
      
  def object_controle(self, entity, attribute, old, new, kwargs):
    fnc = self.get_app("generalvars")
    device, entity_name = self.split_entity(entity)
    for counter in range(1,int(self.args["total_objects"])+1):
      device, entity_name = self.split_entity(entity)
      object_name=self.args["object" + str(counter)]
      if entity_name == object_name:         
        fnc.update_object_time(object_name, self.friendly_name(entity), self.args["dir_name"], self.args["time_gone_by"], self.args["time_format"], self.args["object_type"])
        fnc.save_last_update_time(self.args["dir_name"], object_name)
  def object_controle_minutely(self, kwargs):
    fnc = self.get_app("generalvars")
    for counter in range(1,int(self.args["total_objects"])+1):
      object_name=self.args["object" + str(counter)]
      fnc.update_object_time(object_name, self.friendly_name(self.args["object_type"] + "." + object_name), self.args["dir_name"], self.args["time_gone_by"], self.args["time_format"], self.args["object_type"])

save this 1 as objectcontrole.py in your app directory

and then edit your appdaemon.cfg file like this:

[generalvars]
module = general_app_functions
class = general_fnc

[controle]
module = objectcontrole
class = objectcontrole
object_type = sensor (give here ant object type you like, switch, input_boolean, etc)
dir_name = (the dir you like to have the files saved, no default)
time_gone_by = True or False
time_format = H:M:S ( an strftime format without %)
total_objects = 3 (the amount of object you like to track)
object1 = light1 (the part from the HA entity_ID after the dot)
object2 = light_2
object3 = anything_3

if you like to track more objecttypes make more sections like:

[controle1]
module = objectcontrole
class = objectcontrole
object_type = sensor
dir_name = c:\
time_gone_by = True
time_format = H:M:S
total_objects = 3
object1 = temp_1
object2 = rain_3
object3 = heat_2
[controle2]
module = objectcontrole
class = objectcontrole
object_type = switch
dir_name = c:\
time_gone_by = True
time_format = H:M:S
total_objects = 3
object1 = light1 
object2 = light_2
object3 = anything_3

I hope you have fun with it.
tell me if anything is wrong and i will change it to.
it should be platform independant, but only tested on windows enviroment.

3 Likes

is this the sort of thing that we want to see in the appdaemon examples/standard apps?

if andrew likes it enough he can use it in his setup.

after installing the general_app_functions.py it is now also possible to track any automation you make in youre apps.

just copy:

    fnc = self.get_app("generalvars")
    object_name = "automation_any_description"]
    dir_name = "give any dir here"
    fnc.save_last_update_time(dir_name, object_name)
    fnc.update_object_time(object_name, object_name, dir_name", "False", "d-m-Y H:M:S", "")

to any callbackfunction

time_gone_by must be false because you have no function to update the tracker
you can use any time format you like without the %

@aimc it is up to you if you like it enough to make it a part from your code :wink:

I would be happy to include this as an example :slight_smile:

1 Like

I tried to include documentary how to use, but feel free to change it if you think people could better understand it, or if i made any errors.

if i make an update i’ll tell you.

i thing i thought about after this release is that there is probably no need att all to use any files.
there is a last_changed for every entity. so i could compare that with the actual time and send that to the sensors.

1 thing i would like in your code:
right now i have commented the warning line in check entity.
it would be nice if there was an option in the cfg to have those warnings when you like and not when you dont want them.

Great stuff as I was just looking to extent my monitoring of various devices. I had already implemented a heartbeat mechanism in all my ESP8266 sensors (they send a update via MQTT every minute) as it is not always clear that they were still working.

However after starting implementing other checks I was wondering how HASS would deal with that as it would be checking status constantly to update sensors… killing the performance in the end. With this idea I can set the check frequency to a defined time interval and not have to write an automation rule for every device I want to check!

Thanks for sharing Rene!

1 Like

I am going to fix it so it only warns you the first time if I can …

1 Like

i think that you then have to save every none exsisting entity somewhere in a temp file somewhere.
you cant make a check with HA, because the entity doesnt exsist in HA.

or you must make a create object function.
that would be even better, because then we also can create attributes like friendly name.
as it is now, that isnt possible.

OK, I’m close, but I can’t quite get this to work.

I am running the AIO installer version and I installed Appdaemon outside of the virtual environment. I verified appdaemon is running using the user pi.

I keep getting error messages regarding the objectcontrole.py file.

Thanks in advance - i’m sure it is something simple…

I am trying to monitor two sensors:
sensor.camp_indoor
sensor.camp_outdoor

here is my appdaemon.yaml:

#appdaemon.yaml
AppDaemon:
  logfile: /home/pi/appdaemon.log
  errorfile: /home/pi/appdaemon_errors.log
  logsize: 100000
  log_generations: 3
  threads: 10
HASS:
  ha_url: http://192.168.0.16:8123

here is my apps.yaml:

#apps.yaml

hello_world:
  module: hello
  class: HelloWorld

objectcontrole:
  module: objectcontrole
  class: objectcontrole

general_app_functions:
  module: general_app_functions
  class: general_fnc

Appdaemon.cfg:

[generalvars]
module = general_app_functions
class = general_fnc

[controle]
module = objectcontrole
class = objectcontrole
object_type = sensor
dir_name = /home/pi
time_gone_by = False
time_format = H:M:S
total_objects = 2
object1 = camp_outdoor
object2 = camp_indoor

Here is my appdeamon.log:
2017-11-19 13:49:59.541453 INFO Configuration read from: /home/homeassistant/.homeassistant/configfiles/appdaemon.yaml 2017-11-19 08:49:59.664154 INFO Starting Apps 2017-11-19 08:49:59.761264 INFO Got initial state 2017-11-19 08:49:59.762788 INFO Loading Module: /home/homeassistant/.homeassistant/configfiles/apps/general_app_functions.py 2017-11-19 08:49:59.767534 INFO Loading Object general_app_functions using class general_fnc from module general_app_functions 2017-11-19 08:49:59.768526 INFO Loading Module: /home/homeassistant/.homeassistant/configfiles/apps/objectcontrole.py 2017-11-19 08:49:59.772189 INFO Loading Object objectcontrole using class objectcontrole from module objectcontrole 2017-11-19 08:49:59.777036 WARNING Logged an error to /home/pi/appdaemon_errors.log 2017-11-19 08:49:59.777353 INFO Loading Module: /home/homeassistant/.homeassistant/configfiles/apps/hello.py 2017-11-19 08:49:59.779386 INFO Loading Object hello_world using class HelloWorld from module hello 2017-11-19 08:49:59.944653 INFO hello_world: Hello from AppDaemon 2017-11-19 08:49:59.947479 INFO hello_world: You are now ready to run Apps! 2017-11-19 08:49:59.947857 INFO App initialization complete 2017-11-19 08:49:59.948458 INFO Dashboards are disabled 2017-11-19 08:49:59.948756 INFO API is disabled 2017-11-19 08:49:59.958455 INFO Connected to Home Assistant 0.57.2

Here is the error log:
`2017-11-19 08:37:00.566477 WARNING ------------------------------------------------------------
2017-11-19 08:49:59.772948 WARNING ------------------------------------------------------------
2017-11-19 08:49:59.773341 WARNING Unexpected error during loading of objectcontrole:
2017-11-19 08:49:59.773629 WARNING ------------------------------------------------------------
2017-11-19 08:49:59.776217 WARNING Traceback (most recent call last):
File “/usr/local/lib/python3.4/dist-packages/appdaemon/appdaemon.py”, line 912, in read_app
init_object(name, class_name, module_name, conf.app_config[name])
File “/usr/local/lib/python3.4/dist-packages/appdaemon/appdaemon.py”, line 583, in init_object
conf.objects[name][“object”].initialize()
File “/home/homeassistant/.homeassistant/configfiles/apps/objectcontrole.py”, line 39, in initialize
self.listen_state(self.object_controle, self.args[“object_type”])
KeyError: ‘object_type’

2017-11-19 08:49:59.776716 WARNING ------------------------------------------------------------
`

Last but not least, here is the objectcontrole.py file (I am using verison 2.0):
###########################################################################################

import appdaemon.appapi as appapi
import datetime

class objectcontrole(appapi.AppDaemon):

  def initialize(self):
    self.listen_state(self.object_controle, self.args["object_type"])
    if self.args["time_gone_by"] == "true" or self.args["time_gone_by"] == "True":
      time = datetime.time(0, 0, 0)
      self.run_minutely(self.object_controle_minutely, time)

  def object_controle(self, entity, attribute, old, new, kwargs):
    fnc = self.get_app("generalvars")
    device, entity_name = self.split_entity(entity)
    for counter in range(1,int(self.args["total_objects"])+1):
      device, entity_name = self.split_entity(entity)
      object_name=self.args["object" + str(counter)]
      if entity_name == object_name:
        fnc.update_object_time(object_name, self.friendly_name(entity), self.args["dir_name"], self.args["time_gone_by"], self.args["time_format"], self.args["object_type"])
        fnc.save_last_update_time(self.args["dir_name"], object_name)
  def object_controle_minutely(self, kwargs):
    fnc = self.get_app("generalvars")
    for counter in range(1,int(self.args["total_objects"])+1):
      object_name=self.args["object" + str(counter)]
      fnc.update_object_time(object_name, self.friendly_name(self.args["object_type"] + "." + object_name), self.args["dir_name"], self.args["time_gone_by"], self.args["time_format"], self.args["object_type"])

you are trying to mix old version and new version AD.
the old version used .cfg config the new version only yaml.
so the settings you have in the cfg file should be in apps.yaml and the cfg file can be deleted.

Thanks Rene, that cleared the error on startup, now when my sensor updates, I get this:

pi@raspberrypi:~ $ cat appdaemon_errors.log
2017-11-19 17:44:19.006439 WARNING ------------------------------------------------------------
2017-11-19 17:44:19.008133 WARNING Unexpected error in worker for App objectcontrole:
2017-11-19 17:44:19.008901 WARNING Worker Ags: {'id': UUID('eacc9332-f333-4f88-8db1-c18eca58116e'), 'function': <bound method objectcontrole.object_controle of <objectcontrole.objectcontrole object at 0x759ab430>>, 'name': 'objectcontrole', 'new_state': '31', 'attribute': 'state', 'old_state': '33', 'type': 'attr', 'kwargs': {}, 'entity': 'sensor.camp_outdoor'}
2017-11-19 17:44:19.009499 WARNING ------------------------------------------------------------
2017-11-19 17:44:19.011633 WARNING Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/appdaemon/appdaemon.py", line 512, in worker
    utils.sanitize_state_kwargs(args["kwargs"]))
  File "/home/homeassistant/.homeassistant/configfiles/apps/objectcontrole.py", line 51, in object_controle
    fnc.update_object_time(object_name, self.friendly_name(entity), self.args["dir_name"], self.args["time_gone_by"], self.args["time_format"], self.args["object_type"])
AttributeError: 'NoneType' object has no attribute 'update_object_time'

2017-11-19 17:44:19.012646 WARNING ------------------------------------------------------------
2017-11-19 17:46:21.438687 WARNING ------------------------------------------------------------
2017-11-19 17:46:21.439541 WARNING Unexpected error in worker for App objectcontrole:
2017-11-19 17:46:21.440270 WARNING Worker Ags: {'id': UUID('eacc9332-f333-4f88-8db1-c18eca58116e'), 'function': <bound method objectcontrole.object_controle of <objectcontrole.objectcontrole object at 0x759ab430>>, 'name': 'objectcontrole', 'new_state': '33', 'attribute': 'state', 'old_state': '31', 'type': 'attr', 'kwargs': {}, 'entity': 'sensor.camp_outdoor'}
2017-11-19 17:46:21.440816 WARNING ------------------------------------------------------------
2017-11-19 17:46:21.441703 WARNING Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/appdaemon/appdaemon.py", line 512, in worker
    utils.sanitize_state_kwargs(args["kwargs"]))
  File "/home/homeassistant/.homeassistant/configfiles/apps/objectcontrole.py", line 51, in object_controle
    fnc.update_object_time(object_name, self.friendly_name(entity), self.args["dir_name"], self.args["time_gone_by"], self.args["time_format"], self.args["object_type"])
AttributeError: 'NoneType' object has no attribute 'update_object_time'

2017-11-19 17:46:21.442308 WARNING ------------------------------------------------------------

in your objectcontroleapp there is a connection to another app.

    fnc = self.get_app("generalvars")

that should be the file general_app_functions.py
in the config there was this:

[generalvars]
module = general_app_functions
class = general_fnc

and there is that same name generalvars.
but in your yaml file you have:

general_app_functions:
  module: general_app_functions
  class: general_fnc

so now you named the app general_app_functions in stead of generalvars.
so you need to change or the name in your yaml or the get_app function in your code.

because of that failure your app cant reacht the function update_object_time in the other app, because it doesnt know where to look.

Ugh, it was right there in the readme - if it was a snake it would have bit me…

Thank you Rene!

1 Like