3 """A PresenceDetector that is waitable. This is not part of
4 base_presence.py because I do not want to bring these dependencies
5 into that lower-level module (especially state_tracker).
11 from typing import Optional, Tuple
13 from overrides import overrides
16 from type.locations import Location
20 logger = logging.getLogger(__name__)
23 class WaitablePresenceDetectorWithMemory(state_tracker.WaitableAutomaticStateTracker):
25 This is a waitable class that keeps a PresenceDetector internally
26 and periodically polls it to detect changes in presence in a
27 particular location. Example suggested usage pattern:
29 detector = waitable_presence.WaitablePresenceDetectorWithMemory(60.0)
31 changed = detector.wait(timeout=60 * 5) # or, None for "forever"
32 (someone_is_home, since) = detector.is_someone_home()
36 f'someone_is_home={someone_is_home}, since={since}, changed={changed}'
42 override_update_interval_sec: float = 60.0,
43 override_location: Location = site_config.get_location(),
45 self.last_someone_is_home: Optional[bool] = None
46 self.someone_is_home: Optional[bool] = None
47 self.everyone_gone_since: Optional[datetime.datetime] = None
48 self.someone_home_since: Optional[datetime.datetime] = None
49 self.location = override_location
50 self.detector: base_presence.PresenceDetection = (
51 base_presence.PresenceDetection()
55 'poll_presence': override_update_interval_sec,
56 'check_detector': override_update_interval_sec * 5,
64 now: datetime.datetime,
65 last_invocation: Optional[datetime.datetime],
67 if update_id == 'poll_presence':
68 self.poll_presence(now)
69 elif update_id == 'check_detector':
72 raise Exception(f'Unknown update type {update_id} in {__file__}')
74 def poll_presence(self, now: datetime.datetime) -> None:
75 logger.debug(f'Checking presence in {self.location} now...')
76 self.detector.update()
77 if self.detector.is_anyone_in_location_now(self.location):
78 self.someone_is_home = True
79 self.someone_home_since = now
81 self.someone_is_home = False
82 self.everyone_gone_since = now
83 if self.someone_is_home != self.last_someone_is_home:
84 self.something_changed()
85 self.last_someone_is_home = self.someone_is_home
87 def check_detector(self) -> None:
88 if len(self.detector.dark_locations) > 0:
89 logger.debug('PresenceDetector is incomplete; trying to reinitialize...')
90 self.detector = base_presence.PresenceDetection()
92 def is_someone_home(self) -> Tuple[bool, datetime.datetime]:
93 """Returns a tuple of a bool that indicates whether someone is home
94 and a datetime that indicates how long either someone has been
95 home or no one has been home.
98 if self.someone_is_home is None:
99 raise Exception("Too Soon!")
100 if self.someone_is_home:
101 return (True, self.someone_home_since)
103 return (False, self.everyone_gone_since)