Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for Homekit lights."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from aiohomekit.model.characteristics import CharacteristicsTypes
8 from aiohomekit.model.services import Service, ServicesTypes
9 from propcache import cached_property
10 
12  ATTR_BRIGHTNESS,
13  ATTR_COLOR_TEMP,
14  ATTR_HS_COLOR,
15  ColorMode,
16  LightEntity,
17 )
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.const import Platform
20 from homeassistant.core import HomeAssistant, callback
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 import homeassistant.util.color as color_util
23 
24 from . import KNOWN_DEVICES
25 from .connection import HKDevice
26 from .entity import HomeKitEntity
27 
28 
30  hass: HomeAssistant,
31  config_entry: ConfigEntry,
32  async_add_entities: AddEntitiesCallback,
33 ) -> None:
34  """Set up Homekit lightbulb."""
35  hkid: str = config_entry.data["AccessoryPairingID"]
36  conn: HKDevice = hass.data[KNOWN_DEVICES][hkid]
37 
38  @callback
39  def async_add_service(service: Service) -> bool:
40  if service.type != ServicesTypes.LIGHTBULB:
41  return False
42  info = {"aid": service.accessory.aid, "iid": service.iid}
43  entity = HomeKitLight(conn, info)
44  conn.async_migrate_unique_id(
45  entity.old_unique_id, entity.unique_id, Platform.LIGHT
46  )
47  async_add_entities([entity])
48  return True
49 
50  conn.add_listener(async_add_service)
51 
52 
54  """Representation of a Homekit light."""
55 
56  @callback
57  def _async_reconfigure(self) -> None:
58  """Reconfigure entity."""
59  self._async_clear_property_cache_async_clear_property_cache(
60  ("supported_features", "min_mireds", "max_mireds", "supported_color_modes")
61  )
62  super()._async_reconfigure()
63 
64  def get_characteristic_types(self) -> list[str]:
65  """Define the homekit characteristics the entity cares about."""
66  return [
67  CharacteristicsTypes.ON,
68  CharacteristicsTypes.BRIGHTNESS,
69  CharacteristicsTypes.COLOR_TEMPERATURE,
70  CharacteristicsTypes.HUE,
71  CharacteristicsTypes.SATURATION,
72  ]
73 
74  @property
75  def is_on(self) -> bool:
76  """Return true if device is on."""
77  return self.serviceservice.value(CharacteristicsTypes.ON)
78 
79  @property
80  def brightness(self) -> int:
81  """Return the brightness of this light between 0..255."""
82  return self.serviceservice.value(CharacteristicsTypes.BRIGHTNESS) * 255 / 100
83 
84  @property
85  def hs_color(self) -> tuple[float, float]:
86  """Return the color property."""
87  return (
88  self.serviceservice.value(CharacteristicsTypes.HUE),
89  self.serviceservice.value(CharacteristicsTypes.SATURATION),
90  )
91 
92  @cached_property
93  def min_mireds(self) -> int:
94  """Return minimum supported color temperature."""
95  if not self.serviceservice.has(CharacteristicsTypes.COLOR_TEMPERATURE):
96  return super().min_mireds
97  min_value = self.serviceservice[CharacteristicsTypes.COLOR_TEMPERATURE].minValue
98  return int(min_value) if min_value else super().min_mireds
99 
100  @cached_property
101  def max_mireds(self) -> int:
102  """Return the maximum color temperature."""
103  if not self.serviceservice.has(CharacteristicsTypes.COLOR_TEMPERATURE):
104  return super().max_mireds
105  max_value = self.serviceservice[CharacteristicsTypes.COLOR_TEMPERATURE].maxValue
106  return int(max_value) if max_value else super().max_mireds
107 
108  @property
109  def color_temp(self) -> int:
110  """Return the color temperature."""
111  return self.serviceservice.value(CharacteristicsTypes.COLOR_TEMPERATURE)
112 
113  @property
114  def color_mode(self) -> str:
115  """Return the color mode of the light."""
116  # aiohomekit does not keep track of the light's color mode, report
117  # hs for light supporting both hs and ct
118  if self.serviceservice.has(CharacteristicsTypes.HUE) or self.serviceservice.has(
119  CharacteristicsTypes.SATURATION
120  ):
121  return ColorMode.HS
122 
123  if self.serviceservice.has(CharacteristicsTypes.COLOR_TEMPERATURE):
124  return ColorMode.COLOR_TEMP
125 
126  if self.serviceservice.has(CharacteristicsTypes.BRIGHTNESS):
127  return ColorMode.BRIGHTNESS
128 
129  return ColorMode.ONOFF
130 
131  @cached_property
132  def supported_color_modes(self) -> set[ColorMode]:
133  """Flag supported color modes."""
134  color_modes: set[ColorMode] = set()
135 
136  if self.serviceservice.has(CharacteristicsTypes.HUE) or self.serviceservice.has(
137  CharacteristicsTypes.SATURATION
138  ):
139  color_modes.add(ColorMode.HS)
140  color_modes.add(ColorMode.COLOR_TEMP)
141 
142  elif self.serviceservice.has(CharacteristicsTypes.COLOR_TEMPERATURE):
143  color_modes.add(ColorMode.COLOR_TEMP)
144 
145  if not color_modes and self.serviceservice.has(CharacteristicsTypes.BRIGHTNESS):
146  color_modes.add(ColorMode.BRIGHTNESS)
147 
148  if not color_modes:
149  color_modes.add(ColorMode.ONOFF)
150 
151  return color_modes
152 
153  async def async_turn_on(self, **kwargs: Any) -> None:
154  """Turn the specified light on."""
155  hs_color = kwargs.get(ATTR_HS_COLOR)
156  temperature = kwargs.get(ATTR_COLOR_TEMP)
157  brightness = kwargs.get(ATTR_BRIGHTNESS)
158 
159  characteristics: dict[str, Any] = {}
160 
161  if brightness is not None:
162  characteristics[CharacteristicsTypes.BRIGHTNESS] = int(
163  brightness * 100 / 255
164  )
165 
166  # If they send both temperature and hs_color, and the device
167  # does not support both, temperature will win. This is not
168  # expected to happen in the UI, but it is possible via a manual
169  # service call.
170  if temperature is not None:
171  if self.serviceservice.has(CharacteristicsTypes.COLOR_TEMPERATURE):
172  characteristics[CharacteristicsTypes.COLOR_TEMPERATURE] = int(
173  temperature
174  )
175  elif hs_color is None:
176  # Some HomeKit devices implement color temperature with HS
177  # since the spec "technically" does not permit the COLOR_TEMPERATURE
178  # characteristic and the HUE and SATURATION characteristics to be
179  # present at the same time.
180  hue_sat = color_util.color_temperature_to_hs(
181  color_util.color_temperature_mired_to_kelvin(temperature)
182  )
183  characteristics[CharacteristicsTypes.HUE] = hue_sat[0]
184  characteristics[CharacteristicsTypes.SATURATION] = hue_sat[1]
185 
186  if hs_color is not None:
187  characteristics[CharacteristicsTypes.HUE] = hs_color[0]
188  characteristics[CharacteristicsTypes.SATURATION] = hs_color[1]
189 
190  characteristics[CharacteristicsTypes.ON] = True
191 
192  await self.async_put_characteristicsasync_put_characteristics(characteristics)
193 
194  async def async_turn_off(self, **kwargs: Any) -> None:
195  """Turn the specified light off."""
196  await self.async_put_characteristicsasync_put_characteristics({CharacteristicsTypes.ON: False})
None async_put_characteristics(self, dict[str, Any] characteristics)
Definition: entity.py:125
None _async_clear_property_cache(self, tuple[str,...] properties)
Definition: entity.py:79
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: light.py:33