Error with Appdaemon - split_device_list

Hi,
REfactoring some of my apps as I get more of handle on AppDaemon. Can across this error (appdaemon2/Hassio)

2018-03-08 06:24:17.644505 WARNING ------------------------------------------------------------
2018-03-08 06:24:17.645813 WARNING Unexpected error:
2018-03-08 06:24:17.647023 WARNING ------------------------------------------------------------
2018-03-08 06:24:17.649548 WARNING Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 837, in check_config
    new_config[name]["module"], new_config[name]
  File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 583, in init_object
    conf.objects[name]["object"].initialize()
  File "/config/appdaemon/apps/auto_turn_off.py", line 23, in initialize
    for light in self.split_device_list(self.args["lights"]):
  File "/usr/lib/python3.6/site-packages/appdaemon/appapi.py", line 84, in split_device_list
    return list_.split(",")
AttributeError: 'list' object has no attribute 'split'

The App Config and App

    auto_turnoff_in_five:
      module: auto_turn_off
      class: AutoTurnOffLights
      lights: 
        - switch.laundry_light
        - switch.garage_light
        - switch.pantry_light
      delay: 300 

    class AutoTurnOffLights(appapi.AppDaemon):

      def initialize(self):
        self.handle = None
        if "lights" in self.args:
          for light in self.split_device_list(self.args["lights"]):
            self.listen_state(self.switchDetected, light, new="on")

      def switchDetected(self, entity, attribute, old, new, kwargs):
        if new == "on":
            self.log("{} is on for {} seconds".format(
                self.friendly_name(entity), self.args["delay"]))
            self.cancel_timer(self.handle)
            self.handle = self.run_in(self.light_off, delay)
      
      def light_off(self, entity,kwargs):
        if "light" in self.args:
            self.log("Turned {} off".format(entity)
            self.turn_off(entity)
            
      def cancel(self):
        self.cancel_timer(self.handle)

I use this


        if "entities" in self.args:
            for item in self.args["entities"]:
                entity = item["entity"]
                node_id = self.get_state(entity, attribute="node_id")
                parameter = item["parameter"]

without the split_device_list and it works fine.

from the documents:

split_device_list() will take a comma separated list of device types (or anything else for that matter) and return them as an iterable list

it takes a comma separated list, and not an actual list.

but you dont need it anyway (the function is more to retrieve entities from a group)
this will work also:

for light in self.args["lights"]:

HI,
Seem to be part the way there, but stuck on the next step - Turning the lights off after the delay

   class AutoTurnOffLights(appapi.AppDaemon):

  def initialize(self):
    self.handle = None
    self.cancel_timer(self.handle)
    for light in self.args["lights"]:
        self.listen_state(self.switch_detected, light, new="on")

  def switch_detected(self, entity, attribute, old, new, kwargs):
    if new == "on":
        self.log("{} is on for {} seconds".format(self.friendly_name(entity), self.args["delay"]))
        self.handle = self.run_in(self.light_off(entity,kwargs), 60)
  
  def light_off(self, entity ,kwargs):
    self.log("Turned {} off".format(entity) )
    self.turn_off(entity)

auto_turnoff_in_five:
  module: auto_turn_off
  class: AutoTurnOffLights
  lights: 
    - switch.laundry_light
    - switch.garage_light
    - switch.pantry_light
  delay: 60 

When I turn the light on, it turns off straight away. I figure it has something to do with the with run_in being cancelled when it is called for the next light in the list, but I am at a loss as to how I can match the listen call back to to the run_in one. Or am I on the wrong track

thanks in advance

the run_in starts immediatly because you try to give a function in stead of a callback.

it should be something like:

self.handle = self.run_in(self.light_off, 60,entity = entity, kwargsdict=kwargs)

then you can find the values in the callback like

var = kwargs["entity"]
var2 = kwargs["kwargsdict"]["a kwarg from the listen state"]

Rene,
Thanks for this. But still confused and tried the following and it didn’t work. Sorry I am a Noob to python.

def switch_detected(self, entity, attribute, old, new, kwargs):
    if new == "on":
        self.log("{} is on for {} seconds".format(self.friendly_name(entity), self.args["delay"]))
        self.handle = self.run_in(self.light_off, 60,entity = entity, kwargsdict=kwargs)
  
  def light_off(self, entity ,kwargs):
    var = kwargs["entity"]
    self.log("Turned {} off".format(vary) )
    self.turn_off(var)

thats a callback so it should be

def light_off(self, kwargs):

and that:

.format(vary)

was i typo i think :wink: it should be just var

Thanks, tried this but still stuck

The error that appears when i turn on one of the light is below. I can see that the entity is correct in the dictionary inside kwargs, but not sure why this error is appearing. It stops the app and doesn’t turn the light off

018-03-09 17:43:53.150628 WARNING ------------------------------------------------------------
2018-03-09 17:46:33.143087 WARNING ------------------------------------------------------------
2018-03-09 17:46:33.144381 WARNING Unexpected error during exec_schedule() for App: auto_turnoff_in_five
2018-03-09 17:46:33.145699 WARNING Args: {'name': 'auto_turnoff_in_five', 'id': UUID('e0e7b729-2e14-4744-9ffb-3f0f47589fca'), 'callback': <bound method AutoTurnOffLights.light_off of <auto_turn_off.AutoTurnOffLights object at 0x74c762d0>>, 'timestamp': 1520577993, 'interval': 0, 'basetime': 1520577993, 'repeat': False, 'offset': 0, 'type': None, 'kwargs': {'entity': 'switch.pantry_light', 'kwargsdict': {}}}
2018-03-09 17:46:33.146766 WARNING ------------------------------------------------------------
2018-03-09 17:46:33.148524 WARNING Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 301, in exec_schedule
    "attribute": args["kwargs"]["attribute"],
KeyError: 'attribute'

2018-03-09 17:46:33.149831 WARNING ------------------------------------------------------------
2018-03-09 17:46:33.151798 WARNING Scheduler entry has been deleted
2018-03-09 17:46:33.152679 WARNING ------------------------------------------------------------


   import appdaemon.appapi as appapi
import time
#
# App to turn lights off after a delay

# Args:
#lights : entity to turn off when when delay reached
# delay: amount of time after turning on to turn off again. If not specified defaults to 60 seconds.

class AutoTurnOffLights(appapi.AppDaemon):

  def initialize(self):
    self.handle = None
    self.cancel_timer(self.handle)
    for light in self.args["lights"]:
        self.listen_state(self.switch_detected, light, new="on")

  def switch_detected(self, entity, attribute, old, new, kwargs):
    if new == "on":
        #set delay to 10 for debugging
        #delay = int(self.args["delay"])
        delay = 10
        self.handle = self.run_in(
            self.light_off, delay, entity=entity, kwargsdict=kwargs)

  
  def light_off(self ,kwargs):
    var = kwargs["entity"]
    self.log("{} and".format( var))
    self.turn_off(var)

there are a few more things in it i can comment on.

  1. it makes no sense to cancel an unset timer in the initialize function.
  2. you check for on in the listen_state, so no use to check it again in the callback
  3. what you dont use you should not give ahead.

so what it comes down to is this:

# Args:
#lights : entity to turn off when when delay reached
# delay: amount of time after turning on to turn off again. If not specified defaults to 60 seconds.

class AutoTurnOffLights(appapi.AppDaemon):

  def initialize(self):
    for light in self.args["lights"]:
        self.listen_state(self.switch_detected, light, new="on")

  def switch_detected(self, entity, attribute, old, new, kwargs):
      #set delay to 10 for debugging
      #delay = int(self.args["delay"])
      delay = 10
      self.handle = self.run_in(self.light_off, delay, entity_name=entity)

  def light_off(self ,kwargs):
    var = kwargs["entity_name"]
    self.log("{} and".format( var))
    self.turn_off(var)

i used entity_name to make sure you dont use existing vars from AD.

1 more remark: from the use off appapi.AppDaemon i suspect you use appdaemon 2
but i see you have python 3.6 installed.
because of recent problems and developments i advice you to upgrade to the appdaemon 3 beta version.

Wouldn’t all his code only handle 1 listener for all the devices? I think he’d also want to build in a listener to handle every switch individually.

Something like

class AutoTurnOffLights(appapi.AppDaemon):

  def initialize(self):
    self.handles = {}
    for light in self.args["lights"]:
        self.listen_state(self.switch_detected, light, new="on")

  def switch_detected(self, entity, attribute, old, new, kwargs):
      #set delay to 10 for debugging
      #delay = int(self.args["delay"])
      try:
        self.cancel_timer(self.handles[entity])
      except KeyError:
        # This doesn't matter that the timer doesn't exist.  If it did, we should cancel it.
        self.log("{} timer does not exist".format(entity))
      delay = 10
      self.handles[entity_name] = self.run_in(self.light_off, delay, entity_name=entity)

  def light_off(self ,kwargs):
    var = kwargs["entity_name"]
    self.log("{} and".format( var))
    self.turn_off(var)

yeah oke, a new listen_state would then cancel the run_in from before.
(that is if the self.handles[entity_name] is changed to entity :wink: )

1 Like

Thank you both this works great now. An I think I understand this lot more. I am going to have a crack at including different delays for each light, so i can merge all my auto light shut offs into one app.

Rene - I was holding off on AppDaemon3 while still in beta, but you point this out may explain some other odd behaviour I have seen, so will start the upgrade process. I believe in hassio I can run both 2 and 3 in parallel, at least to I have moved all the apps across.

thanks for your help

i would absolutely not advice to run them side by side.
thats asking for trouble.

AD 3 is in beta, but thats actually because andrew is extra carefull.
he made a big change and the lauches it as beta.
at the moment i can really say that AD 3 is as stable as AD 2 was, but there were always small things that could be better, or updated. thats why he has regular new releases.
i think AD 3 is now in the same area as AD 2 was.