Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Platform for Control4 Lights."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from datetime import timedelta
7 import logging
8 from typing import Any
9 
10 from pyControl4.error_handling import C4Exception
11 from pyControl4.light import C4Light
12 
14  ATTR_BRIGHTNESS,
15  ATTR_TRANSITION,
16  ColorMode,
17  LightEntity,
18  LightEntityFeature,
19 )
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import CONF_SCAN_INTERVAL
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
25 
26 from . import get_items_of_category
27 from .const import CONF_DIRECTOR, CONTROL4_ENTITY_TYPE, DOMAIN
28 from .director_utils import update_variables_for_config_entry
29 from .entity import Control4Entity
30 
31 _LOGGER = logging.getLogger(__name__)
32 
33 CONTROL4_CATEGORY = "lights"
34 CONTROL4_NON_DIMMER_VAR = "LIGHT_STATE"
35 CONTROL4_DIMMER_VARS = ["LIGHT_LEVEL", "Brightness Percent"]
36 
37 
39  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
40 ) -> None:
41  """Set up Control4 lights from a config entry."""
42  entry_data = hass.data[DOMAIN][entry.entry_id]
43  scan_interval = entry_data[CONF_SCAN_INTERVAL]
44  _LOGGER.debug(
45  "Scan interval = %s",
46  scan_interval,
47  )
48 
49  async def async_update_data_non_dimmer() -> dict[int, dict[str, Any]]:
50  """Fetch data from Control4 director for non-dimmer lights."""
51  try:
53  hass, entry, {CONTROL4_NON_DIMMER_VAR}
54  )
55  except C4Exception as err:
56  raise UpdateFailed(f"Error communicating with API: {err}") from err
57 
58  async def async_update_data_dimmer() -> dict[int, dict[str, Any]]:
59  """Fetch data from Control4 director for dimmer lights."""
60  try:
62  hass, entry, {*CONTROL4_DIMMER_VARS}
63  )
64  except C4Exception as err:
65  raise UpdateFailed(f"Error communicating with API: {err}") from err
66 
67  non_dimmer_coordinator = DataUpdateCoordinator[dict[int, dict[str, Any]]](
68  hass,
69  _LOGGER,
70  name="light",
71  update_method=async_update_data_non_dimmer,
72  update_interval=timedelta(seconds=scan_interval),
73  )
74  dimmer_coordinator = DataUpdateCoordinator[dict[int, dict[str, Any]]](
75  hass,
76  _LOGGER,
77  name="light",
78  update_method=async_update_data_dimmer,
79  update_interval=timedelta(seconds=scan_interval),
80  )
81 
82  # Fetch initial data so we have data when entities subscribe
83  await non_dimmer_coordinator.async_refresh()
84  await dimmer_coordinator.async_refresh()
85 
86  items_of_category = await get_items_of_category(hass, entry, CONTROL4_CATEGORY)
87 
88  entity_list = []
89  for item in items_of_category:
90  try:
91  if item["type"] == CONTROL4_ENTITY_TYPE:
92  item_name = item["name"]
93  item_id = item["id"]
94  item_parent_id = item["parentId"]
95 
96  item_manufacturer = None
97  item_device_name = None
98  item_model = None
99 
100  for parent_item in items_of_category:
101  if parent_item["id"] == item_parent_id:
102  item_manufacturer = parent_item["manufacturer"]
103  item_device_name = parent_item["name"]
104  item_model = parent_item["model"]
105  else:
106  continue
107  except KeyError:
108  _LOGGER.exception(
109  "Unknown device properties received from Control4: %s",
110  item,
111  )
112  continue
113 
114  if item_id in dimmer_coordinator.data:
115  item_is_dimmer = True
116  item_coordinator = dimmer_coordinator
117  elif item_id in non_dimmer_coordinator.data:
118  item_is_dimmer = False
119  item_coordinator = non_dimmer_coordinator
120  else:
121  director = entry_data[CONF_DIRECTOR]
122  item_variables = await director.getItemVariables(item_id)
123  _LOGGER.warning(
124  (
125  "Couldn't get light state data for %s, skipping setup. Available"
126  " variables from Control4: %s"
127  ),
128  item_name,
129  item_variables,
130  )
131  continue
132 
133  entity_list.append(
135  entry_data,
136  item_coordinator,
137  item_name,
138  item_id,
139  item_device_name,
140  item_manufacturer,
141  item_model,
142  item_parent_id,
143  item_is_dimmer,
144  )
145  )
146 
147  async_add_entities(entity_list, True)
148 
149 
151  """Control4 light entity."""
152 
153  _attr_has_entity_name = True
154 
155  def __init__(
156  self,
157  entry_data: dict,
158  coordinator: DataUpdateCoordinator[dict[int, dict[str, Any]]],
159  name: str,
160  idx: int,
161  device_name: str | None,
162  device_manufacturer: str | None,
163  device_model: str | None,
164  device_id: int,
165  is_dimmer: bool,
166  ) -> None:
167  """Initialize Control4 light entity."""
168  super().__init__(
169  entry_data,
170  coordinator,
171  name,
172  idx,
173  device_name,
174  device_manufacturer,
175  device_model,
176  device_id,
177  )
178  self._is_dimmer_is_dimmer = is_dimmer
179  if is_dimmer:
180  self._attr_color_mode_attr_color_mode = ColorMode.BRIGHTNESS
181  self._attr_supported_color_modes_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
182  else:
183  self._attr_color_mode_attr_color_mode = ColorMode.ONOFF
184  self._attr_supported_color_modes_attr_supported_color_modes = {ColorMode.ONOFF}
185 
187  """Create a pyControl4 device object.
188 
189  This exists so the director token used is always the latest one, without needing to re-init the entire entity.
190  """
191  return C4Light(self.entry_dataentry_data[CONF_DIRECTOR], self._idx_idx)
192 
193  @property
194  def is_on(self):
195  """Return whether this light is on or off."""
196  if self._is_dimmer_is_dimmer:
197  for var in CONTROL4_DIMMER_VARS:
198  if var in self.coordinator.data[self._idx_idx]:
199  return self.coordinator.data[self._idx_idx][var] > 0
200  raise RuntimeError("Dimmer Variable Not Found")
201  return self.coordinator.data[self._idx_idx][CONTROL4_NON_DIMMER_VAR] > 0
202 
203  @property
204  def brightness(self):
205  """Return the brightness of this light between 0..255."""
206  if self._is_dimmer_is_dimmer:
207  for var in CONTROL4_DIMMER_VARS:
208  if var in self.coordinator.data[self._idx_idx]:
209  return round(self.coordinator.data[self._idx_idx][var] * 2.55)
210  return None
211 
212  @property
213  def supported_features(self) -> LightEntityFeature:
214  """Flag supported features."""
215  if self._is_dimmer_is_dimmer:
216  return LightEntityFeature.TRANSITION
217  return LightEntityFeature(0)
218 
219  async def async_turn_on(self, **kwargs: Any) -> None:
220  """Turn the entity on."""
221  c4_light = self._create_api_object_create_api_object()
222  if self._is_dimmer_is_dimmer:
223  if ATTR_TRANSITION in kwargs:
224  transition_length = kwargs[ATTR_TRANSITION] * 1000
225  else:
226  transition_length = 0
227  if ATTR_BRIGHTNESS in kwargs:
228  brightness = (kwargs[ATTR_BRIGHTNESS] / 255) * 100
229  else:
230  brightness = 100
231  await c4_light.rampToLevel(brightness, transition_length)
232  else:
233  transition_length = 0
234  await c4_light.setLevel(100)
235  if transition_length == 0:
236  transition_length = 1000
237  delay_time = (transition_length / 1000) + 0.7
238  _LOGGER.debug("Delaying light update by %s seconds", delay_time)
239  await asyncio.sleep(delay_time)
240  await self.coordinator.async_request_refresh()
241 
242  async def async_turn_off(self, **kwargs: Any) -> None:
243  """Turn the entity off."""
244  c4_light = self._create_api_object_create_api_object()
245  if self._is_dimmer_is_dimmer:
246  if ATTR_TRANSITION in kwargs:
247  transition_length = kwargs[ATTR_TRANSITION] * 1000
248  else:
249  transition_length = 0
250  await c4_light.rampToLevel(0, transition_length)
251  else:
252  transition_length = 0
253  await c4_light.setLevel(0)
254  if transition_length == 0:
255  transition_length = 1500
256  delay_time = (transition_length / 1000) + 0.7
257  _LOGGER.debug("Delaying light update by %s seconds", delay_time)
258  await asyncio.sleep(delay_time)
259  await self.coordinator.async_request_refresh()
None __init__(self, dict entry_data, DataUpdateCoordinator[dict[int, dict[str, Any]]] coordinator, str name, int idx, str|None device_name, str|None device_manufacturer, str|None device_model, int device_id, bool is_dimmer)
Definition: light.py:166
dict[int, dict[str, Any]] update_variables_for_config_entry(HomeAssistant hass, ConfigEntry entry, set[str] variable_names)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: light.py:40
def get_items_of_category(HomeAssistant hass, ConfigEntry entry, str category)
Definition: __init__.py:155