Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for lights through the SmartThings cloud API."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Sequence
7 from typing import Any
8 
9 from pysmartthings import Capability
10 
12  ATTR_BRIGHTNESS,
13  ATTR_COLOR_TEMP,
14  ATTR_HS_COLOR,
15  ATTR_TRANSITION,
16  ColorMode,
17  LightEntity,
18  LightEntityFeature,
19  brightness_supported,
20 )
21 from homeassistant.config_entries import ConfigEntry
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 import homeassistant.util.color as color_util
25 
26 from .const import DATA_BROKERS, DOMAIN
27 from .entity import SmartThingsEntity
28 
29 
31  hass: HomeAssistant,
32  config_entry: ConfigEntry,
33  async_add_entities: AddEntitiesCallback,
34 ) -> None:
35  """Add lights for a config entry."""
36  broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
38  [
39  SmartThingsLight(device)
40  for device in broker.devices.values()
41  if broker.any_assigned(device.device_id, "light")
42  ],
43  True,
44  )
45 
46 
47 def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None:
48  """Return all capabilities supported if minimum required are present."""
49  supported = [
50  Capability.switch,
51  Capability.switch_level,
52  Capability.color_control,
53  Capability.color_temperature,
54  ]
55  # Must be able to be turned on/off.
56  if Capability.switch not in capabilities:
57  return None
58  # Must have one of these
59  light_capabilities = [
60  Capability.color_control,
61  Capability.color_temperature,
62  Capability.switch_level,
63  ]
64  if any(capability in capabilities for capability in light_capabilities):
65  return supported
66  return None
67 
68 
69 def convert_scale(value, value_scale, target_scale, round_digits=4):
70  """Convert a value to a different scale."""
71  return round(value * target_scale / value_scale, round_digits)
72 
73 
75  """Define a SmartThings Light."""
76 
77  _attr_supported_color_modes: set[ColorMode]
78 
79  # SmartThings does not expose this attribute, instead it's
80  # implemented within each device-type handler. This value is the
81  # lowest kelvin found supported across 20+ handlers.
82  _attr_max_mireds = 500 # 2000K
83 
84  # SmartThings does not expose this attribute, instead it's
85  # implemented within each device-type handler. This value is the
86  # highest kelvin found supported across 20+ handlers.
87  _attr_min_mireds = 111 # 9000K
88 
89  def __init__(self, device):
90  """Initialize a SmartThingsLight."""
91  super().__init__(device)
92  self._attr_supported_color_modes_attr_supported_color_modes = self._determine_color_modes_determine_color_modes()
93  self._attr_supported_features_attr_supported_features = self._determine_features_determine_features()
94 
96  """Get features supported by the device."""
97  color_modes = set()
98  # Color Temperature
99  if Capability.color_temperature in self._device_device.capabilities:
100  color_modes.add(ColorMode.COLOR_TEMP)
101  # Color
102  if Capability.color_control in self._device_device.capabilities:
103  color_modes.add(ColorMode.HS)
104  # Brightness
105  if not color_modes and Capability.switch_level in self._device_device.capabilities:
106  color_modes.add(ColorMode.BRIGHTNESS)
107  if not color_modes:
108  color_modes.add(ColorMode.ONOFF)
109 
110  return color_modes
111 
112  def _determine_features(self) -> LightEntityFeature:
113  """Get features supported by the device."""
114  features = LightEntityFeature(0)
115  # Transition
116  if Capability.switch_level in self._device_device.capabilities:
117  features |= LightEntityFeature.TRANSITION
118 
119  return features
120 
121  async def async_turn_on(self, **kwargs: Any) -> None:
122  """Turn the light on."""
123  tasks = []
124  # Color temperature
125  if ATTR_COLOR_TEMP in kwargs:
126  tasks.append(self.async_set_color_tempasync_set_color_temp(kwargs[ATTR_COLOR_TEMP]))
127  # Color
128  if ATTR_HS_COLOR in kwargs:
129  tasks.append(self.async_set_colorasync_set_color(kwargs[ATTR_HS_COLOR]))
130  if tasks:
131  # Set temp/color first
132  await asyncio.gather(*tasks)
133 
134  # Switch/brightness/transition
135  if ATTR_BRIGHTNESS in kwargs:
136  await self.async_set_levelasync_set_level(
137  kwargs[ATTR_BRIGHTNESS], kwargs.get(ATTR_TRANSITION, 0)
138  )
139  else:
140  await self._device_device.switch_on(set_status=True)
141 
142  # State is set optimistically in the commands above, therefore update
143  # the entity state ahead of receiving the confirming push updates
144  self.async_schedule_update_ha_stateasync_schedule_update_ha_state(True)
145 
146  async def async_turn_off(self, **kwargs: Any) -> None:
147  """Turn the light off."""
148  # Switch/transition
149  if ATTR_TRANSITION in kwargs:
150  await self.async_set_levelasync_set_level(0, int(kwargs[ATTR_TRANSITION]))
151  else:
152  await self._device_device.switch_off(set_status=True)
153 
154  # State is set optimistically in the commands above, therefore update
155  # the entity state ahead of receiving the confirming push updates
156  self.async_schedule_update_ha_stateasync_schedule_update_ha_state(True)
157 
158  async def async_update(self) -> None:
159  """Update entity attributes when the device status has changed."""
160  # Brightness and transition
161  if brightness_supported(self._attr_supported_color_modes_attr_supported_color_modes):
162  self._attr_brightness_attr_brightness = int(
163  convert_scale(self._device_device.status.level, 100, 255, 0)
164  )
165  # Color Temperature
166  if ColorMode.COLOR_TEMP in self._attr_supported_color_modes_attr_supported_color_modes:
167  self._attr_color_temp_attr_color_temp = color_util.color_temperature_kelvin_to_mired(
168  self._device_device.status.color_temperature
169  )
170  # Color
171  if ColorMode.HS in self._attr_supported_color_modes_attr_supported_color_modes:
172  self._attr_hs_color_attr_hs_color = (
173  convert_scale(self._device_device.status.hue, 100, 360),
174  self._device_device.status.saturation,
175  )
176 
177  async def async_set_color(self, hs_color):
178  """Set the color of the device."""
179  hue = convert_scale(float(hs_color[0]), 360, 100)
180  hue = max(min(hue, 100.0), 0.0)
181  saturation = max(min(float(hs_color[1]), 100.0), 0.0)
182  await self._device_device.set_color(hue, saturation, set_status=True)
183 
184  async def async_set_color_temp(self, value: float):
185  """Set the color temperature of the device."""
186  kelvin = color_util.color_temperature_mired_to_kelvin(value)
187  kelvin = max(min(kelvin, 30000), 1)
188  await self._device_device.set_color_temperature(kelvin, set_status=True)
189 
190  async def async_set_level(self, brightness: int, transition: int):
191  """Set the brightness of the light over transition."""
192  level = int(convert_scale(brightness, 255, 100, 0))
193  # Due to rounding, set level to 1 (one) so we don't inadvertently
194  # turn off the light when a low brightness is set.
195  level = 1 if level == 0 and brightness > 0 else level
196  level = max(min(level, 100), 0)
197  duration = int(transition)
198  await self._device_device.set_level(level, duration, set_status=True)
199 
200  @property
201  def color_mode(self) -> ColorMode:
202  """Return the color mode of the light."""
203  if len(self._attr_supported_color_modes_attr_supported_color_modes) == 1:
204  # The light supports only a single color mode
205  return list(self._attr_supported_color_modes_attr_supported_color_modes)[0]
206 
207  # The light supports hs + color temp, determine which one it is
208  if self._attr_hs_color_attr_hs_color and self._attr_hs_color_attr_hs_color[1]:
209  return ColorMode.HS
210  return ColorMode.COLOR_TEMP
211 
212  @property
213  def is_on(self) -> bool:
214  """Return true if light is on."""
215  return self._device_device.status.switch
def async_set_level(self, int brightness, int transition)
Definition: light.py:190
None async_schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1265
bool brightness_supported(Iterable[ColorMode|str]|None color_modes)
Definition: __init__.py:155
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: light.py:34
Sequence[str]|None get_capabilities(Sequence[str] capabilities)
Definition: light.py:47
def convert_scale(value, value_scale, target_scale, round_digits=4)
Definition: light.py:69