changed state determination
This commit is contained in:
@@ -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(
|
else:
|
||||||
((
|
raise ValueError(f'Invalid sun direction: {state["direction"]}')
|
||||||
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]:
|
state['time'] = self.AD.sched.location.time_at_elevation(
|
||||||
assert self.is_stateful
|
elevation=elevation, direction=dir
|
||||||
for dt, scene, off_duration in self.settings()[::-1]:
|
).time()
|
||||||
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:
|
|
||||||
self.log('Setting last scene')
|
|
||||||
return self.settings()[-1]
|
|
||||||
|
|
||||||
def current_scene(self) -> Union[str, Dict]:
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user