started entities and custom services in new namespace

This commit is contained in:
John Lancaster
2024-07-25 00:25:08 -05:00
parent 043402ad2f
commit a703fd15fb

View File

@@ -2,12 +2,14 @@ import datetime
import json import json
import logging import logging
import logging.config import logging.config
import traceback
from copy import deepcopy from copy import deepcopy
from typing import Dict, List from functools import wraps
from typing import Any, Dict, List
from appdaemon.entity import Entity from appdaemon.entity import Entity
from appdaemon.plugins.hass.hassapi import Hass from appdaemon.plugins.hass.hassapi import Hass
from appdaemon.plugins.mqtt.mqttapi import Mqtt from astral.location import Location
from . import console from . import console
from .model import ControllerStateConfig, RoomControllerConfig from .model import ControllerStateConfig, RoomControllerConfig
@@ -15,7 +17,7 @@ from .model import ControllerStateConfig, RoomControllerConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RoomController(Hass, Mqtt): class RoomController(Hass):
"""Class for linking room's lights with a motion sensor. """Class for linking room's lights with a motion sensor.
- Separate the turning on and turning off functions. - Separate the turning on and turning off functions.
@@ -34,12 +36,30 @@ class RoomController(Hass, Mqtt):
assert all(isinstance(s, ControllerStateConfig) for s in new), f'Invalid: {new}' assert all(isinstance(s, ControllerStateConfig) for s in new), f'Invalid: {new}'
self._room_config.states = new self._room_config.states = new
@property
@wraps(Location.time_at_elevation)
def time_at_elevation(self):
return self.AD.sched.location.time_at_elevation
@property
def state_entity(self) -> Entity:
return self.get_entity(f'{self.name}.state')
def initialize(self): def initialize(self):
self.set_namespace('controller')
self.logger = console.load_rich_config(self.name) self.logger = console.load_rich_config(self.name)
self.set_log_level('DEBUG')
self.register_service(f'{self.name}/activate', self.service_activate)
self.register_service(f'{self.name}/deactivate', self.service_deactivate)
self.app_entities = self.gather_app_entities() self.app_entities = self.gather_app_entities()
# self.log(f'entities: {self.app_entities}') # self.log(f'entities: {self.app_entities}')
self.refresh_state_times() self.refresh_state_times()
self.run_daily(callback=self.refresh_state_times, start='00:00:00') self.run_daily(callback=self.refresh_state_times, start='00:00:00')
self.log(f'Initialized [bold green]{type(self).__name__}[/]') self.log(f'Initialized [bold green]{type(self).__name__}[/]')
def terminate(self): def terminate(self):
@@ -82,46 +102,60 @@ class RoomController(Hass, Mqtt):
for state in self._room_config.states: for state in self._room_config.states:
if state.time is None and state.elevation is not None: if state.time is None and state.elevation is not None:
state.time = self.AD.sched.location.time_at_elevation( transition_time = self.time_at_elevation(
elevation=state.elevation, direction=state.direction elevation=state.elevation, direction=state.direction
).time() ).time()
elif isinstance(state.time, str): elif isinstance(state.time, str):
state.time = self.parse_time(state.time) transition_time = self.parse_time(state.time)
assert isinstance(state.time, datetime.time), f'Invalid time: {state.time}' assert isinstance(state.time, datetime.time), f'Invalid time: {state.time}'
self.states = sorted(self.states, key=lambda s: s.time, reverse=True)
# schedule the transitions
for state in self.states[::-1]:
# t: datetime.time = state['time']
t: datetime.time = state.time
try: try:
self.run_at( self.run_at(
callback=self.activate_any_on, callback=lambda cb_args: self.set_controller_scene(cb_args['state']),
start=t.strftime('%H:%M:%S'), start=transition_time.strftime('%H:%M:%S'),
cause='scheduled transition', state=state,
) )
except ValueError:
# happens when the callback time is in the past
pass
except Exception as e: except Exception as e:
self.log(f'Failed with {type(e)}: {e}') self.log(f'Failed with {type(e)}: {e}')
def current_state(self, now: datetime.time = None) -> ControllerStateConfig: def set_controller_scene(self, state: ControllerStateConfig):
try:
self.state_entity.set_state(attributes=state.model_dump())
except Exception:
self.logger.error(traceback.format_exc())
else:
self.log(f'Set controller state of {self.name}: {state.model_dump()}', level='DEBUG')
def current_state(self) -> ControllerStateConfig:
if self.sleep_bool(): if self.sleep_bool():
self.log('sleep: active') self.log('sleep: active', level='DEBUG')
if state := self.args.get('sleep_state'): if state := self.args.get('sleep_state'):
return ControllerStateConfig(**state) return ControllerStateConfig(**state)
else: else:
return ControllerStateConfig(scene={}) return ControllerStateConfig()
else: else:
now = now or self.get_now().time().replace(microsecond=0) attrs = self.state_entity.get_state('all')['attributes']
self.log(f'Getting state for {now.strftime("%I:%M:%S %p")}', level='DEBUG') return ControllerStateConfig.model_validate(attrs)
state = self._room_config.current_state(now) def current_scene(self, transition: int = None) -> Dict[str, Any]:
self.log(f'Current state: {state.time}', level='DEBUG') state = self.current_state()
return state if isinstance(state.scene, str):
return state.scene
elif isinstance(state.scene, dict):
return state.to_apply_kwargs(transition)
def service_activate(self, namespace: str, domain: str, service: str, kwargs: Dict[str, Any]):
scene = self.current_scene(transition=0)
if isinstance(scene, str):
self.turn_on(scene)
elif isinstance(scene, dict):
self.call_service('scene/apply', namespace='default', **scene)
def service_deactivate(self, namespace: str, domain: str, service: str, kwargs: Dict[str, Any]):
for e in self.app_entities:
self.turn_off(e)
def app_entity_states(self) -> Dict[str, str]: def app_entity_states(self) -> Dict[str, str]:
states = {entity: self.get_state(entity) for entity in self.app_entities} states = {entity: self.get_state(entity) for entity in self.app_entities}