light.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. """
  2. Platform to control Tuya lights.
  3. Initially based on the secondary panel lighting control on some climate
  4. devices, so only providing simple on/off control.
  5. """
  6. from homeassistant.components.light import (
  7. LightEntity,
  8. ATTR_BRIGHTNESS,
  9. ATTR_COLOR_MODE,
  10. ATTR_COLOR_TEMP,
  11. ATTR_EFFECT,
  12. ATTR_RGBW_COLOR,
  13. COLOR_MODE_BRIGHTNESS,
  14. COLOR_MODE_COLOR_TEMP,
  15. COLOR_MODE_ONOFF,
  16. COLOR_MODE_RGBW,
  17. COLOR_MODE_UNKNOWN,
  18. SUPPORT_EFFECT,
  19. VALID_COLOR_MODES,
  20. )
  21. import homeassistant.util.color as color_util
  22. import logging
  23. from ..device import TuyaLocalDevice
  24. from ..helpers.device_config import TuyaEntityConfig
  25. from ..helpers.mixin import TuyaLocalEntity
  26. _LOGGER = logging.getLogger(__name__)
  27. class TuyaLocalLight(TuyaLocalEntity, LightEntity):
  28. """Representation of a Tuya WiFi-connected light."""
  29. def __init__(self, device: TuyaLocalDevice, config: TuyaEntityConfig):
  30. """
  31. Initialize the light.
  32. Args:
  33. device (TuyaLocalDevice): The device API instance.
  34. config (TuyaEntityConfig): The configuration for this entity.
  35. """
  36. dps_map = self._init_begin(device, config)
  37. self._switch_dps = dps_map.pop("switch", None)
  38. self._brightness_dps = dps_map.pop("brightness", None)
  39. self._color_mode_dps = dps_map.pop("color_mode", None)
  40. self._color_temp_dps = dps_map.pop("color_temp", None)
  41. self._rgbhsv_dps = dps_map.pop("rgbhsv", None)
  42. self._effect_dps = dps_map.pop("effect", None)
  43. self._init_end(dps_map)
  44. @property
  45. def supported_color_modes(self):
  46. """Return the supported color modes for this light."""
  47. if self._color_mode_dps:
  48. return [
  49. mode
  50. for mode in self._color_mode_dps.values(self._device)
  51. if mode in VALID_COLOR_MODES
  52. ]
  53. else:
  54. mode = self.color_mode
  55. if mode and mode != COLOR_MODE_UNKNOWN:
  56. return [mode]
  57. return []
  58. @property
  59. def supported_features(self):
  60. """Return the supported features for this light."""
  61. if self.effect_list:
  62. return SUPPORT_EFFECT
  63. else:
  64. return 0
  65. @property
  66. def color_mode(self):
  67. """Return the color mode of the light"""
  68. if self._color_mode_dps:
  69. mode = self._color_mode_dps.get_value(self._device)
  70. if mode in VALID_COLOR_MODES:
  71. return mode
  72. if self._rgbhsv_dps:
  73. return COLOR_MODE_RGBW
  74. elif self._color_temp_dps:
  75. return COLOR_MODE_COLOR_TEMP
  76. elif self._brightness_dps:
  77. return COLOR_MODE_BRIGHTNESS
  78. elif self._switch_dps:
  79. return COLOR_MODE_ONOFF
  80. else:
  81. return COLOR_MODE_UNKNOWN
  82. @property
  83. def color_temp(self):
  84. """Return the color temperature in mireds"""
  85. if self._color_temp_dps:
  86. unscaled = self._color_temp_dps.get_value(self._device)
  87. range = self._color_temp_dps.range(self._device)
  88. if range:
  89. min = range["min"]
  90. max = range["max"]
  91. return unscaled * 347 / (max - min) + 153 - min
  92. else:
  93. return unscaled
  94. return None
  95. @property
  96. def is_on(self):
  97. """Return the current state."""
  98. if self._switch_dps:
  99. return self._switch_dps.get_value(self._device)
  100. elif self._brightness_dps:
  101. b = self.brightness
  102. return isinstance(b, int) and b > 0
  103. else:
  104. # There shouldn't be lights without control, but if there are,
  105. # assume always on if they are responding
  106. return self.available
  107. @property
  108. def brightness(self):
  109. """Get the current brightness of the light"""
  110. if self._brightness_dps:
  111. return self._brightness_dps.get_value(self._device)
  112. @property
  113. def rgbw_color(self):
  114. """Get the current RGBW color of the light"""
  115. if self._rgbhsv_dps and self._rgbhsv_dps.rawtype == "hex":
  116. # color data in hex format RRGGBBHHHHSSVV (14 digit hex)
  117. # Either RGB or HSV can be used.
  118. color = self._rgbhsv_dps.get_value(self._device)
  119. h = int(color[6:10], 16)
  120. s = int(color[10:12], 16)
  121. r, g, b = color_util.color_hs_to_RGB(h, s)
  122. w = int(color[12:14], 16) * 255 / 100
  123. return (r, g, b, w)
  124. @property
  125. def effect_list(self):
  126. """Return the list of valid effects for the light"""
  127. if self._effect_dps:
  128. return self._effect_dps.values(self._device)
  129. elif self._color_mode_dps:
  130. return [
  131. effect
  132. for effect in self._color_mode_dps.values(self._device)
  133. if effect not in VALID_COLOR_MODES
  134. ]
  135. @property
  136. def effect(self):
  137. """Return the current effect setting of this light"""
  138. if self._effect_dps:
  139. return self._effect_dps.get_value(self._device)
  140. elif self._color_mode_dps:
  141. mode = self._color_mode_dps.get_value(self._device)
  142. if mode in VALID_COLOR_MODES:
  143. return None
  144. return mode
  145. async def async_turn_on(self, **params):
  146. settings = {}
  147. if self._switch_dps:
  148. settings = {
  149. **settings,
  150. **self._switch_dps.get_values_to_set(self._device, True),
  151. }
  152. if self._color_mode_dps:
  153. color_mode = params.get(ATTR_COLOR_MODE)
  154. if color_mode:
  155. color_values = self._color_mode_dps.get_values_to_set(
  156. self._device, color_mode
  157. )
  158. settings = {
  159. **settings,
  160. **color_values,
  161. }
  162. elif not self._effect_dps:
  163. effect = params.get(ATTR_EFFECT)
  164. if effect:
  165. color_values = self._color_mode_dps.get_values_to_set(
  166. self._device, effect
  167. )
  168. settings = {
  169. **settings,
  170. **color_values,
  171. }
  172. if self._color_temp_dps:
  173. color_temp = params.get(ATTR_COLOR_TEMP)
  174. range = self._color_temp_dps.range(self._device)
  175. if range and color_temp:
  176. min = range["min"]
  177. max = range["max"]
  178. color_temp = round((color_temp - 153 + min) * (max - min) / 347)
  179. if color_temp:
  180. color_values = self._color_temp_dps.get_values_to_set(
  181. self._device,
  182. color_temp,
  183. )
  184. settings = {
  185. **settings,
  186. **color_values,
  187. }
  188. if self._brightness_dps:
  189. bright = params.get(ATTR_BRIGHTNESS, 255)
  190. bright_values = self._brightness_dps.get_values_to_set(
  191. self._device,
  192. bright,
  193. )
  194. settings = {
  195. **settings,
  196. **bright_values,
  197. }
  198. if self._rgbhsv_dps:
  199. rgbw = params.get(ATTR_RGBW_COLOR, None)
  200. if rgbw:
  201. rgb = (rgbw[0], rgbw[1], rgbw[2])
  202. hs = color_util.color_RGB_to_hs(rgbw[0], rgbw[1], rgbw[2])
  203. color = "{:02x}{:02x}{:02x}{:04x}{:02x}{:02x}".format(
  204. round(rgbw[0]),
  205. round(rgbw[1]),
  206. round(rgbw[2]),
  207. round(hs[0]),
  208. round(hs[1]),
  209. round(rgbw[3] * 100 / 255),
  210. )
  211. color_dps = self._rgbhsv_dps.get_values_to_set(
  212. self._device,
  213. color,
  214. )
  215. settings = {**settings, **color_dps}
  216. if self._effect_dps:
  217. effect = params.get(ATTR_EFFECT, None)
  218. if effect:
  219. effect_values = self._effect_dps.get_values_to_set(
  220. self._device,
  221. effect,
  222. )
  223. settings = {
  224. **settings,
  225. **effect_values,
  226. }
  227. await self._device.async_set_properties(settings)
  228. async def async_turn_off(self):
  229. if self._switch_dps:
  230. await self._switch_dps.async_set_value(self._device, False)
  231. elif self._brightness_dps:
  232. await self._brightness_dps.async_set_value(self._device, 0)
  233. else:
  234. raise NotImplementedError()
  235. async def async_toggle(self):
  236. disp_on = self.is_on
  237. await (self.async_turn_on() if not disp_on else self.async_turn_off())