first sleep switch implementation
This commit is contained in:
Submodule apps/ad_toolbox updated: da282058d8...7dd39e0ea9
46
apps/apps.yaml
Normal file
46
apps/apps.yaml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
debugger:
|
||||||
|
module: debugger
|
||||||
|
class: Debugger
|
||||||
|
port: 5678
|
||||||
|
|
||||||
|
motion_tracker:
|
||||||
|
module: motiontracker
|
||||||
|
class: MotionTracker
|
||||||
|
|
||||||
|
priority: 5 # this need to be initialized before app using motion tracker
|
||||||
|
|
||||||
|
mqtt_device_name: AD Motion Tracker
|
||||||
|
|
||||||
|
max_time: 30
|
||||||
|
|
||||||
|
areas:
|
||||||
|
corridor:
|
||||||
|
motion_sensors: binary_sensor.corridor_motion
|
||||||
|
hallway:
|
||||||
|
motion_sensors: binary_sensor.hallway_motion
|
||||||
|
restroom:
|
||||||
|
motion_sensors: binary_sensor.restroom_motion
|
||||||
|
|
||||||
|
sleep_switch:
|
||||||
|
|
||||||
|
module: smartswitch
|
||||||
|
class: SmartSwitch
|
||||||
|
|
||||||
|
entity: input_boolean.sleeping
|
||||||
|
|
||||||
|
trace_events: zha_event
|
||||||
|
|
||||||
|
toggle_events:
|
||||||
|
button_long_press:
|
||||||
|
event_name: zha_event
|
||||||
|
event_data:
|
||||||
|
device_ieee: '00:17:88:01:09:27:74:45'
|
||||||
|
args:
|
||||||
|
press_type: 'hold'
|
||||||
|
button: 'off'
|
||||||
|
reset_data:
|
||||||
|
device_ieee: '00:17:88:01:09:27:74:45'
|
||||||
|
args:
|
||||||
|
button: 'off'
|
||||||
|
press_type: 'long_release'
|
||||||
|
|
||||||
@@ -4,13 +4,136 @@ from ad_toolbox.eventhandler import EventHandler
|
|||||||
from ad_toolbox.expressionparser import ParsingException
|
from ad_toolbox.expressionparser import ParsingException
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MotionTracker — Multi-area motion tracking app
|
||||||
|
# =============================================================================
|
||||||
|
# Tracks the last time motion was detected in each configured area and exposes
|
||||||
|
# that information as HA sensor entities. Also optionally tracks door close
|
||||||
|
# events per area and can reset area data in response to custom HA events.
|
||||||
|
#
|
||||||
|
# Inherits all SmartObject YAML keys (see smartobject.py).
|
||||||
|
#
|
||||||
|
# YAML CONFIGURATION
|
||||||
|
# ------------------
|
||||||
|
#
|
||||||
|
# max_time: <minutes> # optional, default 120
|
||||||
|
# Global cap for the "minutes since last motion" sensors.
|
||||||
|
# Overrides the MAX_TIME constant and can itself be overridden
|
||||||
|
# per area with the area-level max_time key.
|
||||||
|
# Precedence: area max_time > app max_time > MAX_TIME constant
|
||||||
|
#
|
||||||
|
# areas:
|
||||||
|
# <area_name>:
|
||||||
|
# motion_sensors: <entity_id | list of entity_ids>
|
||||||
|
# One or more binary_sensor entities to watch.
|
||||||
|
# update_on_both_front: true # optional, default false
|
||||||
|
# When true, motion is also recorded on the falling edge
|
||||||
|
# (sensor going on→off), not only on the rising edge.
|
||||||
|
# max_time: <minutes> # optional
|
||||||
|
# Per-area cap, overrides the app-level max_time.
|
||||||
|
# door_sensor: <entity_id> # optional
|
||||||
|
# Binary sensor for the area door. When the door closes
|
||||||
|
# (on→off), the close timestamp is stored and exposed as
|
||||||
|
# a sensor (see OUTPUT ENTITIES below).
|
||||||
|
# # Shorthand form (no extra options needed):
|
||||||
|
# <area_name>: <entity_id | list of entity_ids>
|
||||||
|
#
|
||||||
|
# areas_excluded_from_last_area_with_movement:
|
||||||
|
# - <area_name>
|
||||||
|
# - ...
|
||||||
|
# Areas that should NOT update sensor.last_area_with_movement when motion is
|
||||||
|
# detected in them (e.g. utility rooms).
|
||||||
|
#
|
||||||
|
# clear_areas_events:
|
||||||
|
# <label>:
|
||||||
|
# events_to_listen:
|
||||||
|
# <key>:
|
||||||
|
# event_name: <ha_event_name>
|
||||||
|
# event_data: # optional — filter by payload
|
||||||
|
# <field>: <value>
|
||||||
|
# reset_data: # optional — reset fields after match
|
||||||
|
# <field>: <value>
|
||||||
|
# areas_to_clear:
|
||||||
|
# - <area_name>
|
||||||
|
# - ...
|
||||||
|
# When any of the listed HA events fires (and matches optional
|
||||||
|
# event_data filters), the movement timestamp for each area in
|
||||||
|
# areas_to_clear is reset to 0.
|
||||||
|
#
|
||||||
|
# OUTPUT ENTITIES (created automatically per area)
|
||||||
|
# ------------------------------------------------
|
||||||
|
# sensor.<area>_last_motion
|
||||||
|
# Minutes elapsed since last motion was detected in this area.
|
||||||
|
# Capped at max_time (default 120 min). Updated every 30 seconds
|
||||||
|
# and immediately on any motion event.
|
||||||
|
# Attribute: unit_of_measurement = "min"
|
||||||
|
#
|
||||||
|
# sensor.<area>_last_motion_time
|
||||||
|
# Unix timestamp (seconds) of the most recent motion event.
|
||||||
|
# Attribute: unit_of_measurement = "s"
|
||||||
|
#
|
||||||
|
# sensor.<area>_door_close_time (only when door_sensor configured)
|
||||||
|
# Unix timestamp (seconds) of the most recent door-close event.
|
||||||
|
# Attribute: unit_of_measurement = "s"
|
||||||
|
#
|
||||||
|
# sensor.last_area_with_movement (global)
|
||||||
|
# Name of the area where motion was most recently detected.
|
||||||
|
# Not updated for areas listed in
|
||||||
|
# areas_excluded_from_last_area_with_movement.
|
||||||
|
#
|
||||||
|
# EXAMPLE YAML
|
||||||
|
# ------------
|
||||||
|
# motion_tracker:
|
||||||
|
# module: motiontracker
|
||||||
|
# class: MotionTracker
|
||||||
|
#
|
||||||
|
# priority: 5
|
||||||
|
# mqtt_device_name: AD Motion Tracker
|
||||||
|
#
|
||||||
|
# areas:
|
||||||
|
# corridor:
|
||||||
|
# motion_sensors: binary_sensor.corridor_motion
|
||||||
|
# living_room:
|
||||||
|
# motion_sensors:
|
||||||
|
# - binary_sensor.living_room_motion_1
|
||||||
|
# - binary_sensor.living_room_motion_2
|
||||||
|
# max_time: 60
|
||||||
|
# door_sensor: binary_sensor.living_room_door
|
||||||
|
# garage:
|
||||||
|
# motion_sensors: binary_sensor.garage_motion
|
||||||
|
# update_on_both_front: true
|
||||||
|
#
|
||||||
|
# areas_excluded_from_last_area_with_movement:
|
||||||
|
# - garage
|
||||||
|
#
|
||||||
|
# clear_areas_events:
|
||||||
|
# going_to_bed:
|
||||||
|
# events_to_listen:
|
||||||
|
# evt1:
|
||||||
|
# event_name: GOING_TO_BED
|
||||||
|
# areas_to_clear:
|
||||||
|
# - corridor
|
||||||
|
# - living_room
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
class MotionTracker(SmartObject):
|
class MotionTracker(SmartObject):
|
||||||
|
|
||||||
|
# Default cap (in minutes) for the "minutes since last motion" sensor.
|
||||||
|
# Can be overridden per area with the max_time key.
|
||||||
MAX_TIME = 120
|
MAX_TIME = 120
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
super().initialize()
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Initialisation
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Entry point called by SmartObject.initialize() after all base setup.
|
||||||
|
# Initialises the dataset, creates all output entities, subscribes to
|
||||||
|
# motion/door sensors, registers clear_areas_events handlers, and
|
||||||
|
# triggers the first update pass.
|
||||||
|
def on_initialize_smart_object(self):
|
||||||
|
super().on_initialize_smart_object()
|
||||||
|
|
||||||
|
# Bootstrap dataset on first run; prune obsolete area keys on restart.
|
||||||
if self.dataset == None:
|
if self.dataset == None:
|
||||||
self.dataset = { 'areas_movement_time' : dict(), 'last_area_with_movement' : 'Unknown', 'areas_door_close_time' : dict() }
|
self.dataset = { 'areas_movement_time' : dict(), 'last_area_with_movement' : 'Unknown', 'areas_door_close_time' : dict() }
|
||||||
else:
|
else:
|
||||||
@@ -18,13 +141,21 @@ class MotionTracker(SmartObject):
|
|||||||
self.dataset['areas_movement_time'] = {area : self.dataset['areas_movement_time'][area] for area in self.args['areas'] if area in self.dataset['areas_movement_time']}
|
self.dataset['areas_movement_time'] = {area : self.dataset['areas_movement_time'][area] for area in self.args['areas'] if area in self.dataset['areas_movement_time']}
|
||||||
self.dataset['areas_door_close_time'] = {area : self.dataset['areas_door_close_time'][area] for area in self.args['areas'] if area in self.dataset['areas_door_close_time']}
|
self.dataset['areas_door_close_time'] = {area : self.dataset['areas_door_close_time'][area] for area in self.args['areas'] if area in self.dataset['areas_door_close_time']}
|
||||||
|
|
||||||
|
# input_sensors : { entity_id → area } — all subscribed motion sensors
|
||||||
|
# output_* : { area → entity handle } — created HA sensor entities
|
||||||
|
# app_max_time : app-level max_time arg (fallback before MAX_TIME constant)
|
||||||
|
# areas_max_time: { area → max_time_minutes } — per-area overrides
|
||||||
|
# update_cb_handle: handle for the recurring 30 s refresh timer
|
||||||
self.input_sensors = dict()
|
self.input_sensors = dict()
|
||||||
self.output_last_motion_sensors = dict()
|
self.output_last_motion_sensors = dict()
|
||||||
self.output_last_motion_time_sensors = dict()
|
self.output_last_motion_time_sensors = dict()
|
||||||
self.output_door_close_time_sensors = dict()
|
self.output_door_close_time_sensors = dict()
|
||||||
|
self.app_max_time = self.args.get('max_time', self.MAX_TIME)
|
||||||
|
self.areas_max_time = dict()
|
||||||
self.update_cb_handle = None
|
self.update_cb_handle = None
|
||||||
|
|
||||||
if "areas" in self.args:
|
if "areas" in self.args:
|
||||||
|
self.output_last_area_with_movement_sensor = self.create_entity("sensor.last_area_with_movement")
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
for area in self.args['areas']:
|
for area in self.args['areas']:
|
||||||
update_on_both_front = False
|
update_on_both_front = False
|
||||||
@@ -34,9 +165,11 @@ class MotionTracker(SmartObject):
|
|||||||
sensor_entities = self.args['areas'][area]['motion_sensors']
|
sensor_entities = self.args['areas'][area]['motion_sensors']
|
||||||
try: update_on_both_front = self.args['areas'][area]['update_on_both_front']
|
try: update_on_both_front = self.args['areas'][area]['update_on_both_front']
|
||||||
except KeyError: pass
|
except KeyError: pass
|
||||||
|
if 'max_time' in self.args['areas'][area]:
|
||||||
|
self.areas_max_time[area] = self.args['areas'][area]['max_time']
|
||||||
|
|
||||||
if 'door_sensor' in self.args['areas'][area]:
|
if 'door_sensor' in self.args['areas'][area]:
|
||||||
self.output_door_close_time_sensors[area] = self.create_entity(f"{area}_sensor.door_close_time")
|
self.output_door_close_time_sensors[area] = self.create_entity(f"sensor.{area}_door_close_time")
|
||||||
|
|
||||||
if not area in self.dataset['areas_door_close_time']:
|
if not area in self.dataset['areas_door_close_time']:
|
||||||
self.dataset['areas_door_close_time'][area] = 0
|
self.dataset['areas_door_close_time'][area] = 0
|
||||||
@@ -74,6 +207,13 @@ class MotionTracker(SmartObject):
|
|||||||
|
|
||||||
self.update_areas_data()
|
self.update_areas_data()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Event-driven area reset
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Callback fired by EventHandler when a matching clear_areas_events HA
|
||||||
|
# event is received. Resets movement timestamps for the configured
|
||||||
|
# areas to 0 and refreshes all sensors.
|
||||||
def on_clear_areas_event(self, event_name, event_data,entry):
|
def on_clear_areas_event(self, event_name, event_data,entry):
|
||||||
for area in self.args["clear_areas_events"][entry]['areas_to_clear']:
|
for area in self.args["clear_areas_events"][entry]['areas_to_clear']:
|
||||||
self.log(f"{area} movement data reseted by {event_name} event")
|
self.log(f"{area} movement data reseted by {event_name} event")
|
||||||
@@ -82,6 +222,13 @@ class MotionTracker(SmartObject):
|
|||||||
|
|
||||||
self.update_areas_data()
|
self.update_areas_data()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Sensor management helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Subscribe to a single binary_sensor entity for an area. Guards
|
||||||
|
# against registering the same entity twice (logs an error instead).
|
||||||
|
# update_on_both_front=True also listens on the on→off transition.
|
||||||
def register_motion_sensor(self,area,sensor_entity,update_on_both_front):
|
def register_motion_sensor(self,area,sensor_entity,update_on_both_front):
|
||||||
self.log(f"Registering sensor {sensor_entity} for area {area}")
|
self.log(f"Registering sensor {sensor_entity} for area {area}")
|
||||||
if sensor_entity not in self.input_sensors:
|
if sensor_entity not in self.input_sensors:
|
||||||
@@ -92,20 +239,38 @@ class MotionTracker(SmartObject):
|
|||||||
else:
|
else:
|
||||||
self.log_error(f"{sensor_entity} is already registered for area {self.input_sensors[sensor_entity]}")
|
self.log_error(f"{sensor_entity} is already registered for area {self.input_sensors[sensor_entity]}")
|
||||||
|
|
||||||
|
# Recompute elapsed minutes for one area and push it to the output
|
||||||
|
# sensor. Fallback chain: area max_time → app max_time → MAX_TIME constant.
|
||||||
def update_area_sensor(self,area,current_time):
|
def update_area_sensor(self,area,current_time):
|
||||||
time_elapsed = min((current_time - self.dataset['areas_movement_time'][area]) / 60,self.MAX_TIME)
|
max_time = self.areas_max_time.get(area, self.app_max_time)
|
||||||
|
time_elapsed = min((current_time - self.dataset['areas_movement_time'][area]) / 60, max_time)
|
||||||
assert time_elapsed != None
|
assert time_elapsed != None
|
||||||
self.output_last_motion_sensors[area].set_state(state = int(time_elapsed),attributes = {'unit_of_measurement' : "min"})
|
self.output_last_motion_sensors[area].set_state(state = int(time_elapsed),attributes = {'unit_of_measurement' : "min"})
|
||||||
|
|
||||||
|
# Returns True when the area is listed under
|
||||||
|
# areas_excluded_from_last_area_with_movement and should not update
|
||||||
|
# sensor.last_area_with_movement
|
||||||
def is_excluded_from_last_area_with_movement(self,area):
|
def is_excluded_from_last_area_with_movement(self,area):
|
||||||
if "areas_excluded_from_last_area_with_movement" in self.args:
|
if "areas_excluded_from_last_area_with_movement" in self.args:
|
||||||
return area in self.args["areas_excluded_from_last_area_with_movement"]
|
return area in self.args["areas_excluded_from_last_area_with_movement"]
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Returns True when the area has been fully initialised (output sensors
|
||||||
|
# created and motion listeners registered).
|
||||||
def is_area_initialized(self,area):
|
def is_area_initialized(self,area):
|
||||||
return area in self.output_last_motion_sensors #might need a dedicated boolean at some point
|
return area in self.output_last_motion_sensors #might need a dedicated boolean at some point
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Periodic refresh
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Refresh all area sensors and sensor.last_area_with_movement, then reschedule
|
||||||
|
# itself to run again in 30 seconds. Any existing pending timer is
|
||||||
|
# cancelled first to avoid concurrent runs when called directly from
|
||||||
|
# a motion event.
|
||||||
|
# Areas are updated in ascending order of last-movement timestamp so
|
||||||
|
# that brief ordering inversions never trigger false automations.
|
||||||
def update_areas_data(self,*args):
|
def update_areas_data(self,*args):
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
#we don't want update_areas_data_to_fork if it's called directly from on_state_change
|
#we don't want update_areas_data_to_fork if it's called directly from on_state_change
|
||||||
@@ -120,9 +285,16 @@ class MotionTracker(SmartObject):
|
|||||||
for area in sorted(self.dataset['areas_movement_time'], key = lambda area: self.dataset['areas_movement_time'][area]):
|
for area in sorted(self.dataset['areas_movement_time'], key = lambda area: self.dataset['areas_movement_time'][area]):
|
||||||
if (self.is_area_initialized(area)): self.update_area_sensor(area,current_time)
|
if (self.is_area_initialized(area)): self.update_area_sensor(area,current_time)
|
||||||
|
|
||||||
self.set_state("sensor.last_motion",state = self.dataset['last_area_with_movement'])
|
self.output_last_area_with_movement_sensor.set_state(self.dataset['last_area_with_movement'])
|
||||||
self.update_cb_handle = self.run_in(self.update_areas_data,30)
|
self.update_cb_handle = self.run_in(self.update_areas_data,30)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# State-change callbacks
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Fired when a motion sensor transitions off→on (and optionally on→off).
|
||||||
|
# Records the current timestamp, updates last_area_with_movement (unless
|
||||||
|
# excluded), and triggers an immediate sensor refresh.
|
||||||
def on_motion_detected(self, entity, attribute, old, new, kwargs):
|
def on_motion_detected(self, entity, attribute, old, new, kwargs):
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
self.dataset['areas_movement_time'][kwargs['area']] = current_time
|
self.dataset['areas_movement_time'][kwargs['area']] = current_time
|
||||||
@@ -133,5 +305,7 @@ class MotionTracker(SmartObject):
|
|||||||
|
|
||||||
self.update_areas_data()
|
self.update_areas_data()
|
||||||
|
|
||||||
|
# Fired when the door sensor for an area transitions on→off (door closed).
|
||||||
|
# Stores the current unix timestamp in the door_close_time sensor.
|
||||||
def on_door_close(self, entity, attribute, old, new, kwargs):
|
def on_door_close(self, entity, attribute, old, new, kwargs):
|
||||||
self.output_door_close_time_sensors[kwargs['area']].set_state(state = time.time())
|
self.output_door_close_time_sensors[kwargs['area']].set_state(state = time.time())
|
||||||
|
|||||||
@@ -1,30 +1,137 @@
|
|||||||
import appdaemon.plugins.hass.hassapi as hass
|
|
||||||
from smartswitch import SmartSwitch
|
from smartswitch import SmartSwitch
|
||||||
import ad_toolbox.smartcondition as SmartCondition
|
import ad_toolbox.smartcondition as SmartCondition
|
||||||
|
from ad_toolbox.eventhandler import EventHandler
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SmartLight — Light-specific extension of SmartSwitch
|
||||||
|
# =============================================================================
|
||||||
|
# Adds brightness control and smooth transitions on top of all SmartSwitch
|
||||||
|
# features. Use this class instead of SmartSwitch when the entity is a light.
|
||||||
|
#
|
||||||
|
# Inherits all SmartObject and SmartSwitch YAML keys.
|
||||||
|
# The "entity" key is required and must refer to a light entity.
|
||||||
|
#
|
||||||
|
# YAML CONFIGURATION
|
||||||
|
# ------------------
|
||||||
|
#
|
||||||
|
# brightness_pct_step: <int> # optional, default 5
|
||||||
|
# Step size (% of full brightness) used by increase/decrease events.
|
||||||
|
#
|
||||||
|
# increase_brightness_events: # optional
|
||||||
|
# <EventHandler events_block>
|
||||||
|
# HA events that increase brightness by brightness_pct_step.
|
||||||
|
# Has no effect while the light is off.
|
||||||
|
#
|
||||||
|
# decrease_brightness_events: # optional
|
||||||
|
# <EventHandler events_block>
|
||||||
|
# HA events that decrease brightness by brightness_pct_step.
|
||||||
|
# Has no effect while the light is off.
|
||||||
|
#
|
||||||
|
# on_events_with_transition: # optional
|
||||||
|
# <label>:
|
||||||
|
# events:
|
||||||
|
# <EventHandler events_block>
|
||||||
|
# brightness_pct: <0-100>
|
||||||
|
# Target brightness level.
|
||||||
|
# transition_time: <seconds>
|
||||||
|
# Duration of the ramp from 1 % to brightness_pct.
|
||||||
|
# Turns the light on with a smooth ramp-up. Multiple labelled
|
||||||
|
# transitions can be defined (e.g. "dim", "full").
|
||||||
|
#
|
||||||
|
# light_brightness_pct: # optional
|
||||||
|
# always_change_brightness: true|false # optional, default false
|
||||||
|
# When true, apply the resolved brightness even if the light
|
||||||
|
# is currently off. When false, only apply it while on.
|
||||||
|
# <label>:
|
||||||
|
# <SmartCondition block>
|
||||||
|
# The condition value (label) is used directly as the brightness
|
||||||
|
# percentage when the condition succeeds.
|
||||||
|
# Conditions are evaluated in declaration order; the first
|
||||||
|
# Succeeded result wins.
|
||||||
|
# Dynamically sets the brightness based on conditions. When the
|
||||||
|
# active condition changes, the new brightness is applied
|
||||||
|
# immediately (subject to always_change_brightness).
|
||||||
|
# Note: brightness 0 also calls light/turn_off for compatibility
|
||||||
|
# with integrations that do not honour brightness = 0.
|
||||||
|
#
|
||||||
|
# icon_override: # optional
|
||||||
|
# on_icon: <mdi:icon>
|
||||||
|
# off_icon: <mdi:icon>
|
||||||
|
# dest_entities: <entity_id | list of entity_ids>
|
||||||
|
# Updates the icon attribute on dest_entities whenever the light
|
||||||
|
# turns on or off. Useful for dashboard button cards.
|
||||||
|
#
|
||||||
|
# EXAMPLE YAML
|
||||||
|
# ------------
|
||||||
|
# living_light:
|
||||||
|
# module: smartlight
|
||||||
|
# class: SmartLight
|
||||||
|
# entity: light.living_room
|
||||||
|
#
|
||||||
|
# brightness_pct_step: 10
|
||||||
|
#
|
||||||
|
# increase_brightness_events:
|
||||||
|
# btn_up:
|
||||||
|
# event_name: WALL_SWITCH
|
||||||
|
# event_data: {action: brightness_up}
|
||||||
|
# decrease_brightness_events:
|
||||||
|
# btn_down:
|
||||||
|
# event_name: WALL_SWITCH
|
||||||
|
# event_data: {action: brightness_down}
|
||||||
|
#
|
||||||
|
# on_events_with_transition:
|
||||||
|
# dim:
|
||||||
|
# events:
|
||||||
|
# btn:
|
||||||
|
# event_name: WALL_SWITCH
|
||||||
|
# event_data: {action: dim}
|
||||||
|
# brightness_pct: 20
|
||||||
|
# transition_time: 5
|
||||||
|
#
|
||||||
|
# light_brightness_pct:
|
||||||
|
# always_change_brightness: false
|
||||||
|
# "10":
|
||||||
|
# trigger:
|
||||||
|
# condition: sensor.lux > 500
|
||||||
|
# "60":
|
||||||
|
# trigger:
|
||||||
|
# condition: sensor.lux > 100
|
||||||
|
# "100":
|
||||||
|
# trigger:
|
||||||
|
# condition: "true"
|
||||||
|
#
|
||||||
|
# icon_override:
|
||||||
|
# on_icon: mdi:lightbulb
|
||||||
|
# off_icon: mdi:lightbulb-outline
|
||||||
|
# dest_entities: sensor.living_room_button
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
class SmartLight(SmartSwitch):
|
class SmartLight(SmartSwitch):
|
||||||
|
|
||||||
#@SmartCondition.catch_smartcondition_exception(lambda self, message: self.log_error(message,stop_app = True))
|
|
||||||
def on_initialize_smart_object(self):
|
def on_initialize_smart_object(self):
|
||||||
|
# light_brightness_pct_list : ordered list of (Evaluator, label) pairs
|
||||||
|
# light_brightness_pct : currently active brightness % (str label)
|
||||||
self.light_brightness_pct_list = list()
|
self.light_brightness_pct_list = list()
|
||||||
self.light_brightness_pct = None
|
self.light_brightness_pct = None
|
||||||
|
|
||||||
super().on_initialize_smart_object()
|
super().on_initialize_smart_object()
|
||||||
|
|
||||||
|
# increase/decrease brightness step events
|
||||||
if "increase_brightness_events" in self.args:
|
if "increase_brightness_events" in self.args:
|
||||||
self.register_event_from_yaml(self.args["increase_brightness_events"],self.on_increase_brightness_event)
|
self.event_handlers.append(EventHandler(self, self.args["increase_brightness_events"], self.on_increase_brightness_event))
|
||||||
if "decrease_brightness_events" in self.args:
|
if "decrease_brightness_events" in self.args:
|
||||||
self.register_event_from_yaml(self.args["decrease_brightness_events"],self.on_decrease_brightness_event)
|
self.event_handlers.append(EventHandler(self, self.args["decrease_brightness_events"], self.on_decrease_brightness_event))
|
||||||
|
|
||||||
if "brightness_pct_step" in self.args:
|
if "brightness_pct_step" in self.args:
|
||||||
self.brightness_pct_step = self.args["brightness_pct_step"]
|
self.brightness_pct_step = self.args["brightness_pct_step"]
|
||||||
else: self.brightness_pct_step = 5
|
else: self.brightness_pct_step = 5
|
||||||
|
|
||||||
|
# on_events_with_transition: one EventHandler per labelled transition
|
||||||
if "on_events_with_transition" in self.args:
|
if "on_events_with_transition" in self.args:
|
||||||
for key in self.args["on_events_with_transition"]:
|
for key in self.args["on_events_with_transition"]:
|
||||||
#self.log(f"{key}")
|
self.event_handlers.append(EventHandler(self, self.args["on_events_with_transition"][key]["events"], self.on_turn_on_with_transition, key))
|
||||||
self.register_event_from_yaml(self.args["on_events_with_transition"][key]["events"],self.on_turn_on_with_transition,key)
|
|
||||||
|
|
||||||
|
# light_brightness_pct: build ordered evaluator list, run initial pass
|
||||||
if "light_brightness_pct" in self.args:
|
if "light_brightness_pct" in self.args:
|
||||||
self.always_change_brightness = False
|
self.always_change_brightness = False
|
||||||
|
|
||||||
@@ -37,6 +144,8 @@ class SmartLight(SmartSwitch):
|
|||||||
|
|
||||||
self.listen_state(self.on_state_change,self.entity_id)
|
self.listen_state(self.on_state_change,self.entity_id)
|
||||||
|
|
||||||
|
# listen_state callback on self.entity_id. Applies icon_override when
|
||||||
|
# the light turns on or off.
|
||||||
def on_state_change(self, entity, attribute, old, new, *kwargs):
|
def on_state_change(self, entity, attribute, old, new, *kwargs):
|
||||||
if "icon_override" in self.args:
|
if "icon_override" in self.args:
|
||||||
override_data = self.args['icon_override']
|
override_data = self.args['icon_override']
|
||||||
@@ -50,21 +159,35 @@ class SmartLight(SmartSwitch):
|
|||||||
update_icon(override_data['dest_entities'],new)
|
update_icon(override_data['dest_entities'],new)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Brightness event callbacks
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Increase brightness by brightness_pct_step while the light is on.
|
||||||
def on_increase_brightness_event(self, event_name, data, kwargs):
|
def on_increase_brightness_event(self, event_name, data, kwargs):
|
||||||
if self.get_state(self.entity_id) != 'off':
|
if self.get_state(self.entity_id) != 'off':
|
||||||
self.call_service("light/turn_on", entity_id = self.entity_id, brightness_step_pct = self.brightness_pct_step)
|
self.call_service("light/turn_on", entity_id = self.entity_id, brightness_step_pct = self.brightness_pct_step)
|
||||||
|
|
||||||
|
# Decrease brightness by brightness_pct_step while the light is on.
|
||||||
def on_decrease_brightness_event(self, event_name, data, kwargs):
|
def on_decrease_brightness_event(self, event_name, data, kwargs):
|
||||||
if self.get_state(self.entity_id) != 'off':
|
if self.get_state(self.entity_id) != 'off':
|
||||||
self.call_service("light/turn_on", entity_id = self.entity_id, brightness_step_pct = -self.brightness_pct_step)
|
self.call_service("light/turn_on", entity_id = self.entity_id, brightness_step_pct = -self.brightness_pct_step)
|
||||||
|
|
||||||
def on_turn_on_with_transition(self, event_name, data, kwargs,event_category):
|
# EventHandler callback for on_events_with_transition. Jumps to 1 %
|
||||||
|
# first so the ramp always starts from a known low level, then
|
||||||
|
# transitions to the configured brightness over transition_time seconds.
|
||||||
|
def on_turn_on_with_transition(self, event_name, data, kwargs, event_category):
|
||||||
transition_time = self.args["on_events_with_transition"][event_category]["transition_time"]
|
transition_time = self.args["on_events_with_transition"][event_category]["transition_time"]
|
||||||
brightness_pct = self.args["on_events_with_transition"][event_category]["brightness_pct"]
|
brightness_pct = self.args["on_events_with_transition"][event_category]["brightness_pct"]
|
||||||
self.log(f"Turn on at {brightness_pct}% with a transition of {transition_time}s")
|
self.log(f"Turn on at {brightness_pct}% with a transition of {transition_time}s")
|
||||||
self.call_service("light/turn_on", entity_id = self.entity_id,brightness_pct = 1)
|
self.call_service("light/turn_on", entity_id = self.entity_id,brightness_pct = 1)
|
||||||
self.call_service("light/turn_on", entity_id = self.entity_id, transition = transition_time,brightness_pct = brightness_pct)
|
self.call_service("light/turn_on", entity_id = self.entity_id, transition = transition_time,brightness_pct = brightness_pct)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# SmartSwitch overrides
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Override: apply light_brightness_pct when turning on, if one is active.
|
||||||
def switch_on(self):
|
def switch_on(self):
|
||||||
if self.light_brightness_pct != None:
|
if self.light_brightness_pct != None:
|
||||||
self.log(f"Turn on {self.entity_id} at {self.light_brightness_pct}%")
|
self.log(f"Turn on {self.entity_id} at {self.light_brightness_pct}%")
|
||||||
@@ -72,6 +195,10 @@ class SmartLight(SmartSwitch):
|
|||||||
else:
|
else:
|
||||||
super().switch_on()
|
super().switch_on()
|
||||||
|
|
||||||
|
# on_update_cb for all light_brightness_pct evaluators. Walks the list
|
||||||
|
# in order and applies the first Succeeded result. Pushes the new
|
||||||
|
# brightness to the light if it is on (or always_change_brightness=true).
|
||||||
|
# Brightness 0 additionally calls light/turn_off for compatibility.
|
||||||
def on_update_light_brightness_pct(self):
|
def on_update_light_brightness_pct(self):
|
||||||
for brightness_pct_evaluator in self.light_brightness_pct_list:
|
for brightness_pct_evaluator in self.light_brightness_pct_list:
|
||||||
if brightness_pct_evaluator[0].evaluate(False) == SmartCondition.Result.Succeeded:
|
if brightness_pct_evaluator[0].evaluate(False) == SmartCondition.Result.Succeeded:
|
||||||
|
|||||||
@@ -1,42 +1,122 @@
|
|||||||
import appdaemon.plugins.hass.hassapi as hass
|
import appdaemon.plugins.hass.hassapi as hass
|
||||||
import ad_toolbox.smartcondition as SmartCondition
|
import ad_toolbox.smartcondition as SmartCondition
|
||||||
from ad_toolbox.smartobject import SmartObject
|
from ad_toolbox.smartobject import SmartObject
|
||||||
|
from ad_toolbox.eventhandler import EventHandler
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SmartSwitch — Event- and condition-driven switch controller
|
||||||
|
# =============================================================================
|
||||||
|
# Binds a HA switch/input_boolean/light entity to a rich set of controls:
|
||||||
|
# HA events (on/off/toggle), smart conditions, and auto-timed switching.
|
||||||
|
#
|
||||||
|
# Inherits all SmartObject YAML keys (see smartobject.py).
|
||||||
|
# The "entity" key is required.
|
||||||
|
#
|
||||||
|
# YAML CONFIGURATION
|
||||||
|
# ------------------
|
||||||
|
#
|
||||||
|
# smart_conditions: # optional
|
||||||
|
# <SmartCondition block>
|
||||||
|
# When the condition result transitions to Succeeded the entity is
|
||||||
|
# turned on; when it transitions to Failed it is turned off.
|
||||||
|
# Mutually exclusive with off_conditions (off_conditions is ignored
|
||||||
|
# when both are set).
|
||||||
|
#
|
||||||
|
# off_conditions: # optional
|
||||||
|
# <SmartCondition block>
|
||||||
|
# Turns the entity off whenever the condition succeeds.
|
||||||
|
# Has no effect when smart_conditions is also configured.
|
||||||
|
#
|
||||||
|
# off_events: # optional
|
||||||
|
# <EventHandler events_block>
|
||||||
|
# HA events that turn the entity off.
|
||||||
|
#
|
||||||
|
# on_events: # optional
|
||||||
|
# <EventHandler events_block>
|
||||||
|
# HA events that turn the entity on.
|
||||||
|
#
|
||||||
|
# toggle_events: # optional
|
||||||
|
# <EventHandler events_block>
|
||||||
|
# HA events that toggle the entity.
|
||||||
|
#
|
||||||
|
# toggle_action: <ios_action_name> # optional
|
||||||
|
# iOS companion app action name (ios.action_fired) that toggles
|
||||||
|
# the entity.
|
||||||
|
#
|
||||||
|
# auto_switch_off_after: <seconds> # optional
|
||||||
|
# Automatically turn the entity off N seconds after it turns on.
|
||||||
|
# If the app restarts while the entity is already on the switch is
|
||||||
|
# turned off immediately (the remaining delay is not known).
|
||||||
|
#
|
||||||
|
# auto_switch_on_after: <seconds> # optional
|
||||||
|
# Automatically turn the entity on N seconds after it turns off.
|
||||||
|
# If the app restarts while the entity is already off the switch is
|
||||||
|
# turned on immediately.
|
||||||
|
#
|
||||||
|
# NOTES
|
||||||
|
# -----
|
||||||
|
# - smart_conditions and off_conditions use the SmartCondition evaluator;
|
||||||
|
# see smartcondition.py for the condition block schema.
|
||||||
|
# - off_events / on_events / toggle_events use the EventHandler format;
|
||||||
|
# see eventhandler.py for the events_block schema.
|
||||||
|
# - auto_switch_on_after and auto_switch_off_after are mutually independent
|
||||||
|
# and can coexist (e.g. pulse: on_after=0 + off_after=5).
|
||||||
|
#
|
||||||
|
# EXAMPLE YAML
|
||||||
|
# ------------
|
||||||
|
# my_switch:
|
||||||
|
# module: smartswitch
|
||||||
|
# class: SmartSwitch
|
||||||
|
# entity: input_boolean.my_switch
|
||||||
|
#
|
||||||
|
# on_events:
|
||||||
|
# btn:
|
||||||
|
# event_name: MY_BUTTON_PRESSED
|
||||||
|
# event_data:
|
||||||
|
# action: single
|
||||||
|
# off_events:
|
||||||
|
# btn_long:
|
||||||
|
# event_name: MY_BUTTON_PRESSED
|
||||||
|
# event_data:
|
||||||
|
# action: long
|
||||||
|
#
|
||||||
|
# auto_switch_off_after: 300
|
||||||
|
#
|
||||||
|
# smart_conditions:
|
||||||
|
# trigger:
|
||||||
|
# condition: sensor.presence == 'home'
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
class SmartSwitch(SmartObject):
|
class SmartSwitch(SmartObject):
|
||||||
|
|
||||||
#@SmartCondition.catch_smartcondition_exception(lambda self, message: self.log_error(message,stop_app = True))
|
|
||||||
def on_initialize_smart_object(self):
|
def on_initialize_smart_object(self):
|
||||||
#super().initialize()
|
super().on_initialize_smart_object()
|
||||||
|
|
||||||
self.off_conditions_evaluator = None
|
self.off_conditions_evaluator = None
|
||||||
self.smart_conditions_evaluator = None
|
self.smart_conditions_evaluator = None
|
||||||
|
|
||||||
#self.depends_on_module("smartswitch")
|
# smart_conditions: auto on/off based on condition result
|
||||||
|
|
||||||
if "smart_conditions" in self.args:
|
if "smart_conditions" in self.args:
|
||||||
self.smart_conditions_evaluator = SmartCondition.Evaluator(self,self.args['smart_conditions'],condition_name = "smart_conditions",on_change_cb = self.on_smart_conditions_change,constants = self.constants, templates_library = self.templates_library)
|
self.smart_conditions_evaluator = SmartCondition.Evaluator(self,self.args['smart_conditions'],condition_name = "smart_conditions",on_change_cb = self.on_smart_conditions_change,constants = self.constants, templates_library = self.templates_library)
|
||||||
|
|
||||||
|
# off_conditions: turn off only when condition succeeds (ignored if smart_conditions present)
|
||||||
if "off_conditions" in self.args:
|
if "off_conditions" in self.args:
|
||||||
if self.smart_conditions_evaluator == None:
|
if self.smart_conditions_evaluator == None:
|
||||||
self.off_conditions_evaluator = SmartCondition.Evaluator(self,self.args['off_conditions'],condition_name = "off_conditions",on_succeed_cb = self.on_off_conditions,constants = self.constants, templates_library = self.templates_library)
|
self.off_conditions_evaluator = SmartCondition.Evaluator(self,self.args['off_conditions'],condition_name = "off_conditions",on_succeed_cb = self.on_off_conditions,constants = self.constants, templates_library = self.templates_library)
|
||||||
else:
|
else:
|
||||||
self.log(f"Warning you can't have both an off_conditons and a smart_conditions, the off_conditions will be ignored")
|
self.log(f"Warning you can't have both an off_conditons and a smart_conditions, the off_conditions will be ignored")
|
||||||
|
|
||||||
if "debug" in self.args:
|
# Event-driven controls (off / on / toggle / iOS action)
|
||||||
self.log(f'Registering on_debug_display_event for {self.args["debug"]}')
|
self.event_handlers = []
|
||||||
self.listen_event(self.on_debug_display_event,self.args["debug"])
|
|
||||||
|
|
||||||
if "off_events" in self.args:
|
if "off_events" in self.args:
|
||||||
self.register_event_from_yaml(self.args["off_events"],self.on_turn_off_event)
|
self.event_handlers.append(EventHandler(self, self.args["off_events"], self.on_turn_off_event))
|
||||||
if "on_events" in self.args:
|
if "on_events" in self.args:
|
||||||
self.register_event_from_yaml(self.args["on_events"],self.on_turn_on_event)
|
self.event_handlers.append(EventHandler(self, self.args["on_events"], self.on_turn_on_event))
|
||||||
if "toggle_events" in self.args:
|
if "toggle_events" in self.args:
|
||||||
self.register_event_from_yaml(self.args["toggle_events"],self.on_toggle_event)
|
self.event_handlers.append(EventHandler(self, self.args["toggle_events"], self.on_toggle_event))
|
||||||
|
|
||||||
#todo: replace with register_event_from_yaml
|
|
||||||
if "toggle_action" in self.args:
|
|
||||||
self.toggle_action = self.args["toggle_action"]
|
|
||||||
self.listen_event(self.on_toggle_event_action,"ios.action_fired")
|
|
||||||
|
|
||||||
|
# auto_switch_*_after: timed auto-switch; if the app restarts in the
|
||||||
|
# wrong state we apply the switch immediately since the delay is lost.
|
||||||
self.auto_switch_cb_handle = None
|
self.auto_switch_cb_handle = None
|
||||||
if 'auto_switch_on_after' in self.args:
|
if 'auto_switch_on_after' in self.args:
|
||||||
if self.is_off():
|
if self.is_off():
|
||||||
@@ -54,6 +134,8 @@ class SmartSwitch(SmartObject):
|
|||||||
self.off_conditions_evaluator = None
|
self.off_conditions_evaluator = None
|
||||||
super().terminate()
|
super().terminate()
|
||||||
|
|
||||||
|
# listen_state callback for auto_switch_*_after. Cancels any pending
|
||||||
|
# timer then arms a new one for the configured delay.
|
||||||
def on_state_change(self, entity, attribute, old, new, kwargs):
|
def on_state_change(self, entity, attribute, old, new, kwargs):
|
||||||
self.log("state changed from " + str(old) + " to " + str(new))
|
self.log("state changed from " + str(old) + " to " + str(new))
|
||||||
if old != new:
|
if old != new:
|
||||||
@@ -70,14 +152,15 @@ class SmartSwitch(SmartObject):
|
|||||||
self.log(f"{self.entity_id} will auto switch off in {delay}s")
|
self.log(f"{self.entity_id} will auto switch off in {delay}s")
|
||||||
self.auto_switch_cb_handle = self.run_in(self.on_auto_switch_after, delay, new_state = 'on',autoswitch_delay = delay)
|
self.auto_switch_cb_handle = self.run_in(self.on_auto_switch_after, delay, new_state = 'on',autoswitch_delay = delay)
|
||||||
|
|
||||||
|
# Timer callback: applies the deferred state set by auto_switch_*_after.
|
||||||
def on_auto_switch_after(self, kwargs):
|
def on_auto_switch_after(self, kwargs):
|
||||||
self.auto_switch_cb_handle = None
|
self.auto_switch_cb_handle = None
|
||||||
self.log(f"Switching {self.entity_id} {kwargs['new_state']} after {kwargs['autoswitch_delay']}s")
|
self.log(f"Switching {self.entity_id} {kwargs['new_state']} after {kwargs['autoswitch_delay']}s")
|
||||||
self.set_state(self.entity_id,state = kwargs['new_state'])
|
self.set_state(self.entity_id,state = kwargs['new_state'])
|
||||||
|
|
||||||
#debug display to display all events
|
# ------------------------------------------------------------------
|
||||||
def on_debug_display_event(self,event_name,data,kwargs):
|
# Public switch API
|
||||||
self.log(f"events {event_name} has been catched. data = {data}")
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def switch_on(self):
|
def switch_on(self):
|
||||||
self.log(f"Turn on {self.entity_id}")
|
self.log(f"Turn on {self.entity_id}")
|
||||||
@@ -109,6 +192,10 @@ class SmartSwitch(SmartObject):
|
|||||||
|
|
||||||
def is_off(self): return not self.is_on()
|
def is_off(self): return not self.is_on()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Event callbacks
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def on_turn_off_event(self, event_name, data, kwargs):
|
def on_turn_off_event(self, event_name, data, kwargs):
|
||||||
self.log(f"Turned off by event {event_name}")
|
self.log(f"Turned off by event {event_name}")
|
||||||
self.switch_off()
|
self.switch_off()
|
||||||
@@ -124,14 +211,13 @@ class SmartSwitch(SmartObject):
|
|||||||
else:
|
else:
|
||||||
self.switch_on()
|
self.switch_on()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Condition callbacks
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Fired by smart_conditions when its result changes. Mirrors the
|
||||||
|
# condition outcome directly onto the entity state.
|
||||||
def on_smart_conditions_change(self,prev_result,result):
|
def on_smart_conditions_change(self,prev_result,result):
|
||||||
#trying to track some weird behavior
|
|
||||||
# if self.smart_conditions_evaluator:
|
|
||||||
# debug_result = self.smart_conditions_evaluator.evaluate()
|
|
||||||
# if debug_result != result:
|
|
||||||
# self.log(f"on_smart_conditions_change was called with prev_result = {prev_result}, result = {result} and evaluate returned {debug_result}")
|
|
||||||
# else:
|
|
||||||
# self.log("on_smart_conditions_change was called without a smart_conditions_evaluator")
|
|
||||||
if result == SmartCondition.Result.Succeeded:
|
if result == SmartCondition.Result.Succeeded:
|
||||||
if self.is_off():
|
if self.is_off():
|
||||||
self.switch_on()
|
self.switch_on()
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
debugger:
|
|
||||||
module: debugger
|
|
||||||
class: Debugger
|
|
||||||
port: 5678
|
|
||||||
|
|
||||||
motion_tracker:
|
|
||||||
module: motiontracker
|
|
||||||
class: MotionTracker
|
|
||||||
|
|
||||||
priority: 5 # this need to be initialized before app using motion tracker
|
|
||||||
|
|
||||||
mqtt_device_name: AD Motion Tracker
|
|
||||||
|
|
||||||
areas:
|
|
||||||
corridor:
|
|
||||||
motion_sensors: binary_sensor.corridor_motion
|
|
||||||
hallway:
|
|
||||||
motion_sensors: binary_sensor.hallway_motion
|
|
||||||
restroom:
|
|
||||||
motion_sensors: binary_sensor.restroom_motion
|
|
||||||
|
|
||||||
@@ -4,23 +4,38 @@ light_corridor:
|
|||||||
entity: light.corridor
|
entity: light.corridor
|
||||||
|
|
||||||
smart_conditions:
|
smart_conditions:
|
||||||
trigger_conditions: sensor.corridor_last_motion < 5
|
trigger_conditions: sensor.corridor_last_motion < 3
|
||||||
blocking_conditions: sensor.corridor_motion_light_level > 10 and not self
|
blocking_conditions: sensor.corridor_motion_light_level > 10 and not self
|
||||||
|
|
||||||
|
light_brightness_pct:
|
||||||
|
1: input_boolean.sleeping
|
||||||
|
#25: sensor.day_interval == 'Nuit'
|
||||||
|
100: True
|
||||||
|
|
||||||
light_hallway:
|
light_hallway:
|
||||||
module: smartlight
|
module: smartlight
|
||||||
class: SmartLight
|
class: SmartLight
|
||||||
entity: light.hallway
|
entity: light.hallway
|
||||||
|
|
||||||
smart_conditions:
|
smart_conditions:
|
||||||
trigger_conditions: sensor.hallway_last_motion < 5
|
trigger_conditions: sensor.hallway_last_motion < 3
|
||||||
blocking_conditions: sensor.hallway_motion_light_level > 10 and not self
|
blocking_conditions: sensor.hallway_motion_light_level > 10 and not self
|
||||||
|
|
||||||
|
light_brightness_pct:
|
||||||
|
1: input_boolean.sleeping
|
||||||
|
#25: sensor.day_interval == 'Nuit'
|
||||||
|
100: True
|
||||||
|
|
||||||
light_restroom:
|
light_restroom:
|
||||||
module: smartlight
|
module: smartlight
|
||||||
class: SmartLight
|
class: SmartLight
|
||||||
entity: light.restroom
|
entity: light.restroom
|
||||||
|
|
||||||
smart_conditions:
|
smart_conditions:
|
||||||
trigger_conditions: sensor.restroom_last_motion < 5
|
trigger_conditions: sensor.restroom_last_motion < 3
|
||||||
blocking_conditions: sensor.restroom_motion_light_level > 10 and not self
|
blocking_conditions: sensor.restroom_motion_light_level > 10 and not self
|
||||||
|
|
||||||
|
light_brightness_pct:
|
||||||
|
1: input_boolean.sleeping
|
||||||
|
#25: sensor.day_interval == 'Nuit'
|
||||||
|
100: True
|
||||||
Reference in New Issue
Block a user