Run_in is the worst part of 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?

Did you see the post I pasted from aimc above? That’s my issue. “entity” is a parameter name and can’t be used. All working as expected now.

Wow, that was frustrating, as anybody from the future reading this thread will probably deduce.

Ah my bad, forgot about this, I’m sorry.

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

This will probably give you an error if “hold” is not in your yaml config, because the get method will provide “None” if it can not find the “hold” in your config.

You can use lambda of Python:

self.run_in(lambda _: self.turn_off("light.mylight"), 60)
1 Like