From 047e4fd8b56cc9a187287fab18dec38c644c3911 Mon Sep 17 00:00:00 2001 From: Ian Fijolek Date: Mon, 8 Jul 2024 16:14:29 -0700 Subject: [PATCH] Add evening mode light dimming --- apps/apps.yaml | 23 ++++++--- apps/evening_mode_lighting.py | 95 +++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 apps/evening_mode_lighting.py diff --git a/apps/apps.yaml b/apps/apps.yaml index 2fb39d4..398031c 100644 --- a/apps/apps.yaml +++ b/apps/apps.yaml @@ -4,9 +4,20 @@ scene_retry: class: SceneRetry single_scene: true -snoo_mqtt: - module: snoo_mqtt - class: SnooMQTT - username: !secret snoo_username - password: !secret snoo_password - token_path: /config/apps/snoo_token.json +evening_mode_lighting: + module: evening_mode_lighting + class: EveningModeLighting + target_brightness: 20 + target_color_temp: 443 + evening_mode_entity: input_boolean.evening_mode + exclude_entities: + - light.bedroom + + +# snoo_mqtt: +# module: snoo_mqtt +# class: SnooMQTT +# username: !secret snoo_username +# password: !secret snoo_password +# token_path: /config/apps/snoo_token.json +# polling: false diff --git a/apps/evening_mode_lighting.py b/apps/evening_mode_lighting.py new file mode 100644 index 0000000..0f43d22 --- /dev/null +++ b/apps/evening_mode_lighting.py @@ -0,0 +1,95 @@ +import appdaemon.plugins.hass.hassapi as hass + + +class EveningModeLighting(hass.Hass): + + def initialize(self): + self.set_log_level("DEBUG") + # Read configuration + self.target_brightness = self.args["target_brightness"] + self.target_color_temp = self.args["target_color_temp"] + self.evening_mode_entity = self.args["evening_mode_entity"] + self.exclude_entities = self.args.get("exclude_entities", []) + self.light_listeners: list[str] = [] + + # Listen to changes in the evening mode toggle + self.listen_state(self.evening_mode_change, self.evening_mode_entity) + + # Initialize based on current state of evening mode + if self.get_state(self.evening_mode_entity) == "on": + self.setup_listeners() + + def evening_mode_change(self, entity, attribute, old, new, kwargs): + if new == "on": + self.setup_listeners() + else: + self.teardown_listeners() + + def setup_listeners(self): + # Get all light entities and filter out the excluded ones and those that don't support dimming + all_light_entities = self.get_state("light") + + # Set up listeners for light state changes + self.light_listeners = [] + for light in all_light_entities: + if ( + light in self.exclude_entities + or not self.supports_dimming(light) + or self.is_group(light) + ): + continue + listener = self.listen_state(self.light_state_change, light) + self.log( + "Listening to light events for %s: %s", light, listener, level="INFO" + ) + self.light_listeners.append(listener) + + self.log("Listening to %d lights", len(self.light_listeners)) + + def teardown_listeners(self): + # Cancel all light state listeners + for listener in self.light_listeners: + self.cancel_listen_state(listener) + self.log("Canceled light event listener: %s", listener, level="INFO") + self.light_listeners = [] + + def light_state_change(self, entity, attribute, old, new, kwargs): + # Check if the light is being turned on + if old in ("off", "unavailable") and new == "on": + # Get the brightness attribute of the light + brightness = self.get_state(entity, attribute="brightness") + brightness_pct = int(brightness / 255 * 100) + + # Check if the brightness exceeds the target value + if brightness_pct and brightness_pct > self.target_brightness: + self.log("Dimming light %s", entity, level="INFO") + # Dim the light to the target brightness + self.turn_on( + entity, + brightness_pct=self.target_brightness, + color_temp=self.target_color_temp, + ) + else: + self.log("Light state changed, but no action: entity=%s, old=%s, new=%s", entity, old, new) + + def is_group(self, entity) -> bool: + # Check if the light entity is a group + attributes = self.get_state(entity, attribute="attributes") + if attributes.get("is_hue_group", False) or attributes.get("entity_id", []): + self.log( + "Skipping light entity %s because it's a group", entity, level="DEBUG" + ) + return True + return False + + def supports_dimming(self, entity): + # Check if the light entity supports dimming + if self.get_state(entity, attribute="supported_features", default=0): + return True + + self.log( + "Skipping light entity %s because it doesn't support dimming", + entity, + level="DEBUG", + ) + return False