[AppDaemon] Tutorial #1 Tracker-Notifier

Sure! Let me know how it works out for you. I’d love to hear feedback on what you think could be explained better. The complex example is more flexible of an approach to the problem, but doesn’t have all the accompanying explanation. If you’d like me to expand on any of it, let me know and I certainly can!

I should caveat that currently, if you’re going to use the complex example for light notifications, any time you change the brightness, you’ll get a “This light has been on for more than X seconds!” notification. That’s not necessarily what you want in that case, so you’d need to modify it to pull out the brightness level and format your message accordingly. I just used it as an example because I don’t have any door/window/garage sensors! ha ha :wink:

1 Like

Thanks for this. I found the tutorial very useful.

Could you give me any pointers about how one goes about debugging an AppDaemon python scripts? There must be an easier way than checking the output of the error and appdaemon log files. I’m new to Python so any advice would be appreciated.

1 Like

This is awesome. I do have a degree in CS but only dabble in Python so I’m excited to see more of these.

1 Like

Can you give me an example of an error you’re seeing or what you’re expecting to see? The easiest (maybe not the simplest) way would be to periodically check your error log. This does give me a great idea for my next tutorial though, one that would solve this need for the “simplest” way! :slight_smile:

Hint

We’ll simply write an app to notify us any time there is a “new” error!

I just keep three or four terminal sessions open. I use one as a unix prompt to edit various files. I use the second to keep a running tail of my appdaemon.log file, and the third is to keep a running tail of my appdaemon.err file. It does make for a busy screen if you have them all open at once, but it also gives a good feeling of satisfaction to look over and see that your err window’s last error was a few hours ago. You do have to close and reopen them periodically though because AD does log swaps so if your log tail doesn’t change for a while, you probably aren’t watching the live file anymore.

Now, you might not need to clutter up your screen so much. :wink:

Tutorial #2 is up and rockin’! Check it out. Thanks for the awesome idea for an app, @awitty! I’m actually going to be using the complex version in my own setup.

Thank you for doing this - as a python supahnoob it’s exactly what I wanted but didn’t want to ask for! Much appreciated.

I’ll be following along.

1 Like

Me to, great work, much appreciated :smiley:

1 Like

I can’t wait to come back to this and to the other one. So busy trying to get the skins ready for HADash v2!

1 Like

Thanks, i managed to figure out how to debug the code. I’m sure it’s second nature to most people on this forum, and perhaps there’s an easier way. But for anybody struggling like me this is what i did;

edit your .py file and add the following line near the top;

import pdb # this is the python debugger

now add a breakpoint somewhere in your code where you want to start debugging by inserting;

pdb.set_trace()

now stop your appdaemon service (i.e. sudo service appdaemon stop)
and run appdaemon in the foreground. (i.e. appdaemon -c “path to your config file”)

appdaemon will now stop at your breakpoint and you can step through the code and inspect variables etc.

There’s a good tutoral on how to use the debugger here

much easier than trial and error !

2 Likes

There’s a python debugger??? I’m afraid to run my code against it. LOL

Just started using appdaemon today. Got everything set up and running. Just so I could get my bearings on how things work I tried to copy the simple script and run that. But I can’t seem to get it to work. I realize that this tutorial is probably not meant to be copied and pasted but to breakdown and explain the parts of the script but just getting it working will help me step through the parts.

First issue i ran into is initialize is spelled incorrectly. Once i realized and fixed that I started getting the following error.

2017-03-01 23:29:52.005857 WARNING ------------------------------------------------------------
2017-03-01 23:29:52.006550 WARNING Unexpected error during exec_schedule() for App: door_notifications
2017-03-01 23:29:52.007265 WARNING Args: {'repeat': False, 'callback': <bound method DoorMonitor.notifier of <doormon.DoorMonitor object at 0x709cfeb0>>, 'type': None, 'offset': 0, 'id': UUID('8bbb256a-abe7-4f9a-a9ec-9beb63cb39fd'), 'basetime': 1488428992, 'name': 'door_notifications', 'interval': 0, 'timestamp': 1488428992, 'kwargs': {'entity': 'binary_sensor.front_door_opened'}}
2017-03-01 23:29:52.007799 WARNING ------------------------------------------------------------
2017-03-01 23:29:52.008707 WARNING Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/appdaemon/appdaemon.py", line 391, in exec_schedule
    "attribute": args["kwargs"]["attribute"],
KeyError: 'attribute'

2017-03-01 23:29:52.009283 WARNING ------------------------------------------------------------
2017-03-01 23:29:52.009769 WARNING Scheduler entry has been deleted
2017-03-01 23:29:52.010235 WARNING ------------------------------------------------------------

Cheers,
Mike

1 Like

In the notifier function take out the word attribute= from the get_state line

friendly_name = self.get_state(kwargs[‘entity’], ‘friendly_name’)

GIve that a try

This is a slightly modified version

import appdaemon.appapi as appapi

#
# App to send notification when a door left open for too long
#
# Args: (set these in appdaemon.cfg)
# ttl = # of seconds to wait until notified
#
#
# EXAMPLE appdaemon.cfg entry below
# 
# # Apps
# 
# [door_notifications]
# module = door_notifications
# class = DoorMonitor
# ttl = 15
#

class test(appapi.AppDaemon):

    def initialize(self):
        self.log("test")
        self.door_entities = ['input_boolean.spot']

        self.door_timer_library = {}

        for door in self.door_entities:
            self.listen_state(self.tracker, entity=door)

    def tracker(self, entity, attribute, old, new, kwargs):

        try:
            self.cancel_timer(self.door_timer_library[entity])
        except KeyError:
            self.log('Tried to cancel a timer for {}, but none existed!'.format(entity), 
                     level='DEBUG')

        if new == 'on':
            self.door_timer_library[entity] = self.run_in(self.notifier, 
                                                          int(self.args['ttl']), 
                                                          e=entity)

    def notifier(self, kwargs):
        friendly_name = self.get_state(kwargs['e'], 'friendly_name')

        title = "Message from HASS!"
        message = "{} has been open for more than {} seconds.".format(friendly_name,
                                                                      self.args['ttl'])

        self.log('notify/notify, title={}, message={}'.format(title,message))

I think the problem is that sometimes entity especially if passed in through kwargs interferes with it if it’s used in the library code. So I changed where we passed entity=entity into the callback function through kwargs to e=entity and that got rid of the error you were seeing. I also simplified it to look at just one input_boolean (since I don’t have any door sensors like that), and to just dump out a log message instead of sending a notification which I don’t have setup in my config.

1 Like

Thanks for the info. Trying to test this from work by just setting the state of the door sensor to on in the states-dev tool (since I can’t manually open the door) I’m trying to add the json for the friendly_name but I can’t for the life of me figure out the syntax. I’ve tried a ton of variations on basically {"Friendly_name" : "Front Door Opened"} but I keep getting various errors along the lines of Error parsing JSON: SyntaxError: JSON.parse: unexpected character at line 1 column 20 of the JSON data (the error changes depending what i change in the syntax). I’ve tried single quotes outside of the curly brackets, inside instead of double quotes, spaces, no spaces, etc.

EDIT: nevermind. This { "friendly_name" : "Front Door Opened" } worked, which i swear i had already tried.

That’s why I switched to using a input_boolean and a log message. I didn’t have to be at home to physically do anything :slight_smile:

Sweet. Got it running. Changing the entity variable to e did the trick. Now to start practicing trying to understand and write some code on my own. Thanks again for the help.

1 Like

Sure thing,
Anytime we can help let us know.

@Mike_D @turboc

You’re both totally right about entity erroring out. I updated the easy version! You’ll see the complex version of the app (the one I actually tested…) uses entity_name instead of entity. There’s a lesson in here that you shouldn’t use these so-called “reserved” keywords as your variable names, otherwise it can cause some headache when trying to debug. In AppDaemon, entity is one of those such keywords!

Good catch, and thanks for the support!

And to make it worse, the lazy author never listed all of the reserved keywords!

I have an item on my list to rename reserved words to things starting with an underscore, e.g._entity to prevent these kinds of issues in future.

While we have you Aimc,
In SupahNoob’s code there is a run_in(self.callback,seconds=ttl) or something like that. Is seconds being recognized as being the second named arguement, or is it somehow coming across as a kwarg because of the “=”? I’m thinking it’s being seen as the second named arguement since there isn’t a default for it in the appapi code and it’s not erroring out.