Run_in is the worst part of appdaemon

Seriously every time I have to try to use this function I groan, because I know it’s going to be a 3hr ordeal trying to get it to work.

Why can’t I do something like this?

self.run_in(self.turn_off, 60, "light.mylight")

or even this?

self.run_in(self.turn_off, 60, entity_id = "light.mylight")

These are EXACTLY the sort of things people actually want to do as future events.

I can get it to work with this wrapper function like in the docs, but why require the wrapper? Also given the function below, this works:

self.run_in(self.future_off, 180, entity="light.bedside_lights")

but this doesn’t:

self.run_in(self.future_off, 180, entity=self.args["mylight"])
  def future_off(self, kwargs):
    entity=kwargs["entity"]
    self.log("Turning off {}".format(entity))
    self.turn_off(entity)

The whole thing is just clumsy, but then again it is python…

There are trade offs between convenience and flexibility. I have done some amazing things with AppDaemon, it has brought threading to a novice coder like myself, and I’m glad it is an open and flexible platform.

1 Like

um… ok. I’ve done plenty of cool stuff with appdaemon as well, but that is neither here nor there when it comes to this function being way too primitive, and being incredibly poorly documented.

from the docs:

self.handle = self.run_in(self.run_in_c, 10)
self.handle = self.run_in(self.run_in_c, , 5, title = "run_in5")

what does the blank “, ,” even mean there? Is it a typo?
Why the strange nomenclature for this function where you provide the function_name, delay_time, and only then the args?

What would seem more typical would be:

self.run_in(self.turn_off("light.mylight"), 20)

and lets not forget the worthless error messages when you wrap anything in run_in. Thanks for providing an actual line number python. And, for the record 0 positional arguments were given, but whatever.

2019-09-05 21:58:05.018637 WARNING AppDaemon: ------------------------------------------------------------
2019-09-05 21:58:05.018897 WARNING AppDaemon: Unexpected error in worker for App livingroom_av:
2019-09-05 21:58:05.019059 WARNING AppDaemon: Worker Ags: {'name': 'livingroom_av', 'id': UUID('6ee454ae-67df-4c2e-8510-47af6d4e99e9'), 'type': 'timer', 'function': <bound method AutoAV.select_sources of <auto_av.AutoAV object at 0x7f43245f75f8>>, 'kwargs': {}}
2019-09-05 21:58:05.019188 WARNING AppDaemon: ------------------------------------------------------------
2019-09-05 21:58:05.019454 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 586, in worker
    funcref(self.sanitize_timer_kwargs(app, args["kwargs"]))
TypeError: select_sources() takes 1 positional argument but 2 were given

2019-09-05 21:58:05.019588 WARNING AppDaemon: ------------------------------------------------------------

oh ok… well it seems I have to declare my function with:

def select_sources(self, kwargs):

that is self, kwargs if I want to use it with run_in. If I don’t want to use it with run_in I have to either run it with dummy kwargs:

self.select_sources(kwargs)
or
self.run_in(self.select_sources, 0)

Can you please show the code, which gives you this error? I’m almost sure that your code provides 2 positional arguments and not that python spits out wrong errors.

Can you please also show the config yaml for the below app? Because this works fine for me.

self.run_in(self.future_off, 180, entity=self.args["mylight"])
  def future_off(self, kwargs):
    entity=kwargs["entity"]
    self.log("Turning off {}".format(entity))
    self.turn_off(entity)

The wrapper allows you to do more advanced stuff than turning on/off a light. Let’s look at an example with the implementation you suggest:

Suppose you want to just turn off a light

self.run_in(self.turn_off, 60, entity_id = "light.mylight")

Now let’s suppose you want to also log something in addition to turning off the light.

self.run_in(self.future_off, 180, entity_id=self.args["mylight"])
  def future_off(self, kwargs):
    entity=kwargs["entity_id"]
    self.log("Turning off {}".format(entity))
    self.turn_off(entity)

How should the run_in method know that in the first case it should turn off entity_id and in the second case it should hand over the entity_id to the callback? Like this, you would need to include all possible methods with all their required arguments inside the run_in wrapper.

1 Like

It looks like run_in passes both the function_name and the timeout too the call back as positional arguments?

This is not correct. the callback and the delay_time are args, what you provided afterwards are kwargs (keyword arguments).

Don’t wanna be offensive, but why bash on appdaemon, when you don’t know Python?

1 Like

I don’t understand your question. They are both positional arguments, of course the run_in method passes all the given arguments to the callback.

I just wanna do what I wanna do, I’m not really that interested in python and the more I use it the less I am interested. Give me ruby any day.

as for run_in, I guess I still just don’t get why run_in would pass both positional arguments to the call back when one of them is the name of the callback itself, and the other is the number of seconds in the future to execute?
Neither of these are useful to my callback so I don’t quite understand why its getting them.

And to explicitly answer this question, appdaemon provides a lot of builtins that work in a particular way and do exactly what you want them to do. run_in seems syntactically and functionally different than most of the provided functions.

Yes it provides a lot of builtins that work, but it is still python. In my opinion you need to understand the basics of python otherwise it will lead to frustrating situations like this one.

I don’t know what is so hard to understand about the run_in method.

self.run_in(callback you want to execute after delay, delay, additional keyword arguments you want to provide to the callback)

E.g.

self.run_in(turn_light_off_delayed_cb, 60, name = “kitchen lights”, entity_id =“light.mylight”)

turn_light_off_delayed_cb is the callback you want to call
60 is the desired delay
name and entity_id are additional keyword arguments

def turn_light_off_delayed_cb (self, kwargs):
    name = kwargs["name"]
    entity=kwargs["entity_id"]
    self.log("Turning off {}".format(name))
    self.turn_off(entity)
1 Like

Well your example is basically what I ended up doing.

Tthere are 2 cases that I always seem to run into-

One is where I’ve already written a function where I want to run it both with and without run_in. I seem to have to modify the function definition to use only (self, kwargs) for it to work with run_in. I can’t define a function as my_func(self), or my_func(self, **kwargs), or my_func(self, arg, arg2).

The second is the example I gave above where I want to run the same function both as:

self.run_in(turn_light_off_delayed_cb, 60, name = “kitchen lights”, entity_id =“light.mylight”)
and:
self.run_in(turn_light_off_delayed_cb, 60, name = “kitchen lights”, entity_id=self.args["kitchen"])

I still don’t quite understand why the latter example doesn’t work.

If you want to run the same callback with and without run_in you have to do it this way or create another callback for the delayed execution, which will call the other function.

Can you show the config yaml for the below? This works for me without issues, I assume you config is wrong.

self.run_in(turn_light_off_delayed_cb, 60, name = “kitchen lights”, entity_id =“light.mylight”)
and:
self.run_in(turn_light_off_delayed_cb, 60, name = “kitchen lights”, entity_id=self.args["kitchen"])

really that worked for you? interesting. Here are the relevant pieces, starting with the config.yml

kitchen_rear:
  module: auto_lights
  class: AutoLights
  sensor: binary_sensor.motion_kitchen_rear
  hold: light.kitchen_hold
  constrain_input_select: input_select.holman_mode,Present,Guest
 

    if "hold" in self.args:
      self.turn_on(self.args["hold"], brightness_pct=0)
      self.run_in(self.future_off, 10, entity=self.args["hold"])

  def future_off(self, kwargs):
    entity=kwargs["entity"]
    self.log("The Future is here {}".format(entity))
    self.turn_off(entity)

Errror:

2019-09-06 00:10:34.001992 WARNING AppDaemon: Args: {'name': 'kitchen_rear', 'id': UUID('bbc90e5e-862e-4a95-8e10-dcaa6e250831'), 'callback': <bound method AutoLights.future_off of <auto_lights.AutoLights o
bject at 0x7f12dc4ffc18>>, 'timestamp': 1567753834, 'interval': 0, 'basetime': 1567753834, 'repeat': False, 'offset': 0, 'type': None, 'kwargs': {'entity': 'light.kitchen_hold'}}
2019-09-06 00:10:34.002128 WARNING AppDaemon: ------------------------------------------------------------
2019-09-06 00:10:34.002410 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 821, in exec_schedule
    "attribute": args["kwargs"]["attribute"],
KeyError: 'attribute'

I’m currently at work so I’m unable to test. I can do a test in the evening if you like.
Could you try like this:

entity_id = self.args.get("hold")

if entity_id:
      self.turn_on(entity_id, brightness_pct=0)
      self.run_in(self.future_off, 10, entity=entity_id)

  def future_off(self, kwargs):
    entity=kwargs["entity"]
    self.log("The Future is here {}".format(entity))
    self.turn_off(entity)
1 Like

if I do it a bit differently with:

self.run_in(self.future_off, 10, entity=self.args.get("hold"))

The error changes slightly:

2019-09-06 00:27:14.002494 WARNING AppDaemon: Args: {'name': 'kitchen_rear', 'id': UUID('b740b37b-5296-4fff-8440-4c63da548d1d'), 'callback': <bound method AutoLights.future_off of <auto_lights.AutoLights object at 0x7f12dd5f92b0>>, 'timestamp': 1567754834, 'interval': 0, 'basetime': 1567754834, 'repeat': False, 'offset': 0, 'type': None, 'kwargs': {'entity': 'light.kitchen_hold'}}
2019-09-06 00:27:14.002657 WARNING AppDaemon: ------------------------------------------------------------
2019-09-06 00:27:14.002989 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 821, in exec_schedule
    "attribute": args["kwargs"]["attribute"],
KeyError: 'attribute'

Tried it your way, same issue:

entity_id=self.args.get("hold")
self.run_in(self.future_off, 10, entity=entity_id)
2019-09-06 00:31:23.002691 WARNING AppDaemon: Unexpected error during exec_schedule() for App: kitchen_rear
2019-09-06 00:31:23.002956 WARNING AppDaemon: Args: {'name': 'kitchen_rear', 'id': UUID('19d437ea-82eb-4940-84c1-85d686946eed'), 'callback': <bound method AutoLights.future_off of <auto_lights.AutoLights object at 0x7f12dd5f92b0>>, 'timestamp': 1567755083, 'interval': 0, 'basetime': 1567755083, 'repeat': False, 'offset': 0, 'type': None, 'kwargs': {'entity': 'light.kitchen_hold'}}
2019-09-06 00:31:23.003169 WARNING AppDaemon: ------------------------------------------------------------
2019-09-06 00:31:23.003543 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 821, in exec_schedule
    "attribute": args["kwargs"]["attribute"],
KeyError: 'attribute'

Ohhhhhh!!!

aimc
Andrew Cockburn

Apr '17

Yes, you are running into a collision with one of the parameter names - I really should document these and/or change them to less obvious values.

For now, change your parameter entity to something else and it should fix the issue.

Both of these work now:

self.run_in(self.future_off, 10, myentity=self.args.get("hold"))
self.run_in(self.future_off, 10, myentity=self.args["hold"])
  def future_off(self, kwargs):
    myentity=kwargs["myentity"]
    self.log("The Future is here {}".format(myentity))
    self.turn_off(myentity)

Can you please add self.log(kwargs) to the callback like this:

def future_off(self, kwargs):
    self.log(kwargs)
    entity=kwargs["entity"]
    self.log("The Future is here {}".format(entity))
    self.turn_off(entity)

And show what the appdaemon log spits out? Did you try the code I suggested before?