button and door converted

This commit is contained in:
John Lancaster
2024-07-27 14:35:07 -05:00
parent 9ce8432bba
commit 1c5edf6cc7
3 changed files with 70 additions and 138 deletions

View File

@@ -1,53 +1,28 @@
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from logging import Logger from typing import Any, Dict
from typing import TYPE_CHECKING, List
from appdaemon.plugins.mqtt.mqttapi import Mqtt from appdaemon.adapi import ADAPI
from . import console
from .model import ButtonConfig
if TYPE_CHECKING:
from room_control import RoomController
@dataclass(init=False) @dataclass
class Button(Mqtt): class Button:
button: str | List[str] adapi: ADAPI
rich: bool = False button_name: str
config: ButtonConfig
logger: Logger
async def initialize(self): def __post_init__(self):
self.app: 'RoomController' = await self.get_app(self.args['app']) self.log = self.adapi.log
self.logger = console.load_rich_config(self.app.name, type(self).__name__) topic = f'zigbee2mqtt/{self.button_name}'
self.config = ButtonConfig(**self.args) self.adapi.listen_event(
self.log(f'Connected to AD app [room]{self.app.name}[/]', level='DEBUG')
self.button = self.config.button
self.setup_buttons(self.button)
def setup_buttons(self, buttons):
if isinstance(buttons, list):
for button in buttons:
self.setup_button(button)
else:
self.setup_button(buttons)
def setup_button(self, name: str):
topic = f'zigbee2mqtt/{name}'
# self.mqtt_subscribe(topic, namespace='mqtt')
self.listen_event(
self.handle_button, self.handle_button,
'MQTT_MESSAGE', 'MQTT_MESSAGE',
topic=topic, topic=topic,
namespace='mqtt', namespace='mqtt',
button=name, button=self.button_name,
) )
self.log(f'MQTT topic [topic]{topic}[/] controls app [room]{self.app.name}[/]') self.log(f'MQTT topic [topic]{topic}[/] controls [room]{self.adapi.name}[/]')
def handle_button(self, event_name, data, kwargs): def handle_button(self, event_name: str, data: Dict[str, Any], kwargs: Dict[str, Any]):
try: try:
payload = json.loads(data['payload']) payload = json.loads(data['payload'])
action = payload['action'] action = payload['action']
@@ -58,19 +33,7 @@ class Button(Mqtt):
else: else:
if isinstance(action, str) and action != '': if isinstance(action, str) and action != '':
self.log(f'Action: [yellow]{action}[/]') self.log(f'Action: [yellow]{action}[/]')
self.handle_action(action) if action == 'single':
self.adapi.call_service(
def handle_action(self, action: str): f'{self.adapi.name}/toggle', namespace='controller', cause='button'
if action == 'single': )
state = self.get_state(self.args['ref_entity'])
kwargs = {'kwargs': {'cause': f'button single click: toggle while {state}'}}
if manual_entity := self.args.get('manual_mode'):
self.set_state(entity_id=manual_entity, state='off')
if state == 'on':
self.app.deactivate(**kwargs)
else:
self.app.activate(**kwargs)
else:
pass

View File

@@ -1,26 +1,25 @@
from logging import Logger from dataclasses import dataclass
from typing import TYPE_CHECKING from logging import Logger, LoggerAdapter
from appdaemon.plugins.hass.hassapi import Hass from appdaemon.adapi import ADAPI
from . import console
if TYPE_CHECKING:
from room_control import RoomController
class Door(Hass): @dataclass
app: 'RoomController' class Door:
logger: Logger adapi: ADAPI
entity_id: str
async def initialize(self): def __post_init__(self):
self.app: 'RoomController' = await self.get_app(self.args['app']) self.logger = LoggerAdapter(
self.logger = console.load_rich_config(room=self.app.name, component=type(self).__name__) self.adapi.logger.logger.getChild('door'), self.adapi.logger.extra
self.log(f'Connected to AD app [room]{self.app.name}[/]', level='DEBUG') )
await self.listen_state( self.adapi.listen_state(
self.app.activate_all_off, callback=lambda *args, **kwargs: self.call_service(
entity_id=self.args['door'], f'{self.adapi.name}/activate_all_off', namespace='controller'
),
entity_id=self.entity_id,
new='on', new='on',
cause='door open', cause='door open',
) )
self.logger.debug(f'Initialized door for [room]{self.adapi.name}[/]')

View File

@@ -1,9 +1,7 @@
import datetime import datetime
import json
import logging import logging
import logging.config import logging.config
import traceback import traceback
from copy import deepcopy
from functools import wraps from functools import wraps
from typing import Any, Dict, List, Set from typing import Any, Dict, List, Set
@@ -12,6 +10,8 @@ from appdaemon.plugins.hass.hassapi import Hass
from astral.location import Location from astral.location import Location
from . import console from . import console
from .button import Button
from .door import Door
from .model import ControllerStateConfig, RoomControllerConfig from .model import ControllerStateConfig, RoomControllerConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -32,6 +32,7 @@ class RoomController(Hass):
- <name>/activate - <name>/activate
- <name>/activate_all_off - <name>/activate_all_off
- <name>/deactivate - <name>/deactivate
- <name>/toggle
""" """
@@ -69,13 +70,22 @@ class RoomController(Hass):
self.register_service( self.register_service(
f'{self.name}/deactivate', self._service_deactivate, namespace='controller' f'{self.name}/deactivate', self._service_deactivate, namespace='controller'
) )
self.register_service(f'{self.name}/toggle', self._service_toggle, namespace='controller')
# This needs to come after this first call of refresh_state_times # This needs to come after this first call of refresh_state_times
self.app_entities = self.get_app_entities() self.app_entities = self.get_app_entities()
self.log(f'entities: {self.app_entities}', level='DEBUG') self.log(f'entities: {self.app_entities}', level='DEBUG')
if button := self.args.get('button'):
if isinstance(button, str):
self.button = Button(self, button_name=button)
if door := self.args.get('door'):
if isinstance(door, str):
self.log('door--')
self.door = Door(self, entity_id=door)
self.log(f'Initialized [bold green]{type(self).__name__}[/]') self.log(f'Initialized [bold green]{type(self).__name__}[/]')
self.activate_all_off(test_kwarg='abc123')
def terminate(self): def terminate(self):
self.log('[bold red]Terminating[/]', level='DEBUG') self.log('[bold red]Terminating[/]', level='DEBUG')
@@ -152,13 +162,6 @@ class RoomController(Hass):
# self.log(f'Current state: {state.model_dump(exclude_none=True)}', level='DEBUG') # self.log(f'Current state: {state.model_dump(exclude_none=True)}', level='DEBUG')
return state return state
# def current_scene(self, transition: int = None) -> Dict[str, Any]:
# state = self.current_state()
# if isinstance(state.scene, str):
# return state.scene
# elif isinstance(state.scene, dict):
# return state.to_apply_kwargs(transition)
def activate(self, **kwargs): def activate(self, **kwargs):
self.call_service(f'{self.name}/activate', namespace='controller', **kwargs) self.call_service(f'{self.name}/activate', namespace='controller', **kwargs)
@@ -173,12 +176,24 @@ class RoomController(Hass):
self.call_service('scene/apply', **scene) self.call_service('scene/apply', **scene)
# scene = state.to_apply_kwargs(transition=0) # scene = state.to_apply_kwargs(transition=0)
def activate_any_on(self, **kwargs):
"""Activate if any of the entities are on. Args and kwargs are passed directly to self.activate()"""
self.call_service(f'{self.name}/activate_any_on', namespace='controller', **kwargs)
def _service_activate_any_on(
self, namespace: str, domain: str, service: str, kwargs: Dict[str, Any]
):
if self.any_on() and not self.manual_mode():
self.activate(**kwargs)
def activate_all_off(self, **kwargs): def activate_all_off(self, **kwargs):
"""Activate if all of the entities are off. Args and kwargs are passed directly to self.activate()""" """Activate if all of the entities are off. Args and kwargs are passed directly to self.activate()"""
self.call_service(f'{self.name}/activate_all_off', namespace='controller', **kwargs) self.call_service(f'{self.name}/activate_all_off', namespace='controller', **kwargs)
def _service_activate_all_off(self, namespace: str, domain: str, service: str, kwargs: Dict[str, Any]): def _service_activate_all_off(
if self.all_off(): self, namespace: str, domain: str, service: str, kwargs: Dict[str, Any]
):
if self.all_off() and not self.manual_mode():
self.activate(**kwargs) self.activate(**kwargs)
def deactivate(self, **kwargs): def deactivate(self, **kwargs):
@@ -190,6 +205,15 @@ class RoomController(Hass):
for e in self.app_entities: for e in self.app_entities:
self.turn_off(e) self.turn_off(e)
def toggle(self, **kwargs):
self.call_service(f'{self.name}/toggle', namespace='controller', **kwargs)
def _service_toggle(self, namespace: str, domain: str, service: str, kwargs: Dict[str, Any]):
if self.any_on():
self.deactivate(**kwargs)
else:
self.activate(**kwargs)
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}
return states return states
@@ -224,16 +248,6 @@ class RoomController(Hass):
else: else:
return False 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')
def off_duration(self, now: datetime.time = None) -> datetime.timedelta: def off_duration(self, now: datetime.time = None) -> datetime.timedelta:
"""Determines the time that the motion sensor has to be clear before deactivating """Determines the time that the motion sensor has to be clear before deactivating
@@ -251,47 +265,3 @@ class RoomController(Hass):
else: else:
now = now or self.get_now().time() now = now or self.get_now().time()
return self._room_config.current_off_duration(now) return self._room_config.current_off_duration(now)
# def activate(self, entity=None, attribute=None, old=None, new=None, kwargs=None):
# if kwargs is not None:
# cause = kwargs.get('cause', 'unknown')
# else:
# cause = 'unknown'
# self.log(f'Activating: {cause}')
# scene_kwargs = self.current_state().to_apply_kwargs(transition=0)
# if isinstance(scene_kwargs, str):
# self.turn_on(scene_kwargs)
# self.log(f'Turned on scene: {scene_kwargs}')
# elif isinstance(scene_kwargs, dict):
# self.call_service('scene/apply', **scene_kwargs)
# self.log(f'Applied scene:\n{json.dumps(scene_kwargs, indent=2)}', level='DEBUG')
# elif scene_kwargs is None:
# self.log('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()
# else:
# self.log(f'ERROR: unknown scene: {scene_kwargs}')
def activate_any_on(self, *args, **kwargs):
"""Activate if any of the entities are on. Args and kwargs are passed directly to self.activate()"""
if self.any_on() and not self.manual_mode():
self.activate(*args, **kwargs)
else:
self.log('Skipped activating - everything is off')
def toggle_activate(self, *args, **kwargs):
if self.any_on():
self.deactivate(*args, **kwargs)
else:
self.activate(*args, **kwargs)
def deactivate(self, entity=None, attribute=None, old=None, new=None, kwargs=None):
cause = kwargs.get('cause', 'unknown')
self.log(f'Deactivating: {cause}')
for e in self.app_entities:
self.turn_off(e)
self.log(f'Turned off {e}')