This is what started as a simple App that if the light level was below a given threshold, it turned on my light switch for a specific time when motion was detected…
Then I found out that I needed to take PIR time (the time of no motion before the motion detector will be ready to activate again) into consideration as well.
Then @crondk showed off input sliders which I thought was a great way to interactively change the light threshold from my phone.
A special thanks to @aimc and @ReneTode for their excellent support in the forums!
Comments are welcome!
motionLightSensor.py
import appdaemon.appapi as appapi
class MotionLightSensor(appapi.AppDaemon):
"""Manage a switch using motion and light
Features:
- Turns on switch when motion is detected and light level is not above
configured threshold.
- Turns off switch after configured delay period
- If the motion sensor is still active after delay period, keep light on
and start a new delay period.
- Optionally, use input_slider to set light threshold and switch adjusts
directly.
Arguments:
light_level: Above this level, the switch is always off.
light_slider (optional): Slider to interactively set light_level with.
The light_level above is used as the starting value.
delay: Seconds to keep switch on after binary_sensor has triggered.
switch: Switch to control.
binary_sensor: Motion sensor.
light_sensor: Sensor used to meassure luminance.
Example configuration
In appdaemon.yaml:
motion_constrained_sensor:
module: motionLightSensor
class: MotionLightSensor
light_level: '10'
light_slider: input_slider.light_threshold
delay: '300'
switch: switch.fibaro_wall_plug_switch
binary_sensor: binary_sensor.hallway_sensor
light_sensor: sensor.aeotec_zw100_multisensor_6_luminance
In configuration.yaml:
# Define input_slider
input_slider:
light_threshold:
name: Light level treshold
min: 0
max: 20
step: 1
"""
def initialize(self):
self.log("Initializing MotionLightSensor")
self.handle = None
# Access all arguments in order to trigger error due to missing
# arguments during initialization
self.arg_switch = self.args["switch"]
self.arg_light_sensor = self.args["light_sensor"]
self.arg_binary_sensor = self.args["binary_sensor"]
self.light_threshold = float(self.args["light_level"])
self.log("Light threshold is {}".format(self.light_threshold))
# If a slider has been defined, it is used to interactively set light
# threshold
if "light_slider" in self.args:
self.log("Using {} to set light threshold".format(
self.args["light_slider"]))
self.set_state(self.args["light_slider"],
state=self.light_threshold)
self.listen_state(self.light_slider_change,
self.args["light_slider"])
self.arg_delay = self.args["delay"]
# Subscribe to all "on" events from the binary sensor
self.listen_state(self.motion_event, self.arg_binary_sensor, new="on")
# Subscribe to all events from the light sensor
self.listen_state(self.light_event, self.arg_light_sensor)
# Report light sensors current value
light_level = self.light_level()
self.log("Light level {}".format(light_level))
def light_slider_change(self, entity, attribute, old, new, kwargs):
self.log("Light threshold changed from {} to {}".format(old, new))
self.light_threshold = float(new)
light_level = self.light_level()
if (
light_level > float(new) and
light_level <= float(old)
):
self.turn_switch_off()
elif (
light_level <= float(new) and
light_level > float(old) and
self.get_state(self.arg_binary_sensor, "state") == "on"
):
self.turn_switch_on()
def light_level(self):
light_level = float(self.get_state(self.arg_light_sensor, "state"))
return light_level
def motion_event(self, entity, attribute, old, new, kwargs):
self.log("Motion detected")
if self.light_level() <= self.light_threshold:
self.log("It is dark enough")
self.turn_switch_on()
def light_event(self, entity, attribute, old, new, kwargs):
self.log("Light level changed from {} to {}".format(old, new))
if (
float(new) > self.light_threshold and
float(old) <= self.light_threshold
):
self.turn_switch_off()
elif (
float(new) <= self.light_threshold and
float(old) > self.light_threshold and
self.get_state(self.arg_binary_sensor, "state") == "on"
):
self.turn_switch_on()
def start_delay(self):
self.log("Starting delay timer")
self.cancel_timer(self.handle)
self.handle = self.run_in(self.delay_done, self.arg_delay)
def delay_done(self, kwargs):
self.log("Delay passed")
if (
self.get_state(self.arg_binary_sensor, "state") == "on" and
self.light_level() <= self.light_threshold
):
self.log("Motion sensor still active and it is dark enough,"
" starting new delay")
self.start_delay()
else:
self.log("Sensor inactive or not dark enough")
self.turn_switch_off()
def turn_switch_on(self):
self.log("Turning on {}".format(self.arg_switch))
self.turn_on(self.arg_switch)
self.start_delay()
def turn_switch_off(self, kwargs=None):
self.log("Turning off {}".format(self.arg_switch))
self.turn_off(self.arg_switch)