Loop through variable passed in from apps.yaml

Hi!

I’m trying to create an app that starts a timer for any given light when it is turned on, and if it hasn’t been turned off for 4 hours, automatically turns off.

The below code works for a single entity supplied from apps.yaml, but when I provide a comma separated list of additional entities, I get an error that the device doesn’t exist in Home Assistant (it looks like it sees the 2 devices as 1 device).

I have 2 questions:

  • How can I supply more than 1 device as a self.args argument passed in from apps.yaml?

  • More preferred - How can I point to a group of lights/switches in HA from apps.yaml and have them passed in one at at time as they turn on? If I supply an actual group, such as group.kitchen_area, then it seems to only set the timer at the group level, not at the device level so any light turning on would trigger the off timer to start for all devices in the kitchen area.

A for loop seems ideal here, something like:
for devs in group.kitchen_area:
self.turn_off[devs]

But I’m not figuring out how to get there :slight_smile:

Thanks!
-Chad

relevant part of apps.yaml:

 Timer Lights Off:
  module: auto_lights_off
  class: TimerLightsOff
  #lights: light.kitchen_sink_light
  #lights: group.kitchen_area
  lights: light.dining_room_lights,light.kitchen_sink_light,switch.kitchen_lights
  delay: 14400 # 4 hours

auto_lights_off.py

import appdaemon.appapi as appapi

#########################################################
#                                                       #
#   Launches a timer to turn off a light 4 hours        #
#   after it has been turned on. This gets disabled     #
#   when party mode is enabled.                         #
#                                                       #
#########################################################

class TimerLightsOff(appapi.AppDaemon):

  def initialize(self):
    self.listen_state(self.start_timer, self.args["lights"], old = "off")

  def start_timer(self, entity, attribute, old, new, kwargs):
    self.run_in(self.lights_out, self.args["delay"], device = self.args["lights"])

  def lights_out(self, kwargs):
    self.log("Timer for {} seconds is up on {}, turning off.".format(self.args["delay"],self.args["lights"]))
    self.turn_off(self.args["lights"])

I think the simplest approach for this is to create a different app for each light, rather than to try and combine them all in to one app

kitchen_sink_light:
  module: auto_lights_off
  class: TimerLightsOff
  lights: light.kitchen_sink_light
  delay: 14400 # 4 hours

dining_room_lights:
  module: auto_lights_off
  class: TimerLightsOff
  lights: group.dining_room_lights
  delay: 14400 # 4 hours

But if you want to process a list of devices, I use the self.split_device_list() api call


    def turn_everything_off(self, kwargs):
        if self.get_state(self.args["sensor"]) == "off":
            for item in self.split_device_list(self.args["items"]):
                self.turn_off(item)

Which goes through an items argument like

lounge_end_of_day:
  module: end_of_day
  class: EndOfDay

  items: group.lounge_lights,switch.tv
  sensor: group.lounge_motion_sensors

You just have to be careful of spaces in the list of devices - they are not stripped out automatically.

Since the introduction of yaml config files there is a cleaner way to do this using native yaml lists in the args:

Timer Lights Off:
  module: auto_lights_off
  class: TimerLightsOff
  lights: 
    - light.dining_room_lights
    - light.kitchen_sink_light
    - switch.kitchen_lights
  delay: 14400 # 4 hours

Then in the code:

 def turn_everything_off(self, kwargs):
        for item in self.args["lights"]:
            self.turn_off(item)

Edit: But I agree with @gpbenton that it’s easier to just configure multiple apps.

Thanks for the responses and ideas!

I’m hoping to not have separate apps because I currently have about 50 devices I want to use this on, and will be growing that list with about 20 more in the next few months.

I do like the yaml approach as it gives me the most control over what is included, but to have to list them out there entity-by-entity seems duplicative and redundant since I already create groups in HA using similar syntax.

The split_device_list looks very promising and looks like it will bridge the yaml approach while still using my groups I’ve already created. I do have some groups that reduce those 50 devices down to maybe 20 entities (group.kitchen_area instead of the 5 separately controllable lights in there for example) so a comma separated list of 20 mixed entities (lights, switches, and groups of either) is manageable and would probably be more ideal for user experience overall.

Appreciate all of the info! I’ll report back to let you know how things go.

-Chad

So far I’m getting:

WARNING Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.5/site-packages/appdaemon/appdaemon.py", line 763, in process_message
    process_state_change(data)
  File "/srv/homeassistant/lib/python3.5/site-packages/appdaemon/appdaemon.py", line 679, in process_state_change
    cdevice, centity = callback["entity"].split(".")
ValueError: too many values to unpack (expected 2)

in my error log, and in my daemon log:

WARNING Timer Lights Off: Entity light.kitchen_sink_light,light.dining_room_lights not found in Home Assistant

I’m testing initially with just 2 light device entities figuring once I can get it working with 2, I can get it working with 20, or 50, or 70, or … :slight_smile:

I’ll keep plugging away, just wanted to post the actual errors for future reference.

Well then, why don’t you read the groups using get_state() then - that would seem to do what you want. Maybe something like:

def turn_everything_off(self, kwargs):
       for entity in self.get_state("group.all_lights", attribute="all")["attributes"]["entity_id"]:
            self.turn_off(entity)
2 Likes