Enhance motion tracking and occupancy detection with new sensor configurations and improved logic
This commit is contained in:
@@ -14,12 +14,18 @@ motion_tracker:
|
|||||||
restroom:
|
restroom:
|
||||||
motion_sensors: binary_sensor.restroom_motion
|
motion_sensors: binary_sensor.restroom_motion
|
||||||
garage:
|
garage:
|
||||||
motion_sensors: binary_sensor.garage_motion
|
door_generate_motion: true
|
||||||
|
motion_sensors:
|
||||||
|
- binary_sensor.garage_motion
|
||||||
|
door_sensors:
|
||||||
|
- binary_sensor.garage_entrance
|
||||||
|
- binary_sensor.garage_door
|
||||||
kitchen:
|
kitchen:
|
||||||
motion_sensors: binary_sensor.kitchen_motion
|
motion_sensors: binary_sensor.kitchen_motion
|
||||||
mezzanine:
|
mezzanine:
|
||||||
motion_sensors: binary_sensor.mezzanine_motion
|
motion_sensors: binary_sensor.mezzanine_motion
|
||||||
living_room:
|
living_room:
|
||||||
|
update_on_both_front: true
|
||||||
motion_sensors: binary_sensor.living_room_presence_presence
|
motion_sensors: binary_sensor.living_room_presence_presence
|
||||||
|
|
||||||
occupancy_sensors:
|
occupancy_sensors:
|
||||||
@@ -28,9 +34,15 @@ occupancy_sensors:
|
|||||||
priority: 8 # default priority app is 50, since most of them require sensors created by virtual sensors, it's important that virtual_sensors start first
|
priority: 8 # default priority app is 50, since most of them require sensors created by virtual sensors, it's important that virtual_sensors start first
|
||||||
|
|
||||||
sensors:
|
sensors:
|
||||||
binary_sensor.occupancy_living_room:
|
binary_sensor.living_room_occupancy:
|
||||||
trigger_conditions: sensor.living_room_last_motion <= 5
|
trigger_conditions: sensor.living_room_last_motion <= 5
|
||||||
binary_sensor.occupancy_kitchen:
|
binary_sensor.kitchen_occupancy:
|
||||||
trigger_conditions: sensor.kitchen_last_motion <= 5
|
trigger_conditions: sensor.kitchen_last_motion <= 5
|
||||||
binary_sensor.occupancy_restroom:
|
binary_sensor.restroom_occupancy:
|
||||||
trigger_conditions: sensor.restroom_last_motion <= 3
|
trigger_conditions: sensor.restroom_last_motion <= 3
|
||||||
|
retain_condition.desk_occupancy:
|
||||||
|
retain_time: 5 * 60
|
||||||
|
conditions:
|
||||||
|
trigger_conditions:
|
||||||
|
- binary_sensor.alienware_r10
|
||||||
|
- sensor.macbook_m3_pro_de_pierre_frontmost_app != 'loginwindow' and sensor.macbook_m3_pro_de_pierre_displays == 2
|
||||||
|
|||||||
14
apps/global/virtualsensors.yaml
Normal file
14
apps/global/virtualsensors.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
virtual_sensors:
|
||||||
|
module: virtualsensors
|
||||||
|
class: VirtualSensorsApp
|
||||||
|
priority: 10 # default priority app is 50, since most of them require sensors created by virtual sensors, it's important that virtual_sensors start first
|
||||||
|
|
||||||
|
sensors:
|
||||||
|
continuous_condition.is_apple_tv_idle:
|
||||||
|
time: 5
|
||||||
|
conditions: media_player.apple_tv == 'paused' or media_player.apple_tv == 'idle' or media_player.apple_tv == 'standby'
|
||||||
|
|
||||||
|
binary_sensor.is_apple_tv_on: media_player.apple_tv != 'off'
|
||||||
|
binary_sensor.someone_home: person.maeva == 'home' or person.pierre == 'home'
|
||||||
|
binary_sensor.is_everybody_sleeping: input_boolean.sleeping
|
||||||
|
|
||||||
@@ -29,12 +29,14 @@ import time
|
|||||||
# update_on_both_front: true # optional, default false
|
# update_on_both_front: true # optional, default false
|
||||||
# When true, motion is also recorded on the falling edge
|
# When true, motion is also recorded on the falling edge
|
||||||
# (sensor going on→off), not only on the rising edge.
|
# (sensor going on→off), not only on the rising edge.
|
||||||
|
# While any configured motion sensor remains on, the area
|
||||||
|
# timestamp is refreshed during the periodic update loop.
|
||||||
# max_time: <minutes> # optional
|
# max_time: <minutes> # optional
|
||||||
# Per-area cap, overrides the app-level max_time.
|
# Per-area cap, overrides the app-level max_time.
|
||||||
# door_sensor: <entity_id> # optional
|
# door_sensors: <entity_id | list of entity_ids> # optional
|
||||||
# Binary sensor for the area door. When the door closes
|
# One or more binary sensors for the area door. When any
|
||||||
# (on→off), the close timestamp is stored and exposed as
|
# door closes (on→off), the close timestamp is stored and
|
||||||
# a sensor (see OUTPUT ENTITIES below).
|
# exposed as a sensor (see OUTPUT ENTITIES below).
|
||||||
# # Shorthand form (no extra options needed):
|
# # Shorthand form (no extra options needed):
|
||||||
# <area_name>: <entity_id | list of entity_ids>
|
# <area_name>: <entity_id | list of entity_ids>
|
||||||
#
|
#
|
||||||
@@ -98,7 +100,7 @@ import time
|
|||||||
# - binary_sensor.living_room_motion_1
|
# - binary_sensor.living_room_motion_1
|
||||||
# - binary_sensor.living_room_motion_2
|
# - binary_sensor.living_room_motion_2
|
||||||
# max_time: 60
|
# max_time: 60
|
||||||
# door_sensor: binary_sensor.living_room_door
|
# door_sensors: binary_sensor.living_room_door
|
||||||
# garage:
|
# garage:
|
||||||
# motion_sensors: binary_sensor.garage_motion
|
# motion_sensors: binary_sensor.garage_motion
|
||||||
# update_on_both_front: true
|
# update_on_both_front: true
|
||||||
@@ -150,6 +152,7 @@ class MotionTracker(SmartObject):
|
|||||||
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.area_motion_sensors = dict()
|
||||||
self.app_max_time = self.args.get('max_time', self.MAX_TIME)
|
self.app_max_time = self.args.get('max_time', self.MAX_TIME)
|
||||||
self.areas_max_time = dict()
|
self.areas_max_time = dict()
|
||||||
self.update_cb_handle = None
|
self.update_cb_handle = None
|
||||||
@@ -159,16 +162,18 @@ class MotionTracker(SmartObject):
|
|||||||
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
|
||||||
|
door_generate_motion = False
|
||||||
self.output_last_motion_sensors[area] = self.create_entity(f"sensor.{area}_last_motion")
|
self.output_last_motion_sensors[area] = self.create_entity(f"sensor.{area}_last_motion")
|
||||||
self.output_last_motion_time_sensors[area] = self.create_entity(f"sensor.{area}_last_motion_time")
|
self.output_last_motion_time_sensors[area] = self.create_entity(f"sensor.{area}_last_motion_time")
|
||||||
|
self.area_motion_sensors[area] = list()
|
||||||
if isinstance(self.args['areas'][area], dict):
|
if isinstance(self.args['areas'][area], dict):
|
||||||
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']
|
update_on_both_front = self.args['areas'][area].get('update_on_both_front', False)
|
||||||
except KeyError: pass
|
door_generate_motion = self.args['areas'][area].get('door_generate_motion', False)
|
||||||
if 'max_time' in self.args['areas'][area]:
|
if 'max_time' in self.args['areas'][area]:
|
||||||
self.areas_max_time[area] = self.args['areas'][area]['max_time']
|
self.areas_max_time[area] = self.args['areas'][area]['max_time']
|
||||||
|
|
||||||
if 'door_sensor' in self.args['areas'][area]:
|
if 'door_sensors' in self.args['areas'][area]:
|
||||||
self.output_door_close_time_sensors[area] = self.create_entity(f"sensor.{area}_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']:
|
||||||
@@ -177,7 +182,13 @@ class MotionTracker(SmartObject):
|
|||||||
if not self.output_door_close_time_sensors[area].exists():
|
if not self.output_door_close_time_sensors[area].exists():
|
||||||
self.output_door_close_time_sensors[area].set_state(state = self.dataset['areas_door_close_time'][area],attributes = {'unit_of_measurement' : "s"})
|
self.output_door_close_time_sensors[area].set_state(state = self.dataset['areas_door_close_time'][area],attributes = {'unit_of_measurement' : "s"})
|
||||||
|
|
||||||
self.listen_state(self.on_door_close,self.args['areas'][area]['door_sensor'],new = 'off', old = 'on',area = area)
|
door_sensor_entities = self.args['areas'][area]['door_sensors']
|
||||||
|
if not isinstance(door_sensor_entities, list):
|
||||||
|
door_sensor_entities = [door_sensor_entities]
|
||||||
|
for door_entity in door_sensor_entities:
|
||||||
|
self.listen_state(self.on_door_close, door_entity, new = 'off', old = 'on', area = area)
|
||||||
|
if door_generate_motion:
|
||||||
|
self.listen_state(self.on_motion_detected, door_entity, area = area)
|
||||||
else:
|
else:
|
||||||
sensor_entities = self.args['areas'][area]
|
sensor_entities = self.args['areas'][area]
|
||||||
|
|
||||||
@@ -233,6 +244,7 @@ class MotionTracker(SmartObject):
|
|||||||
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:
|
||||||
self.input_sensors[sensor_entity] = area
|
self.input_sensors[sensor_entity] = area
|
||||||
|
self.area_motion_sensors[area].append(sensor_entity)
|
||||||
self.listen_state(self.on_motion_detected,sensor_entity,old = "off", new = "on", area = area)
|
self.listen_state(self.on_motion_detected,sensor_entity,old = "off", new = "on", area = area)
|
||||||
if update_on_both_front:
|
if update_on_both_front:
|
||||||
self.listen_state(self.on_motion_detected,sensor_entity,old = "on", new = "off", area = area)
|
self.listen_state(self.on_motion_detected,sensor_entity,old = "on", new = "off", area = area)
|
||||||
@@ -247,6 +259,11 @@ class MotionTracker(SmartObject):
|
|||||||
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 at least one motion sensor in the area is currently on.
|
||||||
|
# Used for presence sensors that can remain latched on for long periods.
|
||||||
|
def is_area_active(self,area):
|
||||||
|
return any(self.get_state(sensor_entity) == "on" for sensor_entity in self.area_motion_sensors.get(area, []))
|
||||||
|
|
||||||
# Returns True when the area is listed under
|
# Returns True when the area is listed under
|
||||||
# areas_excluded_from_last_area_with_movement and should not update
|
# areas_excluded_from_last_area_with_movement and should not update
|
||||||
# sensor.last_area_with_movement
|
# sensor.last_area_with_movement
|
||||||
@@ -283,7 +300,11 @@ class MotionTracker(SmartObject):
|
|||||||
#For example if A = 5 and B = 6 and add 2 to the both of them starting by A
|
#For example if A = 5 and B = 6 and add 2 to the both of them starting by A
|
||||||
#A will be bigger than B (A = 7 and B = 6) for a very short time, and might trigger some automation
|
#A will be bigger than B (A = 7 and B = 6) for a very short time, and might trigger some automation
|
||||||
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)):
|
||||||
|
if self.is_area_active(area):
|
||||||
|
self.dataset['areas_movement_time'][area] = current_time
|
||||||
|
self.output_last_motion_time_sensors[area].set_state(state = current_time)
|
||||||
|
self.update_area_sensor(area,current_time)
|
||||||
|
|
||||||
self.output_last_area_with_movement_sensor.set_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)
|
||||||
|
|||||||
@@ -25,3 +25,19 @@ light_mezzanine_02:
|
|||||||
5: input_boolean.sleeping
|
5: input_boolean.sleeping
|
||||||
25: binary_sensor.day_interval_night
|
25: binary_sensor.day_interval_night
|
||||||
100: True
|
100: True
|
||||||
|
|
||||||
|
light_bookshelves:
|
||||||
|
module: smartlight
|
||||||
|
class: SmartLight
|
||||||
|
entity: light.living_room_bookshelves
|
||||||
|
|
||||||
|
smart_conditions:
|
||||||
|
trigger_conditions: binary_sensor.living_room_occupancy
|
||||||
|
blocking_conditions:
|
||||||
|
- sensor.mezzanine_motion_light_level > 10
|
||||||
|
- binary_sensor.is_apple_tv_on and not binary_sensor.is_apple_tv_idle
|
||||||
|
|
||||||
|
#light_brightness_pct:
|
||||||
|
# 5: input_boolean.sleeping
|
||||||
|
# 25: binary_sensor.day_interval_night
|
||||||
|
# 100: True
|
||||||
Reference in New Issue
Block a user