From 6aa9154a78f846ebc46956b9a9b25ff2b599674f Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Sat, 15 Apr 2023 15:15:37 -0500 Subject: [PATCH] controllers start, kind of working --- apps/controller.py | 145 +++++++++++++++++++++++++++++++++++++++++++++ apps/kitchen.yaml | 21 +++++++ apps/rooms.yaml | 76 ++++++++++++------------ 3 files changed, 204 insertions(+), 38 deletions(-) create mode 100644 apps/controller.py create mode 100644 apps/kitchen.yaml diff --git a/apps/controller.py b/apps/controller.py new file mode 100644 index 0000000..9bcc2f5 --- /dev/null +++ b/apps/controller.py @@ -0,0 +1,145 @@ +from dataclasses import dataclass, field +from datetime import timedelta +from typing import List + +from appdaemon.adapi import ADAPI +from appdaemon.entity import Entity + +from appdaemon.plugins.hass.hassapi import Hass + + +@dataclass(init=False) +class ControllerBase(Hass): + entities: List[Entity] + + def initialize(self): + # assign fields + for arg, val in self.args.items(): + if arg not in ['class', 'module']: + setattr(self, arg, val) + self.log(f'Set {arg} to {val}') + + for entity in self.entities: + assert self.entity_exists(entity), f'{entity} does not exist' + + self.entities = [self.get_entity(e) for e in self.entities] + + self.log(f'Initialized controller for {[e.friendly_name for e in self.entities]}') + +@dataclass(init=False) +class ControllerRoom(ControllerBase): + def initialize(self): + super().initialize() + self.register_service(f'{self.name}/activate', self.activate) + self.register_service(f'{self.name}/deactivate', self.deactivate) + + def activate(self, namespace: str = None, domain: str = None, service=None, kwargs=None): + for entity in self.entities: + self.log(f'Turning on {entity.name}') + entity.turn_on() + + def deactivate(self, namespace: str = None, domain: str = None, service=None, kwargs=None): + for entity in self.entities: + self.log(f'Turning off {entity.name}') + entity.turn_off() + + @property + def state(self) -> bool: + return any([e.get_state() == 'on' for e in self.entities]) + +@dataclass(init=False) +class ControllerMotion(ControllerBase): + room: ControllerRoom + off_duration: timedelta + + def initialize(self): + super().initialize() + # self.log('Motion Controller init') + + # convert room to App + self.room: ControllerRoom = self.get_app(self.room) + + # convert off_duration + try: + hours, minutes, seconds = map(int, self.args['off_duration'].split(':')) + self.off_duration = timedelta(hours=hours, minutes=minutes, seconds=seconds) + except Exception: + self.off_duration = timedelta() + + self.sync_state() + + self.listen_state(self.sync_state, [e.entity_id for e in self.entities]) + + @property + def current_state(self) -> bool: + return any(e.get_state() == 'on' for e in self.entities) + + def sync_state(self, + entity=None, + attribute=None, + old=None, + new=None, + kwargs=None): + self.log(f'Syncing state, current state: {self.current_state}') + if self.current_state: + self.room.activate() + self.listen_motion_off() + else: + self.room.deactivate() + self.listen_motion_on() + + def listen_motion_on(self): + self.listen_state( + callback=self.callback_motion_on, + entity_id=[e.entity_id for e in self.entities], + new='on', + oneshot=True + ) + self.log(f'Waiting for motion on {[e.friendly_name for e in self.entities]} to turn on room {self.room.name}') + + def listen_motion_off(self): + self.listen_state( + callback=self.callback_motion_off, + entity_id=[e.entity_id for e in self.entities], + new='off', + duration=self.off_duration.total_seconds(), + oneshot=True + ) + self.log(f'Waiting for motion off {[e.friendly_name for e in self.entities]} for {self.off_duration}') + + def callback_motion_on(self, entity, attribute, old, new, kwargs): + self.log(f'Motion detected on {self.friendly_name(entity)}') + self.room.activate() + + def callback_motion_off(self, entity, attribute, old, new, kwargs): + self.log(f'Motion stopped on {self.friendly_name(entity)} for {self.off_duration}') + self.room.deactivate() + +@dataclass(init=False) +class ControllerButton(Hass): + room: ControllerRoom + buttons: List[str] + + def initialize(self): + self.buttons = self.args['buttons'] + + # convert room to App + self.room: ControllerRoom = self.get_app(self.args['room']) + + for button in self.buttons: + self.listen_event( + self.callback_button, + event='deconz_event', + id=button, + ) + self.log(f'Listening to presses on button ID={button}') + + def callback_button(self, event_name, data, kwargs): + # single press + if data['event'] == 1002: + self.log(f"Single press: {data['id']}") + if self.room.state: + self.room.deactivate() + else: + self.room.activate() + \ No newline at end of file diff --git a/apps/kitchen.yaml b/apps/kitchen.yaml new file mode 100644 index 0000000..ad3c9c4 --- /dev/null +++ b/apps/kitchen.yaml @@ -0,0 +1,21 @@ +kitchen: + module: controller + class: ControllerRoom + entities: + - light.kitchen + +kitchen_motion: + module: controller + class: ControllerMotion + room: kitchen + off_duration: 00:01:00 + entities: + - binary_sensor.motion_kitchen + # - binary_sensor.motion_bedroom + +kitchen_button: + module: controller + class: ControllerButton + room: kitchen + buttons: + - kitchen \ No newline at end of file diff --git a/apps/rooms.yaml b/apps/rooms.yaml index bc04614..7543c45 100755 --- a/apps/rooms.yaml +++ b/apps/rooms.yaml @@ -170,44 +170,44 @@ living_room: color_name: 'red' brightness_pct: 50 -kitchen: - module: basic_motion - class: MotionLight - entity: light.kitchen - sensor: binary_sensor.motion_kitchen - off_duration: '00:10:00' - button: kitchen_switch - scene: - - time: sunrise - scene: - light.kitchen: - state: on - color_temp: 200 - brightness_pct: 10 - - time: '12:00:00' - scene: - light.kitchen: - state: on - color_temp: 300 - brightness_pct: 30 - - time: sunset - scene: - light.kitchen: - state: on - color_temp: 450 - brightness_pct: 40 - - time: '22:00:00' - off_duration: '00:02:00' - scene: - light.kitchen: - state: on - color_temp: 650 - brightness_pct: 10 - sleep: input_boolean.sleeping - sleep_scene: - light.kitchen: - state: on - brightness_pct: 1 +# kitchen: +# module: basic_motion +# class: MotionLight +# entity: light.kitchen +# sensor: binary_sensor.motion_kitchen +# off_duration: '00:10:00' +# button: kitchen_switch +# scene: +# - time: sunrise +# scene: +# light.kitchen: +# state: on +# color_temp: 200 +# brightness_pct: 10 +# - time: '12:00:00' +# scene: +# light.kitchen: +# state: on +# color_temp: 300 +# brightness_pct: 30 +# - time: sunset +# scene: +# light.kitchen: +# state: on +# color_temp: 450 +# brightness_pct: 40 +# - time: '22:00:00' +# off_duration: '00:02:00' +# scene: +# light.kitchen: +# state: on +# color_temp: 650 +# brightness_pct: 10 +# sleep: input_boolean.sleeping +# sleep_scene: +# light.kitchen: +# state: on +# brightness_pct: 1 # patio: