controllers start, kind of working
This commit is contained in:
145
apps/controller.py
Normal file
145
apps/controller.py
Normal file
@@ -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()
|
||||
|
||||
21
apps/kitchen.yaml
Normal file
21
apps/kitchen.yaml
Normal file
@@ -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
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user