diff --git a/apps/global/presence.yaml b/apps/global/presence.yaml index 3bafa1a..bf8d876 100644 --- a/apps/global/presence.yaml +++ b/apps/global/presence.yaml @@ -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 diff --git a/apps/global/virtualsensors.yaml b/apps/global/virtualsensors.yaml new file mode 100644 index 0000000..4cc89db --- /dev/null +++ b/apps/global/virtualsensors.yaml @@ -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 + diff --git a/apps/motiontracker.py b/apps/motiontracker.py index 7608b11..4427fc0 100644 --- a/apps/motiontracker.py +++ b/apps/motiontracker.py @@ -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: # optional # Per-area cap, overrides the app-level max_time. -# door_sensor: # 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: # 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): # : # @@ -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()) \ No newline at end of file diff --git a/apps/rooms/living_room.yaml b/apps/rooms/living_room.yaml index 339b2d9..02e91a7 100644 --- a/apps/rooms/living_room.yaml +++ b/apps/rooms/living_room.yaml @@ -24,4 +24,20 @@ light_mezzanine_02: light_brightness_pct: 5: input_boolean.sleeping 25: binary_sensor.day_interval_night - 100: True \ No newline at end of file + 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 \ No newline at end of file