Restoring lights to previous state (e.g. after processing motion sensor)

Hi,

I recently started exploring the AppDaemon possibilities as I found YAML automations to be too complex (or it might be just me) for some (simple) tasks. This post is to share my approach for those who are interested:)

Scenario:

  • Lights (carport in this case) are in some state (dimmed at certain times when dark or simply off)
  • Motion sensor gets triggered; lights need to be switched on bright
  • After some timeout there is no motion and lights need to go back to previous state (being either dimmed or off)

Automation in AppDaemon (incl. self explaining comments):

import appdaemon.plugins.hass.hassapi as hass

# -------------------------------------------------------------------------------------------------------------------------------
# Switch carport lights bright when motion is detected and restore the lights to previous state after a given time out
# -------------------------------------------------------------------------------------------------------------------------------

class motion_detection(hass.Hass):
  
  def initialize(self): 
    self.handle = None
    # Initialize carport lights to default (off) 
    self.global_vars["CarportLightStatus"] = "off"
    
    # Whenever it's dark AND motion is detected (on) update the lights
    if self.get_state("binary_sensor.dusk_sensor") == "off":
      self.listen_state(self.motion, self.args["sensor"], new="on")
  
  
  # Switch light on and set a timer for the lights to be automatically switched off again
  def motion(self, enitity, attribute, old, new, kwargs):    
    
    # Check and store the current light status (off, dimmed, bright)
    tmp = self.get_state(self.args["lights"], attribute="brightness")
    if tmp is None:
      self.global_vars["CarportLightStatus"] = "off"
    elif int(tmp) < 100:
      self.global_vars["CarportLightStatus"] = "dimmed"
    self.log("Light status before motion is {}".format(self.global_vars["CarportLightStatus"]))

    # Check whether a timeout has been provided otherwise use 60 seconds as default
    if "timeout" in self.args:
      timeout = self.args["timeout"]
    else:
      timeout = 60

    # Switch on the lights
    self.turn_on(self.args["lights"], brightness_pct = self.args["bright_max"])
    
    # Reset timer (if any) and switch the lights back to previous state after passing timeout
    self.cancel_timer(self.handle)
    self.handle = self.run_in(self.lights_restore, timeout)

  
  # Switch lights back to previous state (dimmed, off)
  def lights_restore(self, kwargs):

    if self.global_vars["CarportLightStatus"] == "off":
      self.turn_off(self.args["lights"])
    else:
      self.turn_on(self.args["lights"], brightness_pct = self.args["bright_min"])

And the following app config:

my_app:
  module: carport_motion
  class:  motion_detection
  sensor: sensor.motion_carport
  lights: light.carport_spots
  bright_max: 75
  bright_min: 2
  timeout: '180'

Let me know your thoughts. I’m sure there is (plenty of) room for improvement :).

Oh BTW, the carport lights being switched on (dimmed) and off is currently still done by YAML so that’s one for a starter…

1 Like
    if self.get_state("binary_sensor.dusk_sensor") == "off":
      self.listen_state(self.motion, self.args["sensor"], new="on")

this is a wrong setup.
when during the startup from appdaemon the dusksensor is not off (but none, unknown, or on) the app will never start working untill you restart appdaemon.

1 Like

Ah… yes I might have read something about this somewhere indeed… Do you mind indicating how to overcome this? It’s indeed the case that the sensor is likely to be ‘unknown’ at start

My proposal:

  def initialize(self):
    self.handle = None
    
    # Determine the current carport light status (on or off)
    if self.get_state("light.carport_spots") != "on":
      self.global_vars["CarportLightStatus"] = "off"

    # Create a listener for motion sensor 
    self.listen_state(self.motion, self.args["sensor"], new="on")

if it is just to determine status when you start AD, and you need to know that when it starts the app i would suggest something else:

import time

  def initialize(self):
    while not self.entity_exists("light.carport_spots"):
      time.sleep(1)
    if  self.get_state("light.carport_spots") == "off":
      self.global_vars["CarportLightStatus"] = "off"

its a risky variant, because when the light is not created in HA, AD will never startup, so you need to be aware that if you change the name for example, you could get in trouble untill you change your app.
but with that little you got going on it shouldnt be a problem.
what it does is that it waits untill the light can be found, and only after that it gets the state

but why do you only set the global var when the state is off?
if the state from the light is on, the var should stay at None?

It’s indeed to determine the status but since the status is overwritten by the actual status when self.motion is called, this whole part is redundant. The simplified version just needs to initialize the variabele and set a listener.

I guess I’m trying to much to replicate my LUA scripts in Domoticz without properly using the HASSIO and AppDaemon functionality… Still learning :slight_smile:

1 Like

in that case, forget the loop and just set the var :wink:
and automating is a journey where we keep learning. :wink:

Making some improvements so that the app can be reused by the door sensors. Same story as with the motion sensor but now whenever the front door or garage door is opened.

#Todo: currently I assume that when only one sensor is provided the type is ‘on/off’ and in case more sensors are provided the type is ‘open/closed’. For now this works but is not future proof (e.g. in case two motion sensors are provided this will break).

Initialize:

  def initialize(self):
    self.handle = None
    self.global_vars["CarportLightStatus"] = None

    # Create a listener(s) for sensor(s) provided 
    if (isinstance(self.args["sensor"], str)):
      self.listen_state(self.carport_event, self.args["sensor"], new="on")
    else:
      self.sensor_entities = self.args["sensor"]
      for sensor in self.sensor_entities: 
          self.listen_state(self.carport_event, sensor, new="open")

App:

# Carport motion
app_carport_motion_detection:
  module: carport_lights
  class:  carport_light_control
  sensor: sensor.motion_carport
  lights: light.carport_spots
  bright_max: 80
  bright_min: 2
  timeout: '180'

# Carport doors 
app_carport_door_detection:
  module: carport_lights
  class:  carport_light_control
  sensor: ['sensor.door_front', 'sensor.door_garage']
  lights: light.carport_spots
  bright_max: 80
  bright_min: 2
  timeout: '60'
1 Like