229 lines
11 KiB
Python
229 lines
11 KiB
Python
from smartswitch import SmartSwitch
|
|
import ad_toolbox.smartcondition as SmartCondition
|
|
from ad_toolbox.eventhandler import EventHandler
|
|
|
|
# =============================================================================
|
|
# SmartLight — Light-specific extension of SmartSwitch
|
|
# =============================================================================
|
|
# Adds brightness control and smooth transitions on top of all SmartSwitch
|
|
# features. Use this class instead of SmartSwitch when the entity is a light.
|
|
#
|
|
# Inherits all SmartObject and SmartSwitch YAML keys.
|
|
# The "entity" key is required and must refer to a light entity.
|
|
#
|
|
# YAML CONFIGURATION
|
|
# ------------------
|
|
#
|
|
# brightness_pct_step: <int> # optional, default 5
|
|
# Step size (% of full brightness) used by increase/decrease events.
|
|
#
|
|
# brightness_step_transition: <seconds> # optional, default 0.5
|
|
# Transition duration applied to each increase/decrease brightness
|
|
# step. Smooths out the brightness change instead of jumping.
|
|
#
|
|
# increase_brightness_events: # optional
|
|
# <EventHandler events_block>
|
|
# HA events that increase brightness by brightness_pct_step.
|
|
# Has no effect while the light is off.
|
|
#
|
|
# decrease_brightness_events: # optional
|
|
# <EventHandler events_block>
|
|
# HA events that decrease brightness by brightness_pct_step.
|
|
# Has no effect while the light is off.
|
|
#
|
|
# on_events_with_transition: # optional
|
|
# <label>:
|
|
# events:
|
|
# <EventHandler events_block>
|
|
# brightness_pct: <0-100>
|
|
# Target brightness level.
|
|
# transition_time: <seconds>
|
|
# Duration of the ramp from 1 % to brightness_pct.
|
|
# Turns the light on with a smooth ramp-up. Multiple labelled
|
|
# transitions can be defined (e.g. "dim", "full").
|
|
#
|
|
# light_brightness_pct: # optional
|
|
# always_change_brightness: true|false # optional, default false
|
|
# When true, apply the resolved brightness even if the light
|
|
# is currently off. When false, only apply it while on.
|
|
# <label>:
|
|
# <SmartCondition block>
|
|
# The condition value (label) is used directly as the brightness
|
|
# percentage when the condition succeeds.
|
|
# Conditions are evaluated in declaration order; the first
|
|
# Succeeded result wins.
|
|
# Dynamically sets the brightness based on conditions. When the
|
|
# active condition changes, the new brightness is applied
|
|
# immediately (subject to always_change_brightness).
|
|
# Note: brightness 0 also calls light/turn_off for compatibility
|
|
# with integrations that do not honour brightness = 0.
|
|
#
|
|
# icon_override: # optional
|
|
# on_icon: <mdi:icon>
|
|
# off_icon: <mdi:icon>
|
|
# dest_entities: <entity_id | list of entity_ids>
|
|
# Updates the icon attribute on dest_entities whenever the light
|
|
# turns on or off. Useful for dashboard button cards.
|
|
#
|
|
# EXAMPLE YAML
|
|
# ------------
|
|
# living_light:
|
|
# module: smartlight
|
|
# class: SmartLight
|
|
# entity: light.living_room
|
|
#
|
|
# brightness_pct_step: 10
|
|
# brightness_step_transition: 0.5
|
|
#
|
|
# increase_brightness_events:
|
|
# btn_up:
|
|
# event_name: WALL_SWITCH
|
|
# event_data: {action: brightness_up}
|
|
# decrease_brightness_events:
|
|
# btn_down:
|
|
# event_name: WALL_SWITCH
|
|
# event_data: {action: brightness_down}
|
|
#
|
|
# on_events_with_transition:
|
|
# dim:
|
|
# events:
|
|
# btn:
|
|
# event_name: WALL_SWITCH
|
|
# event_data: {action: dim}
|
|
# brightness_pct: 20
|
|
# transition_time: 5
|
|
#
|
|
# light_brightness_pct:
|
|
# always_change_brightness: false
|
|
# "10":
|
|
# trigger:
|
|
# condition: sensor.lux > 500
|
|
# "60":
|
|
# trigger:
|
|
# condition: sensor.lux > 100
|
|
# "100":
|
|
# trigger:
|
|
# condition: "true"
|
|
#
|
|
# icon_override:
|
|
# on_icon: mdi:lightbulb
|
|
# off_icon: mdi:lightbulb-outline
|
|
# dest_entities: sensor.living_room_button
|
|
# =============================================================================
|
|
|
|
class SmartLight(SmartSwitch):
|
|
|
|
def on_initialize_smart_object(self):
|
|
# light_brightness_pct_list : ordered list of (Evaluator, label) pairs
|
|
# light_brightness_pct : currently active brightness % (str label)
|
|
self.light_brightness_pct_list = list()
|
|
self.light_brightness_pct = None
|
|
|
|
super().on_initialize_smart_object()
|
|
|
|
# increase/decrease brightness step events
|
|
if "increase_brightness_events" in self.args:
|
|
self.event_handlers.append(EventHandler(self, self.args["increase_brightness_events"], self.on_increase_brightness_event))
|
|
if "decrease_brightness_events" in self.args:
|
|
self.event_handlers.append(EventHandler(self, self.args["decrease_brightness_events"], self.on_decrease_brightness_event))
|
|
|
|
if "brightness_pct_step" in self.args:
|
|
self.brightness_pct_step = self.args["brightness_pct_step"]
|
|
else: self.brightness_pct_step = 5
|
|
|
|
self.brightness_step_transition = self.args.get("brightness_step_transition", 0.8)
|
|
|
|
# on_events_with_transition: one EventHandler per labelled transition
|
|
if "on_events_with_transition" in self.args:
|
|
for key in self.args["on_events_with_transition"]:
|
|
self.event_handlers.append(EventHandler(self, self.args["on_events_with_transition"][key]["events"], self.on_turn_on_with_transition, key))
|
|
|
|
# light_brightness_pct: build ordered evaluator list, run initial pass
|
|
if "light_brightness_pct" in self.args:
|
|
self.always_change_brightness = False
|
|
if isinstance(self.args["light_brightness_pct"], int):
|
|
self.light_brightness_pct = self.args["light_brightness_pct"]
|
|
else:
|
|
for key in self.args["light_brightness_pct"]:
|
|
if key == 'always_change_brightness':
|
|
self.always_change_brightness = bool(self.args["light_brightness_pct"][key])
|
|
else:
|
|
self.light_brightness_pct_list.append((SmartCondition.Evaluator(self,self.args["light_brightness_pct"][key],condition_name = key, on_update_cb = self.on_update_light_brightness_pct,constants = self.constants, templates_library = self.templates_library, log_callback_trigger_reason = False),key))
|
|
self.on_update_light_brightness_pct()
|
|
|
|
self.listen_state(self.on_state_change,self.entity_id)
|
|
|
|
# listen_state callback on self.entity_id. Applies icon_override when
|
|
# the light turns on or off.
|
|
def on_state_change(self, entity, attribute, old, new, *kwargs):
|
|
if "icon_override" in self.args:
|
|
override_data = self.args['icon_override']
|
|
|
|
def update_icon(entity_id,new_state): self.set_state(entity_id,attributes = { 'icon' : override_data['on_icon'] if new_state == 'on' else override_data['off_icon'] })
|
|
|
|
if isinstance(override_data['dest_entities'],list):
|
|
for target_entity in override_data['dest_entities']:
|
|
update_icon(target_entity,new)
|
|
else:
|
|
update_icon(override_data['dest_entities'],new)
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Brightness event callbacks
|
|
# ------------------------------------------------------------------
|
|
|
|
# Increase brightness by brightness_pct_step while the light is on.
|
|
def on_increase_brightness_event(self, event_name, data, kwargs):
|
|
if self.get_state(self.entity_id) != 'off':
|
|
self.call_service("light/turn_on", entity_id = self.entity_id, brightness_step_pct = self.brightness_pct_step, transition = self.brightness_step_transition)
|
|
|
|
# Decrease brightness by brightness_pct_step while the light is on.
|
|
def on_decrease_brightness_event(self, event_name, data, kwargs):
|
|
if self.get_state(self.entity_id) != 'off':
|
|
self.call_service("light/turn_on", entity_id = self.entity_id, brightness_step_pct = -self.brightness_pct_step, transition = self.brightness_step_transition)
|
|
|
|
# EventHandler callback for on_events_with_transition. Jumps to 1 %
|
|
# first so the ramp always starts from a known low level, then
|
|
# transitions to the configured brightness over transition_time seconds.
|
|
def on_turn_on_with_transition(self, event_name, data, event_category):
|
|
transition_time = self.args["on_events_with_transition"][event_category]["transition_time"]
|
|
brightness_pct = self.args["on_events_with_transition"][event_category]["brightness_pct"]
|
|
self.log(f"Turn on at {brightness_pct}% with a transition of {transition_time}s")
|
|
self.call_service("light/turn_on", entity_id = self.entity_id,brightness_pct = 1)
|
|
self.call_service("light/turn_on", entity_id = self.entity_id, transition = transition_time,brightness_pct = brightness_pct)
|
|
|
|
# ------------------------------------------------------------------
|
|
# SmartSwitch overrides
|
|
# ------------------------------------------------------------------
|
|
|
|
# Override: apply light_brightness_pct when turning on, if one is active.
|
|
def switch_on(self):
|
|
if self.light_brightness_pct != None:
|
|
self.log(f"Turn on {self.entity_id} at {self.light_brightness_pct}%")
|
|
self.call_service("light/turn_on", entity_id = self.entity_id,brightness_pct = self.light_brightness_pct)
|
|
else:
|
|
super().switch_on()
|
|
|
|
# on_update_cb for all light_brightness_pct evaluators. Walks the list
|
|
# in order and applies the first Succeeded result. Pushes the new
|
|
# brightness to the light if it is on (or always_change_brightness=true).
|
|
# Brightness 0 additionally calls light/turn_off for compatibility.
|
|
def on_update_light_brightness_pct(self):
|
|
for brightness_pct_evaluator in self.light_brightness_pct_list:
|
|
if brightness_pct_evaluator[0].evaluate(False) == SmartCondition.Result.Succeeded:
|
|
if self.light_brightness_pct != brightness_pct_evaluator[1]:
|
|
brightness_pct_evaluator[0].log_callback_trigger_reason()
|
|
brightness_pct_evaluator[0].log_evaluation_result()
|
|
self.light_brightness_pct = brightness_pct_evaluator[1]
|
|
self.log_info(f"New brightness : {self.light_brightness_pct}%")
|
|
break
|
|
|
|
if (self.always_change_brightness or self.get_state(self.entity_id) == "on") and self.light_brightness_pct != None:
|
|
self.call_service("light/turn_on", entity_id = self.entity_id,brightness_pct = self.light_brightness_pct)
|
|
|
|
if int(self.light_brightness_pct) == 0: #some integration seems to not turn off the light when you set up a brightness of 0
|
|
self.log_info("Turning off")
|
|
self.call_service("light/turn_off", entity_id = self.entity_id)
|
|
|
|
|