**kwargs question (calling from another class + internal recursive call)

Hi,

I have couple of zwave plugs that do not always respond on first hit (on or off). so I’ve decided to build a utility function with internal timer to retry till the state changes. Just that the recursive call on the run_in fails with this error:

TypeError: turn_switch() takes 1 positional argument but 2 were given

this is the calling line on a class test:

self.utils.turn_switch(entity_id = "switch.light_kitchen_closet", action = "on")
#or
self.utils.turn_switch(entity_id = "switch.light_kitchen_closet", action = "off")

and this is the utility function on class utility (on a different module):

    def turn_switch(self, **kwargs):
        state = self.get_state(kwargs["entity_id"])
        if kwargs["action"] == "on" and state != kwargs["action"]:
            self.turn_on(kwargs["entity_id"])
            self.run_in(self.turn_switch, 5, entity_id=kwargs["entity_id"], action=kwargs["action"])   
        elif kwargs["action"] == "off" and state != kwargs["action"]:
            self.turn_off(kwargs["entity_id"])
            self.run_in(self.turn_switch, 5, entity_id=kwargs["entity_id"], action=kwargs["action"])   

so when AD tries to run the turn_switch after 5 seconds, it fails with the above error.

I have close to none experience with python and I don’t have a clue what am I doing wrong. Any help will be appreciated.
Thanks.

[the edit holds mentioning that the code above is on different classes and also updated the subject to a better one]

Try

self.run_in(self.turn_switch, 5, kwargs)

Hi,

In that case the error I’m getting is:

File "/config/appdaemon/apps/not in use/test.py", line 20, in initialize
    self.utils.turn_switch(entity_id = entityid, action = "off")
  File "/config/appdaemon/apps/utils.py", line 42, in turn_switch
    self.run_in(self.turn_switch, 5, kwargs)
TypeError: run_in() takes 3 positional arguments but 4 were given

Thanks!

Define turn_switch as this (without the **):

def turn_switch(self, kwargs):

I can’t say I understand how exactly AppDaemon deals with these callbacks, but whenever some of this run_* functions are involved in the example apps, like here in commute.py, then it’s defined like that without the asterisks (see line 24 and 26). I also use it in my apps this way and it seems to work.

Also I see you’re sometimes calling/mentioning self.utils.turn_switch but then use self.turn_switch in the run_in() call. Make sure you are calling the correct function and not mixing two of them up.

run in passes kwargs as kwargs to the callback not **kwargs if I recall correctly.

I believe this should work (I optimized it a bit too to reduce redundancy):

    def turn_switch(self, kwargs):
        action = kwargs.get('action')
        entity_id = kwargs.get('entity_id')
        state = self.get_state(entity_id)
        if state != action:
           if action == 'on':
               self.turn_on(entity_id)
           else:
               self.turn_off(entity_id)
           self.run_in(self.turn_switch, 5, entity_id=entity_id, action=action)           
2 Likes

@attila_ha, @petro,

Without the **, the following error pops:

2019-06-21 17:55:00.785292 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/appdaemon/appdaemon.py", line 1581, in init_object
    init()
  File "/config/appdaemon/apps/not in use/test.py", line 17, in initialize
    self.utils.turn_switch(entity_id = entityid, action = "on")
TypeError: turn_switch() got an unexpected keyword argument 'entity_id'

Looking at my original post I see I failed to mention that I’m using a utility class on a different module for the timer logic (that is why I have self.utils.turn_switch on the calling class and self.turn_switch on the recursive call within the utility class. I will update the post now.

With @petro suggestion. Try this:

self.utils.turn_switch({"entity_id": "entityid", "action": "on"})

I tested it in a similar setup and it worked for me.
The self.turn_switch method expects only 1 positional argument “kwargs”, however in your call self.utils.turn_switch(entity_id = entityid, action = "on") you provide two positional arguments, once “entity_id” and once “action”. With my method you just convert the two positional arguments into one by putting them into a dictionary.

By the way, I highly suggest you to look up “kwargs” and “args” in the python documentation. It will help you understand why you got these errors and how you can fix them.

1 Like

Thanks. Using the dictionary makes sense (taking in to account the nature of kwargs), though the samples on AD docs do not use it as such and hence my trials made sense to me. Unfortunately, trying to implement your suggestion still fails for me with this error (it fails on the calling to run_in):

2019-06-22 10:54:37.710246 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/appdaemon/appdaemon.py", line 1581, in init_object
    init()
  File "/config/appdaemon/apps/not in use/test.py", line 12, in initialize
    self.utils.turn_switch({"entity_id": entityid, "action": "off"})
  File "/config/appdaemon/apps/utils.py", line 17, in turn_switch
    self.run_in(self.turn_switch, 5, {"entity_id": entity_id, "action": action})
TypeError: run_in() takes 3 positional arguments but 4 were given
2019-06-22 10:54:37.711107 WARNING AppDaemon: ------------------------------------------------------------

@Burningstone - I’ll appreciate if you can help me identify my current mistake.

For one’s convenience, this is the full code of the 2 classes I’m currently using (implementing all previous posts suggestions) - if one’s wants to test, just create the two files and define the apps in apps.yaml:

#apps.yaml
utils:
  module: utils
  class: utils
  
test:
  module: test
  class: test
#utils.py
import appdaemon.plugins.hass.hassapi as hass

class utils(hass.Hass):

    def initialize(self):
        return
           
    def turn_switch(self, kwargs):
        entity_id = kwargs["entity_id"]
        action = kwargs["action"]
        state = self.get_state(entity_id)
        if state != action:
            if action == "on":
                self.turn_on(entity_id)
            else:#if action == "off":
                self.turn_off(entity_id)
            self.run_in(self.turn_switch, 5, {"entity_id": entity_id, "action": action})          
#test.py
import appdaemon.plugins.hass.hassapi as hass

class test(hass.Hass):

    def initialize (self): 
        self.utils = self.get_app('utils')
        
        entityid = "switch.light_kitchen_closet_s1" #replace with your entity name
        state = self.get_state(entityid)
        
        if state == "on":
            self.utils.turn_switch({"entity_id": entityid, "action": "off"})
        else:#if state == "off":
            self.utils.turn_switch({"entity_id": entityid, "action": "on"})

You only need to change it to a dictionary in the lines where you call the method from outside, not in the self.run_in(...)

1 Like

yes, just found it at this moment :slight_smile:

the run_in should be:
self.run_in(self.turn_switch, 5, entity_id=entity_id, action=action)

and all works now.

Thank you very much!

1 Like