diff --git a/README.md b/README.md index ae49a4e..99df363 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,11 @@ bathroom: - switch.plug_68fe8b4c9fa1 motion: - binary_sensor.motion_sensor_158d033224e141 + # For the bathroom leave the light on as long as the door is closed + door: + - binary_sensor.bathroom_door + # Optional : When the door opens turn off the light after specified delay + door_open_delay: 10 ``` ## Auto-Discovery of Lights and Sensors @@ -152,6 +157,8 @@ key | optional | type | default | description `humidity_threshold` | True | integer | | If humidity is *above* this value, lights will *not switched off* `motion_state_on` | True | integer | | If using motion sensors which don't send events if already activated, like Xiaomi do, add this to your config with "on". This will listen to state changes instead `motion_state_off` | True | integer | | If using motion sensors which don't send events if already activated, like Xiaomi do, add this to your config with "off". This will listen to the state changes instead. +`door` | True | list/string | | Door close sensor, light will not turn off when door is closed +`door_open_delay` | True | integer | | Override normal delay when door is opened `debug_log` | True | bool | false | Activate debug logging (for this room) ### daytimes diff --git a/apps/automoli/automoli.py b/apps/automoli/automoli.py index c5db86f..158bf2e 100644 --- a/apps/automoli/automoli.py +++ b/apps/automoli/automoli.py @@ -245,6 +245,9 @@ async def initialize(self) -> None: # general delay self.delay = int(self.args.pop("delay", DEFAULT_DELAY)) + # Delay override upon door open + self.door_open_delay = int(self.args.pop("door_open_delay", 0)) + # directly switch to new daytime light settings self.transition_on_daytime_switch: bool = bool( self.args.pop("transition_on_daytime_switch", False) @@ -354,11 +357,24 @@ async def initialize(self) -> None: ) ) + # doors + self.doors: dict[str, Any] = {} + + # enumerate sensors for doors + self.doors[EntityType.DOOR_WINDOW.idx] = self.listr( + self.args.pop( + "door", + await self.find_sensors( + EntityType.DOOR_WINDOW.prefix, self.room_name, states + ), + ) + ) + self.room = Room( name=self.room_name, room_lights=self.lights, motion=self.sensors[EntityType.MOTION.idx], - door_window=set(), + door_window=self.doors[EntityType.DOOR_WINDOW.idx], temperature=set(), push_data=dict(), appdaemon=self.get_ad_api(), @@ -440,6 +456,15 @@ async def initialize(self) -> None: ) ) + # set up event listener for door events + for door in self.doors[EntityType.DOOR_WINDOW.idx]: + listener.add( + self.listen_state( + self.door_event, + entity_id=door + ) + ) + self.args.update( { "room": self.room_name.capitalize(), @@ -604,6 +629,24 @@ async def motion_event( if event != "state_changed_detection": await self.refresh_timer() + async def door_event( + self, entity: str, attribute: str, old: str, new: str, kwargs: dict[str, Any] + ) -> None: + + self.lg( + f"{stack()[0][3]}: received '{old}' -> '{new}' event from {entity}", + level=logging.DEBUG + ) + if new == "off": + # calling motion event handler for door close + data: dict[str, Any] = {"entity_id": entity, "new": new, "old": old} + await self.motion_event("state_changed_detection", data,kwargs) + + else: + # Handle door open events by overriding the off delay + await self.refresh_timer(self.door_open_delay) + + def has_min_ad_version(self, required_version: str) -> bool: required_version = required_version if required_version else "4.0.7" return bool( @@ -630,7 +673,7 @@ async def clear_handles(self, handles: set[str] = None) -> None: self.lg(f"{stack()[0][3]}: cancelled scheduled callbacks", level=logging.DEBUG) - async def refresh_timer(self) -> None: + async def refresh_timer(self, delay_override: int = 0) -> None: """refresh delay timer.""" fnn = f"{stack()[0][3]}:" @@ -646,6 +689,9 @@ async def refresh_timer(self) -> None: # if no delay is set or delay = 0, lights will not switched off by AutoMoLi if delay := self.active.get("delay"): + if delay_override != 0: + delay = delay_override + self.lg( f"{fnn} {self.active = } | {delay = } | {self.dim = }", level=logging.DEBUG, @@ -720,6 +766,12 @@ async def is_blocked(self) -> bool: ) return True + # The doors, if closed we are blocked + for door in self.doors[EntityType.DOOR_WINDOW.idx]: + if (await self.get_state(door) == "off"): + self.lg(f"blocked due to closed door : {door}", level=logging.DEBUG) + return True + return False async def dim_lights(self, _: Any) -> None: