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:
|
||||
motion_sensors: binary_sensor.restroom_motion
|
||||
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:
|
||||
motion_sensors: binary_sensor.kitchen_motion
|
||||
mezzanine:
|
||||
motion_sensors: binary_sensor.mezzanine_motion
|
||||
living_room:
|
||||
update_on_both_front: true
|
||||
motion_sensors: binary_sensor.living_room_presence_presence
|
||||
|
||||
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
|
||||
|
||||
sensors:
|
||||
binary_sensor.occupancy_living_room:
|
||||
binary_sensor.living_room_occupancy:
|
||||
trigger_conditions: sensor.living_room_last_motion <= 5
|
||||
binary_sensor.occupancy_kitchen:
|
||||
binary_sensor.kitchen_occupancy:
|
||||
trigger_conditions: sensor.kitchen_last_motion <= 5
|
||||
binary_sensor.occupancy_restroom:
|
||||
binary_sensor.restroom_occupancy:
|
||||
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
|
||||
# When true, motion is also recorded on the falling 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
|
||||
# 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).
|
||||
# door_sensors: <entity_id | list of entity_ids> # optional
|
||||
# One or more binary sensors for the area door. When any
|
||||
# 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>
|
||||
#
|
||||
@@ -98,7 +100,7 @@ import time
|
||||
# - binary_sensor.living_room_motion_1
|
||||
# - binary_sensor.living_room_motion_2
|
||||
# max_time: 60
|
||||
# door_sensor: binary_sensor.living_room_door
|
||||
# door_sensors: binary_sensor.living_room_door
|
||||
# garage:
|
||||
# motion_sensors: binary_sensor.garage_motion
|
||||
# update_on_both_front: true
|
||||
@@ -150,6 +152,7 @@ class MotionTracker(SmartObject):
|
||||
self.output_last_motion_sensors = dict()
|
||||
self.output_last_motion_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.areas_max_time = dict()
|
||||
self.update_cb_handle = None
|
||||
@@ -159,16 +162,18 @@ class MotionTracker(SmartObject):
|
||||
current_time = time.time()
|
||||
for area in self.args['areas']:
|
||||
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_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):
|
||||
sensor_entities = self.args['areas'][area]['motion_sensors']
|
||||
try: update_on_both_front = self.args['areas'][area]['update_on_both_front']
|
||||
except KeyError: pass
|
||||
update_on_both_front = self.args['areas'][area].get('update_on_both_front', False)
|
||||
door_generate_motion = self.args['areas'][area].get('door_generate_motion', False)
|
||||
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_sensors' in self.args['areas'][area]:
|
||||
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']:
|
||||
@@ -177,7 +182,13 @@ class MotionTracker(SmartObject):
|
||||
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.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:
|
||||
sensor_entities = self.args['areas'][area]
|
||||
|
||||
@@ -233,6 +244,7 @@ class MotionTracker(SmartObject):
|
||||
self.log(f"Registering sensor {sensor_entity} for area {area}")
|
||||
if sensor_entity not in self.input_sensors:
|
||||
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)
|
||||
if update_on_both_front:
|
||||
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
|
||||
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
|
||||
# areas_excluded_from_last_area_with_movement and should not update
|
||||
# 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
|
||||
#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]):
|
||||
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.update_cb_handle = self.run_in(self.update_areas_data,30)
|
||||
@@ -308,4 +329,4 @@ class MotionTracker(SmartObject):
|
||||
# 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):
|
||||
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())
|
||||
@@ -24,4 +24,20 @@ light_mezzanine_02:
|
||||
light_brightness_pct:
|
||||
5: input_boolean.sleeping
|
||||
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