changed state determination

This commit is contained in:
John Lancaster
2023-04-27 23:05:31 -05:00
parent ad2eda75cc
commit aa5c0d0092

View File

@@ -2,6 +2,7 @@ from copy import deepcopy
from datetime import time, timedelta from datetime import time, timedelta
from typing import Dict, List, Tuple, Union from typing import Dict, List, Tuple, Union
import astral
from appdaemon.plugins.hass.hassapi import Hass from appdaemon.plugins.hass.hassapi import Hass
from continuous import Continuous from continuous import Continuous
@@ -18,6 +19,7 @@ class MotionLight(Hass):
def initialize(self): def initialize(self):
self.state_change_handle = self.listen_state(self.state_change, self.entity) self.state_change_handle = self.listen_state(self.state_change, self.entity)
self.states = self.parse_states()
self.sync_state() self.sync_state()
self.schedule_transitions() self.schedule_transitions()
self.run_daily(callback=self.schedule_transitions, start='00:00:00') self.run_daily(callback=self.schedule_transitions, start='00:00:00')
@@ -60,10 +62,10 @@ class MotionLight(Hass):
""" """
if self.sleeping_active: duration_str = self.current_state().get(
_, duration_str = self.sleep_scene() 'off_duration',
else: self.args.get('off_duration', '00:00:00')
_, _, duration_str = self.current_setting() )
try: try:
hours, minutes, seconds = map(int, duration_str.split(':')) hours, minutes, seconds = map(int, duration_str.split(':'))
@@ -113,58 +115,48 @@ class MotionLight(Hass):
def is_stateful(self): def is_stateful(self):
return 'scene' in self.args and isinstance(self.args['scene'], (list, dict)) return 'scene' in self.args and isinstance(self.args['scene'], (list, dict))
def settings(self) -> List[Tuple[time, Union[Dict, str], timedelta]]: def parse_states(self):
"""Gets the settings for all the scenes based on time of day def gen():
for state in deepcopy(self.args['scene']):
if (time := state.get('time')):
state['time'] = self.parse_time(time)
"Settings" refers to `tuple` groups that consist of elif isinstance((elevation := state.get('elevation')), (int, float)):
- Scene start time assert 'direction' in state, f'State needs a direction if it has an elevation'
- Dictionary of states or scene entity name
- Time for motion to be off
Returns: if state['direction'] == 'rising':
List[Tuple[time, Union[Dict, str], timedelta]]: Sorted list of settings dir = astral.SunDirection.RISING
""" elif state['direction'] == 'setting':
assert self.is_stateful dir = astral.SunDirection.SETTING
return sorted(
((
self.parse_time(s['time']),
s['scene'],
s.get('off_duration', self.args.get('off_duration', '00:00:00'))
)
for s in self.args['scene']),
)
def current_setting(self) -> Tuple[time, Dict, timedelta]:
assert self.is_stateful
for dt, scene, off_duration in self.settings()[::-1]:
if dt <= self.time():
# self.log(f'Active scene: {str(self.time())[:8]} {str(dt)[:8]}, {scene}, {off_duration}')
return dt, scene, off_duration
else: else:
self.log('Setting last scene') raise ValueError(f'Invalid sun direction: {state["direction"]}')
return self.settings()[-1]
def current_scene(self) -> Union[str, Dict]: state['time'] = self.AD.sched.location.time_at_elevation(
elevation=elevation, direction=dir
).time()
else:
raise ValueError(f'Missing time')
yield state
states = sorted(gen(), key=lambda s: s['time'])
return states
def current_state(self, time: time = None):
if self.sleeping_active: if self.sleeping_active:
return self.sleep_scene()[0] if (state := self.args.get['sleep_state']):
return state
else: else:
if self.is_stateful: time = time or self.get_now().time()
dt, scene, _ = self.current_setting() for state in self.states[::-1]:
self.log(f'Current scene: {str(dt)[:8]}, {scene}') if state['time'] <= time:
return scene return state
else: else:
return self.args['scene'] return self.states[-1]
def sleep_scene(self) -> Tuple[Dict, timedelta]: def current_scene(self, time: time = None):
if (scene := self.args.get('sleep_scene')): return self.current_state(time=time)['scene']
scene = deepcopy(scene)
if isinstance(scene, dict):
off_duration = scene.pop('off_duration', '00:00:00')
else:
off_duration = '00:00:00'
return scene, off_duration
else:
return None, None
@property @property
def app_entities(self): def app_entities(self):
@@ -354,9 +346,8 @@ class MotionLight(Hass):
self.log(f'Turned off {entity}') self.log(f'Turned off {entity}')
def schedule_transitions(self, *args, **kwargs): def schedule_transitions(self, *args, **kwargs):
# times, scenes, offs = zip(*self.settings()) for state in self.states:
for dt, scene, off_duration in self.settings(): dt = str(state['time'])[:8]
dt = str(dt)[:8]
self.log(f'Scheduling transition at: {dt}') self.log(f'Scheduling transition at: {dt}')
try: try:
self.run_at(callback=self.activate_any_on, start=dt) self.run_at(callback=self.activate_any_on, start=dt)