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)
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"]
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
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.
it makes no sense to cancel an unset timer in the initialize function.
you check for on in the listen_state, so no use to check it again in the callback
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)
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.
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.