Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for Rflink lights."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import re
7 from typing import Any
8 
9 import voluptuous as vol
10 
12  ATTR_BRIGHTNESS,
13  PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
14  ColorMode,
15  LightEntity,
16 )
17 from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_TYPE
18 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
22 
23 from .const import (
24  CONF_ALIASES,
25  CONF_AUTOMATIC_ADD,
26  CONF_DEVICE_DEFAULTS,
27  CONF_FIRE_EVENT,
28  CONF_GROUP,
29  CONF_GROUP_ALIASES,
30  CONF_NOGROUP_ALIASES,
31  CONF_SIGNAL_REPETITIONS,
32  DATA_DEVICE_REGISTER,
33  DEVICE_DEFAULTS_SCHEMA,
34  EVENT_KEY_COMMAND,
35  EVENT_KEY_ID,
36 )
37 from .entity import SwitchableRflinkDevice
38 from .utils import brightness_to_rflink, rflink_to_brightness
39 
40 _LOGGER = logging.getLogger(__name__)
41 
42 PARALLEL_UPDATES = 0
43 
44 TYPE_DIMMABLE = "dimmable"
45 TYPE_SWITCHABLE = "switchable"
46 TYPE_HYBRID = "hybrid"
47 TYPE_TOGGLE = "toggle"
48 
49 PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
50  {
51  vol.Optional(
52  CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({})
53  ): DEVICE_DEFAULTS_SCHEMA,
54  vol.Optional(CONF_AUTOMATIC_ADD, default=True): cv.boolean,
55  vol.Optional(CONF_DEVICES, default={}): {
56  cv.string: vol.Schema(
57  {
58  vol.Optional(CONF_NAME): cv.string,
59  vol.Optional(CONF_TYPE): vol.Any(
60  TYPE_DIMMABLE, TYPE_SWITCHABLE, TYPE_HYBRID, TYPE_TOGGLE
61  ),
62  vol.Optional(CONF_ALIASES, default=[]): vol.All(
63  cv.ensure_list, [cv.string]
64  ),
65  vol.Optional(CONF_GROUP_ALIASES, default=[]): vol.All(
66  cv.ensure_list, [cv.string]
67  ),
68  vol.Optional(CONF_NOGROUP_ALIASES, default=[]): vol.All(
69  cv.ensure_list, [cv.string]
70  ),
71  vol.Optional(CONF_FIRE_EVENT): cv.boolean,
72  vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int),
73  vol.Optional(CONF_GROUP, default=True): cv.boolean,
74  }
75  )
76  },
77  },
78  extra=vol.ALLOW_EXTRA,
79 )
80 
81 
83  """Return entity class for protocol of a given device_id.
84 
85  Async friendly.
86  """
87  entity_type_mapping = {
88  # KlikAanKlikUit support both dimmers and on/off switches on the same
89  # protocol
90  "newkaku": TYPE_HYBRID
91  }
92  protocol = device_id.split("_")[0]
93  return entity_type_mapping.get(protocol)
94 
95 
96 def entity_class_for_type(entity_type):
97  """Translate entity type to entity class.
98 
99  Async friendly.
100  """
101  entity_device_mapping = {
102  # sends only 'dim' commands not compatible with on/off switches
103  TYPE_DIMMABLE: DimmableRflinkLight,
104  # sends only 'on/off' commands not advices with dimmers and signal
105  # repetition
106  TYPE_SWITCHABLE: RflinkLight,
107  # sends 'dim' and 'on' command to support both dimmers and on/off
108  # switches. Not compatible with signal repetition.
109  TYPE_HYBRID: HybridRflinkLight,
110  # sends only 'on' commands for switches which turn on and off
111  # using the same 'on' command for both.
112  TYPE_TOGGLE: ToggleRflinkLight,
113  }
114 
115  return entity_device_mapping.get(entity_type, RflinkLight)
116 
117 
118 def devices_from_config(domain_config):
119  """Parse configuration and add Rflink light devices."""
120  devices = []
121  for device_id, config in domain_config[CONF_DEVICES].items():
122  # Determine which kind of entity to create
123  if CONF_TYPE in config:
124  # Remove type from config to not pass it as and argument to entity
125  # instantiation
126  entity_type = config.pop(CONF_TYPE)
127  else:
128  entity_type = entity_type_for_device_id(device_id)
129  entity_class = entity_class_for_type(entity_type)
130 
131  device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config)
132 
133  is_hybrid = entity_class is HybridRflinkLight
134 
135  # Make user aware this can cause problems
136  repetitions_enabled = device_config[CONF_SIGNAL_REPETITIONS] != 1
137  if is_hybrid and repetitions_enabled:
138  _LOGGER.warning(
139  (
140  "Hybrid type for %s not compatible with signal "
141  "repetitions. Please set 'dimmable' or 'switchable' "
142  "type explicitly in configuration"
143  ),
144  device_id,
145  )
146 
147  device = entity_class(device_id, **device_config)
148  devices.append(device)
149 
150  return devices
151 
152 
154  hass: HomeAssistant,
155  config: ConfigType,
156  async_add_entities: AddEntitiesCallback,
157  discovery_info: DiscoveryInfoType | None = None,
158 ) -> None:
159  """Set up the Rflink light platform."""
161 
162  async def add_new_device(event):
163  """Check if device is known, otherwise add to list of known devices."""
164  device_id = event[EVENT_KEY_ID]
165 
166  entity_type = entity_type_for_device_id(event[EVENT_KEY_ID])
167  entity_class = entity_class_for_type(entity_type)
168 
169  device_config = config[CONF_DEVICE_DEFAULTS]
170  device = entity_class(device_id, initial_event=event, **device_config)
171  async_add_entities([device])
172 
173  if config[CONF_AUTOMATIC_ADD]:
174  hass.data[DATA_DEVICE_REGISTER][EVENT_KEY_COMMAND] = add_new_device
175 
176 
178  """Representation of a Rflink light."""
179 
180  _attr_color_mode = ColorMode.ONOFF
181  _attr_supported_color_modes = {ColorMode.ONOFF}
182 
183 
185  """Rflink light device that support dimming."""
186 
187  _attr_color_mode = ColorMode.BRIGHTNESS
188  _attr_supported_color_modes = {ColorMode.BRIGHTNESS}
189  _brightness = 255
190 
191  async def async_added_to_hass(self) -> None:
192  """Restore RFLink light brightness attribute."""
193  await super().async_added_to_hass()
194 
195  old_state = await self.async_get_last_stateasync_get_last_state()
196  if (
197  old_state is not None
198  and old_state.attributes.get(ATTR_BRIGHTNESS) is not None
199  ):
200  # restore also brightness in dimmables devices
201  self._brightness_brightness_brightness = int(old_state.attributes[ATTR_BRIGHTNESS])
202 
203  async def async_turn_on(self, **kwargs: Any) -> None:
204  """Turn the device on."""
205  if ATTR_BRIGHTNESS in kwargs:
206  # rflink only support 16 brightness levels
208  brightness_to_rflink(kwargs[ATTR_BRIGHTNESS])
209  )
210 
211  # Turn on light at the requested dim level
212  await self._async_handle_command_async_handle_command("dim", self._brightness_brightness_brightness)
213 
214  def _handle_event(self, event):
215  """Adjust state if Rflink picks up a remote command for this device."""
216  self.cancel_queued_send_commandscancel_queued_send_commands()
217 
218  command = event["command"]
219  if command in ["on", "allon"]:
220  self._state_state_state_state = True
221  elif command in ["off", "alloff"]:
222  self._state_state_state_state = False
223  # dimmable device accept 'set_level=(0-15)' commands
224  elif re.search("^set_level=(0?[0-9]|1[0-5])$", command, re.IGNORECASE):
225  self._brightness_brightness_brightness = rflink_to_brightness(int(command.split("=")[1]))
226  self._state_state_state_state = True
227 
228  @property
229  def brightness(self):
230  """Return the brightness of this light between 0..255."""
231  return self._brightness_brightness_brightness
232 
233 
235  """Rflink light device that sends out both dim and on/off commands.
236 
237  Used for protocols which support lights that are not exclusively on/off
238  style. For example KlikAanKlikUit supports both on/off and dimmable light
239  switches using the same protocol. This type allows unconfigured
240  KlikAanKlikUit devices to support dimming without breaking support for
241  on/off switches.
242 
243  This type is not compatible with signal repetitions as the 'dim' and 'on'
244  command are send sequential and multiple 'on' commands to a dimmable
245  device can cause the dimmer to switch into a pulsating brightness mode.
246  Which results in a nice house disco :)
247  """
248 
249  async def async_turn_on(self, **kwargs: Any) -> None:
250  """Turn the device on and set dim level."""
251  await super().async_turn_on(**kwargs)
252  # if the receiving device does not support dimlevel this
253  # will ensure it is turned on when full brightness is set
254  if self.brightnessbrightnessbrightnessbrightness == 255:
255  await self._async_handle_command_async_handle_command("turn_on")
256 
257 
259  """Rflink light device which sends out only 'on' commands.
260 
261  Some switches like for example Livolo light switches use the
262  same 'on' command to switch on and switch off the lights.
263  If the light is on and 'on' gets sent, the light will turn off
264  and if the light is off and 'on' gets sent, the light will turn on.
265  """
266 
267  def _handle_event(self, event):
268  """Adjust state if Rflink picks up a remote command for this device."""
269  self.cancel_queued_send_commandscancel_queued_send_commands()
270 
271  if event["command"] == "on":
272  # if the state is unknown or false, it gets set as true
273  # if the state is true, it gets set as false
274  self._state_state_state_state = self._state_state_state_state in [None, False]
275 
276  async def async_turn_on(self, **kwargs: Any) -> None:
277  """Turn the device on."""
278  await self._async_handle_command_async_handle_command("toggle")
279 
280  async def async_turn_off(self, **kwargs: Any) -> None:
281  """Turn the device off."""
282  await self._async_handle_command_async_handle_command("toggle")