From af8d13bfce866eb6cb8b64ead7aef8d57f056d52 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Sun, 28 Jan 2024 09:04:54 -0600 Subject: [PATCH] pre-merge (kinda fucked up before) --- appdaemon.yaml | 2 -- apps/apps.yaml | 4 ++-- apps/cubes/cube.py | 17 ++++++--------- apps/hello.py | 24 ++++++++++++++++++++- apps/leaving.py | 18 +++++++--------- apps/rich_logging.py | 33 ++++++++++++++++++++++++++++ apps/room_control | 2 +- apps/rooms/bathroom.yaml | 12 +++++------ apps/rooms/bedroom.yaml | 30 +++++++++++++------------- apps/rooms/closet.yaml | 8 +++---- apps/rooms/kitchen.yaml | 28 ++++++++++++------------ apps/rooms/living_room.yaml | 30 +++++++++++++------------- apps/scene_detect.py | 11 +++++++++- apps/sleep.py | 43 ++++++++++++++++++++----------------- docker-compose.yml | 2 +- 15 files changed, 162 insertions(+), 102 deletions(-) create mode 100644 apps/rich_logging.py diff --git a/appdaemon.yaml b/appdaemon.yaml index 4d4d345..e4212af 100755 --- a/appdaemon.yaml +++ b/appdaemon.yaml @@ -1,6 +1,4 @@ appdaemon: - invalid_yaml_warnings: 0 - missing_app_warnings: 0 latitude: 30.250968 longitude: -97.748193 elevation: 150 diff --git a/apps/apps.yaml b/apps/apps.yaml index a0a58d7..57c45a9 100644 --- a/apps/apps.yaml +++ b/apps/apps.yaml @@ -13,9 +13,9 @@ scene_detect: module: scene_detect class: MotionCanceller scene: bedsport - app: bedroom + app: bedroom_motion -scene_detect: +scene_detect2: module: scene_detect class: MotionCanceller scene: in_bed diff --git a/apps/cubes/cube.py b/apps/cubes/cube.py index 13b8e06..01634c0 100644 --- a/apps/cubes/cube.py +++ b/apps/cubes/cube.py @@ -1,15 +1,15 @@ import json from copy import deepcopy +from appdaemon.plugins.hass.hassapi import Hass from appdaemon.plugins.mqtt.mqttapi import Mqtt -class AqaraCube(Mqtt): +class AqaraCube(Hass, Mqtt): def initialize(self): - self.set_namespace('mqtt') topic = f'zigbee2mqtt/{self.args["cube"]}' - self.mqtt_subscribe(topic) - self.listen_event(self.handle_event, "MQTT_MESSAGE", topic=topic) + self.mqtt_subscribe(topic, namespace='mqtt') + self.listen_event(self.handle_event, "MQTT_MESSAGE", topic=topic, namespace='mqtt') self.log(f'Listening for cube events on: {topic}') self.app = self.get_app(self.args['app']) @@ -39,12 +39,9 @@ class AqaraCube(Mqtt): self.call_service('scene/turn_on', entity_id=description, namespace='default') self.log(f'Turned on {description}') - elif description == 'toggle': - cause = f'{action} from {self.args["cube"]}' - if self.app.entity_state: - self.app.deactivate(cause=cause) - else: - self.app.activate(cause=cause) + elif description.startswith('toggle'): + cause = f'{self.args["cube"]} {action}' + self.app.toggle_activate(kwargs={'cause': cause}) # def handle_rotate_right(self, payload): # self.log(f'{self.args["cube"]}: Rotate right') diff --git a/apps/hello.py b/apps/hello.py index 1d71881..8035937 100644 --- a/apps/hello.py +++ b/apps/hello.py @@ -1,7 +1,29 @@ +from datetime import datetime + from appdaemon.plugins.hass.hassapi import Hass class HelloWorld(Hass): def initialize(self): - # self.set_state(entity_id='input_boolean.enable', state='on') self.log('Hello World') + + now: datetime = self.get_now() + eid = self.args['eid'] + self.log(eid) + self.listen_state(self.my_callback, eid) + self.set_state(eid, state=now.isoformat(), attributes=dict(name='Test Fade Start')) + self.log(f'Set state to {now.time()}') + + def my_callback(self, entity, attribute, old, new, kwargs): + new = self.convert_time(new) + old = self.convert_time(old) + self.log(f'{attribute} {old} -> {new}') + + def convert_time(self, time_str: str) -> datetime: + dt = datetime.fromisoformat(time_str) + try: + dt = self.AD.tz.localize(dt) + except ValueError: + dt = dt.astimezone(self.AD.tz) + finally: + return dt \ No newline at end of file diff --git a/apps/leaving.py b/apps/leaving.py index 1c65ff1..edcd285 100644 --- a/apps/leaving.py +++ b/apps/leaving.py @@ -1,18 +1,16 @@ -from appdaemon.adapi import ADAPI +from appdaemon.plugins.hass.hassapi import Hass -class Leaving(ADAPI): + +class Leaving(Hass): def initialize(self): - self.listen_state(self.handle_state_change, entity_id=self.args['person']) - - def handle_state_change(self, entity, attribute, old, new, kwargs): - self.log(f'Changed state {old} -> {new}') - if old == 'home' and new != 'home': - self.turn_everything_off() + self.listen_state(self.turn_everything_off, entity_id=self.args['person'], old='home') - def turn_everything_off(self): + def turn_everything_off(self, entity, attribute, old, new, kwargs): + self.log(f'turning everything off') + self.log(kwargs) for app_name in self.args['apps']: try: - self.get_app(app_name).deactivate(cause='leaving') + self.get_app(app_name).deactivate(kwargs={'cause': 'leaving'}) except Exception as e: self.log(f'{type(e).__name__}: {e}') continue diff --git a/apps/rich_logging.py b/apps/rich_logging.py new file mode 100644 index 0000000..c1005ec --- /dev/null +++ b/apps/rich_logging.py @@ -0,0 +1,33 @@ +import logging + +from rich.console import Console +from rich.highlighter import NullHighlighter +from rich.logging import RichHandler + + +def init_logging(log_level: int = logging.INFO): + rich_handler = RichHandler( + console=Console(width=150), + highlighter=NullHighlighter(), + markup=True, + rich_tracebacks=True, + tracebacks_suppress=['pandas', 'discord'], + ) + dt_fmt = '%Y-%m-%d %I:%M:%S %p' + # https://docs.python.org/3/library/logging.html#logrecord-attributes + log_format = '[magenta]%(name)s[/]: [cyan]%(funcName)s[/] %(message)s' + + root_logger = logging.getLogger() + formatter = logging.Formatter(log_format) + formatter.datefmt = dt_fmt + rich_handler.setFormatter(formatter) + root_logger.addHandler(rich_handler) + root_logger.setLevel(log_level) + # logging.debug(f'Set up logging') + + # logging.basicConfig( + # level=log_level, + # format=log_format, + # datefmt=dt_fmt, + # handlers=[rich_handler] + # ) diff --git a/apps/room_control b/apps/room_control index c9cc841..d8a9d3d 160000 --- a/apps/room_control +++ b/apps/room_control @@ -1 +1 @@ -Subproject commit c9cc841d58ca45dac8557576913249ed807fef2c +Subproject commit d8a9d3d83af6510429b55d4f525e5de1c42483d4 diff --git a/apps/rooms/bathroom.yaml b/apps/rooms/bathroom.yaml index e4b71db..00137ff 100644 --- a/apps/rooms/bathroom.yaml +++ b/apps/rooms/bathroom.yaml @@ -6,22 +6,22 @@ bathroom: - time: '05:00:00' scene: light.bathroom: - brightness_pct: 40 + brightness: 100 color_temp: 250 - time: '12:00:00' scene: light.bathroom: - brightness_pct: 70 + brightness: 175 color_temp: 300 - time: sunset scene: light.bathroom: - brightness_pct: 50 + brightness: 125 color_temp: 350 - time: '23:00:00' scene: light.bathroom: - brightness_pct: 20 + brightness: 50 color_temp: 350 sleep: input_boolean.sleeping sleep_state: @@ -33,10 +33,10 @@ bathroom: bathroom_button: module: button - class: ButtonController + class: Button app: bathroom - ref_entity: light.bathroom button: Bathroom Button + ref_entity: light.bathroom bathroom_motion: module: motion diff --git a/apps/rooms/bedroom.yaml b/apps/rooms/bedroom.yaml index efff443..1f85954 100644 --- a/apps/rooms/bedroom.yaml +++ b/apps/rooms/bedroom.yaml @@ -8,11 +8,11 @@ bedroom: light.bedroom: state: on color_temp: 200 - brightness_pct: 20 + brightness: 50 light.globe: state: on color_temp: 200 - brightness_pct: 20 + brightness: 50 light.overhead: state: off - time: '06:00:00' @@ -20,62 +20,62 @@ bedroom: light.bedroom: state: on color_temp: 250 - brightness_pct: 20 + brightness: 50 light.globe: state: on color_temp: 250 - brightness_pct: 20 + brightness: 50 light.overhead: state: on color_temp: 250 - brightness_pct: 15 + brightness: 40 - time: '12:00:00' scene: light.bedroom: state: on color_temp: 325 - brightness_pct: 30 + brightness: 75 light.globe: state: on color_temp: 325 - brightness_pct: 30 + brightness: 75 light.overhead: state: on color_temp: 325 - brightness_pct: 50 + brightness: 50 - time: 'sunset' scene: light.bedroom: state: on color_temp: 325 - brightness_pct: 50 + brightness: 50 light.globe: state: on color_temp: 325 - brightness_pct: 50 + brightness: 50 light.overhead: state: on color_temp: 350 - brightness_pct: 10 + brightness: 65 - time: '01:00:00' scene: light.bedroom: state: on color_name: green - brightness_pct: 50 + brightness: 50 light.globe: state: on color_name: blue - brightness_pct: 50 + brightness: 50 light.overhead: state: on color_name: blueviolet - brightness_pct: 100 + brightness: 255 sleep: input_boolean.sleeping bedroom_buttons: module: button - class: ButtonController + class: Button app: bedroom ref_entity: light.bedroom button: diff --git a/apps/rooms/closet.yaml b/apps/rooms/closet.yaml index 5011890..b9ac54a 100644 --- a/apps/rooms/closet.yaml +++ b/apps/rooms/closet.yaml @@ -6,22 +6,22 @@ closet: - time: 'sunrise - 03:00:00' scene: light.closet: - brightness_pct: 10 + brightness: 25 color_temp: 200 - time: 'sunrise' scene: light.closet: - brightness_pct: 30 + brightness: 75 color_temp: 200 - time: '12:00:00' scene: light.closet: - brightness_pct: 70 + brightness: 175 color_temp: 300 - time: sunset scene: light.closet: - brightness_pct: 40 + brightness: 100 color_temp: 400 closet_motion: diff --git a/apps/rooms/kitchen.yaml b/apps/rooms/kitchen.yaml index ffd5479..e7bc239 100755 --- a/apps/rooms/kitchen.yaml +++ b/apps/rooms/kitchen.yaml @@ -2,46 +2,46 @@ kitchen: module: room_control class: RoomController off_duration: '00:10:00' - ha_button: input_button.activate_kitchen + # ha_button: input_button.activate_kitchen + sleep: input_boolean.sleeping + sleep_state: + scene: + light.kitchen: + state: on + brightness: 1 states: - time: sunrise scene: light.kitchen: state: on color_temp: 200 - brightness_pct: 10 + brightness: 25 - time: '12:00:00' scene: light.kitchen: state: on color_temp: 300 - brightness_pct: 30 + brightness: 75 - time: sunset scene: light.kitchen: state: on color_temp: 450 - brightness_pct: 40 + brightness: 100 - 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_state: - scene: - light.kitchen: - state: on - brightness_pct: 1 - + brightness: 25 + kitchen_button: module: button - class: ButtonController + class: Button app: kitchen - ref_entity: light.kitchen button: Kitchen Button + ref_entity: light.kitchen kitchen_motion: module: motion diff --git a/apps/rooms/living_room.yaml b/apps/rooms/living_room.yaml index df9a4a2..da15c4b 100644 --- a/apps/rooms/living_room.yaml +++ b/apps/rooms/living_room.yaml @@ -8,42 +8,42 @@ living_room: light.living_room: state: on color_temp: 200 - brightness_pct: 30 + brightness: 75 light.couch_corner: state: on color_temp: 200 - brightness_pct: 7 + brightness: 20 - time: '09:00:00' scene: light.living_room: state: on color_temp: 250 - brightness_pct: 50 + brightness: 130 light.couch_corner: state: on color_temp: 250 - brightness_pct: 25 + brightness: 65 - time: '12:00:00' scene: light.living_room: state: on color_temp: 300 - brightness_pct: 100 + brightness: 255 light.couch_corner: state: on color_temp: 450 - brightness_pct: 50 + brightness: 125 - time: sunset off_duration: 01:00:00 scene: light.living_room: state: on color_temp: 350 - brightness_pct: 70 + brightness: 175 light.couch_corner: state: on color_temp: 650 - brightness_pct: 10 + brightness: 25 - elevation: -20 direction: setting off_duration: 00:30:00 @@ -51,29 +51,29 @@ living_room: light.living_room: state: on color_temp: 350 - brightness_pct: 50 + brightness: 125 light.couch_corner: state: on color_temp: 650 - brightness_pct: 5 + brightness: 5 sleep: input_boolean.sleeping sleep_state: - off_duration: '00:02:00' + # off_duration: '00:02:00' scene: light.living_room: state: 'on' - color_name: 'red' - brightness_pct: 10 + rgb_color: [255, 0, 0] + brightness: 25 front_door: module: door - class: DoorControl + class: Door app: living_room door: binary_sensor.front_contact living_room_button: module: button - class: ButtonController + class: Button app: living_room button: Living Room Button ref_entity: light.living_room diff --git a/apps/scene_detect.py b/apps/scene_detect.py index a5e13b0..6df4891 100644 --- a/apps/scene_detect.py +++ b/apps/scene_detect.py @@ -1,5 +1,6 @@ from appdaemon.plugins.hass.hassapi import Hass + class SceneDetector(Hass): def initialize(self): self.scene_entity = self.args['scene'] if self.args['scene'].startswith('scene.') else f'scene.{self.args["scene"]}' @@ -21,4 +22,12 @@ class MotionCanceller(SceneDetector): def scene_detected(self): super().scene_detected() app = self.get_app(self.args['app']) - app.cancel_motion_callback(new='off') \ No newline at end of file + try: + self.run_in( + callback=lambda *args, **kwargs: app.cancel_motion_callback(), + delay=0.5 + ) + except: + self.log(f'Error cancelling motion callback for {self.args["app"]}', level='ERROR') + else: + self.log('Cancelled motion callback') diff --git a/apps/sleep.py b/apps/sleep.py index 8aab5cf..135dd2e 100755 --- a/apps/sleep.py +++ b/apps/sleep.py @@ -8,9 +8,8 @@ from appdaemon.plugins.mqtt.mqttapi import Mqtt class SleepSetter(Hass, Mqtt): def initialize(self): assert self.entity_exists(entity_id=self.variable), f'{self.variable} does not exist' - self.variable_entity.listen_state(callback=self.handle_state) + self.listen_state(callback=self.handle_state, entity_id=self.variable) self.setup_buttons() - self.log(f'{self.variable} can be set using {self.button}, currently {self.state}') def setup_buttons(self): if isinstance(self.button, list): @@ -23,7 +22,7 @@ class SleepSetter(Hass, Mqtt): topic = f'zigbee2mqtt/{name}' self.mqtt_subscribe(topic, namespace='mqtt') self.listen_event(self.handle_button, "MQTT_MESSAGE", topic=topic, namespace='mqtt', button=name) - self.log(f'Listening for sleep setting on {name}') + self.log(f'Subscribed: {topic}') @property def button(self) -> str: @@ -56,15 +55,13 @@ class SleepSetter(Hass, Mqtt): @property def sun_elevation(self) -> float: - try: - return float(self.get_state('sun.sun', 'elevation')) - except: - self.log(f'Failed to return sun elevation') - return + state = self.get_state('sun.sun', 'elevation') + assert isinstance(state, float) + return state def handle_state(self, entity, attribute, old, new, kwargs): - self.log(f'{entity}: {old} -> {new}') - if self.state and self.sun_elevation < float(self.args['elevation_limit']): + self.log(f'new state: {self.state}') + if self.state: self.all_off() try: self.call_service('scene/turn_on', entity_id=self.scene) @@ -86,22 +83,18 @@ class SleepSetter(Hass, Mqtt): return else: self.handle_action(action) - + def handle_action(self, action: str): if action == '': return - elif action == 'hold': + + if action == 'hold': + self.log(f' {action.upper()} '.center(50, '=')) self.state = True elif action == 'double': + self.log(f' {action.upper()} '.center(50, '=')) self.state = not self.state - if (on_apps := self.args.get('on_apps', None)) is not None: - for app_name in on_apps: - try: - self.get_app(app_name).activate(cause='sleep setter') - except: - return - else: - self.log(f'Activated {app_name}') + self.on_apps() def all_off(self): self.log(f'Deactivating apps') @@ -119,3 +112,13 @@ class SleepSetter(Hass, Mqtt): except: self.log(f'Failed to turn off {entity}') continue + + def on_apps(self): + if (on_apps := self.args.get('on_apps', None)) is not None: + for app_name in on_apps: + try: + self.get_app(app_name).activate(kwargs={'cause': 'sleep setter'}) + except: + return + else: + self.log(f'Activated {app_name}') \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d6d370f..036ce3d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: volumes: - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro - - config:/conf + - config:/conf:ro ports: - 5050:5050 restart: unless-stopped