async working...?

This commit is contained in:
John Lancaster
2023-11-25 17:37:47 -06:00
parent c9cc841d58
commit a00f10a967
4 changed files with 133 additions and 101 deletions

View File

@@ -1,8 +1,11 @@
import asyncio
from copy import deepcopy
from datetime import time, timedelta
from typing import List
import appdaemon.utils as utils
import astral
from appdaemon.entity import Entity
from appdaemon.plugins.hass.hassapi import Hass
from appdaemon.plugins.mqtt.mqttapi import Mqtt
@@ -17,40 +20,63 @@ class RoomController(Hass, Mqtt):
- When the light comes on, check if it's attributes match what they should, given the time.
"""
def initialize(self):
self.app_entities = self.gather_app_entities()
self.refresh_state_times()
self.run_daily(callback=self.refresh_state_times, start='00:00:00')
async def initialize(self):
self.app_entities = await self.gather_app_entities()
self.log(f'entities: {self.app_entities}')
await self.refresh_state_times()
await self.run_daily(callback=self.refresh_state_times, start='00:00:00')
if (ha_button := self.args.get('ha_button')):
self.log(f'Setting up input button: {self.friendly_name(ha_button)}')
self.listen_state(callback=self.activate_any_on, entity_id=ha_button)
# if (ha_button := self.args.get('ha_button')):
# self.log(f'Setting up input button: {self.friendly_name(ha_button)}')
# self.listen_state(callback=self.activate_any_on, entity_id=ha_button)
def refresh_state_times(self, *args, **kwargs):
async def gather_app_entities(self) -> List[str]:
"""Returns a list of all the entities involved in any of the states
"""
async def async_generator():
for settings in deepcopy(self.args['states']):
if (scene := settings.get('scene')):
if isinstance(scene, str):
assert scene.startswith('scene.'), f"Scene definition must start with 'scene.' for app {self.name}"
entity: Entity = self.get_entity(scene)
entity_state = await entity.get_state('all')
attributes = entity_state['attributes']
for entity in attributes['entity_id']:
yield entity
else:
for key in scene.keys():
yield key
else:
yield self.args['entity']
entities = [e async for e in async_generator()]
return set(entities)
async def refresh_state_times(self, *args, **kwargs):
"""Resets the `self.states` attribute to a newly parsed version of the states.
Parsed states have an absolute time for a certain day.
"""
# re-parse the state strings into times for the current day
self.states = self.parse_states()
self.states = await self.parse_states()
# schedule the transitions
for state in self.states:
dt = str(state['time'])[:8]
self.log(f'Scheduling transition at: {dt}')
try:
self.run_at(callback=self.activate_any_on, start=dt)
await self.run_at(callback=self.activate_any_on, start=dt)
except ValueError:
# happens when the callback time is in the past
pass
except Exception as e:
self.log(f'Failed with {type(e)}: {e}')
def parse_states(self):
def gen():
async def parse_states(self):
async def gen():
for state in deepcopy(self.args['states']):
if (time := state.get('time')):
state['time'] = self.parse_time(time)
state['time'] = await self.parse_time(time)
elif isinstance((elevation := state.get('elevation')), (int, float)):
assert 'direction' in state, f'State needs a direction if it has an elevation'
@@ -71,17 +97,20 @@ class RoomController(Hass, Mqtt):
yield state
states = sorted(gen(), key=lambda s: s['time'])
states = [s async for s in gen()]
states = sorted(states, key=lambda s: s['time'])
return states
def current_state(self, time: time = None):
if self.sleep_bool:
async def current_state(self, time: time = None):
if (await self.sleep_bool()):
if (state := self.args.get('sleep_state')):
return state
else:
return {}
else:
time = time or self.get_now().time()
now = await self.get_now()
self.log(f'Getting state for datetime: {now}')
time = time or (await self.get_now()).time()
for state in self.states[::-1]:
if state['time'] <= time:
self.log(f'Selected state from {state["time"]}')
@@ -89,26 +118,10 @@ class RoomController(Hass, Mqtt):
else:
return self.states[-1]
def current_scene(self, time: time = None):
if (state := self.current_state(time=time)) is not None:
async def current_scene(self, time: time = None):
if (state := (await self.current_state(time=time))) is not None:
return state['scene']
def gather_app_entities(self) -> List[str]:
"""Returns a list of all the entities involved in any of the states
"""
def gen():
for settings in deepcopy(self.args['states']):
# dt = self.parse_time(settings.pop('time'))
if (scene := settings.get('scene')):
if isinstance(scene, str):
yield from self.get_entity(scene).get_state('all')['attributes']['entity_id']
else:
yield from scene.keys()
else:
yield self.args['entity']
return list(set(gen()))
@property
def all_off(self) -> bool:
""""All off" is the logic opposite of "any on"
@@ -127,26 +140,23 @@ class RoomController(Hass, Mqtt):
"""
return any(self.get_state(entity) == 'on' for entity in self.app_entities)
@property
def sleep_bool(self) -> bool:
async def sleep_bool(self) -> bool:
if (sleep_var := self.args.get('sleep')):
return self.get_state(sleep_var) == 'on'
return (await self.get_state(sleep_var)) == 'on'
else:
# self.log('WARNING')
return False
@sleep_bool.setter
def sleep_bool(self, val) -> bool:
if (sleep_var := self.args.get('sleep')):
if isinstance(val, str):
self.set_state(sleep_var, state=val)
elif isinstance(val, bool):
self.set_state(sleep_var, state='on' if val else 'off')
else:
raise ValueError('Sleep variable is undefined')
# @sleep_bool.setter
# def sleep_bool(self, val) -> bool:
# if (sleep_var := self.args.get('sleep')):
# if isinstance(val, str):
# self.set_state(sleep_var, state=val)
# elif isinstance(val, bool):
# self.set_state(sleep_var, state='on' if val else 'off')
# else:
# raise ValueError('Sleep variable is undefined')
@property
def off_duration(self) -> timedelta:
async def off_duration(self) -> timedelta:
"""Determines the time that the motion sensor has to be clear before deactivating
Priority:
@@ -156,8 +166,8 @@ class RoomController(Hass, Mqtt):
- Sleep - 0
"""
duration_str = self.current_state().get(
current_state = await self.current_state()
duration_str = current_state.get(
'off_duration',
self.args.get('off_duration', '00:00:00')
)
@@ -168,9 +178,9 @@ class RoomController(Hass, Mqtt):
except Exception:
return timedelta()
def activate(self, *args, cause: str = 'unknown', **kwargs):
async def activate(self, *args, cause: str = 'unknown', **kwargs):
self.log(f'Activating: {cause}')
scene = self.current_scene()
scene = await self.current_scene()
if isinstance(scene, str):
self.turn_on(scene)
@@ -188,24 +198,24 @@ class RoomController(Hass, Mqtt):
elif scene is None:
self.log(f'No scene, ignoring...')
# Need to act as if the light had just turned off to reset the motion (and maybe other things?)
self.callback_light_off()
# self.callback_light_off()
else:
self.log(f'ERROR: unknown scene: {scene}')
def activate_all_off(self, *args, **kwargs):
async def activate_all_off(self, *args, **kwargs):
"""Activate if all of the entities are off
"""
if self.all_off:
self.log(f'Activate all off kwargs: {kwargs}')
self.activate(*args, **kwargs)
await self.activate(*args, **kwargs)
else:
self.log(f'Skipped activating - everything is not off')
def activate_any_on(self, *args, **kwargs):
async def activate_any_on(self, *args, **kwargs):
"""Activate if any of the entities are on
"""
if self.any_on:
self.activate(*args, **kwargs)
await self.activate(*args, **kwargs)
else:
self.log(f'Skipped activating - everything is off')