light_strip.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. from __future__ import annotations
  2. import logging
  3. from typing import Any
  4. from bleak.backends.device import BLEDevice
  5. from ..const import SwitchbotModel
  6. from ..const.light import ColorMode, StripLightColorMode
  7. from .base_light import SwitchbotSequenceBaseLight
  8. from .device import (
  9. REQ_HEADER,
  10. SwitchbotEncryptedDevice,
  11. SwitchbotOperationError,
  12. update_after_operation,
  13. )
  14. STRIP_COMMMAND_HEADER = "4901"
  15. STRIP_REQUEST = f"{REQ_HEADER}4A01"
  16. STRIP_COMMAND = f"{REQ_HEADER}{STRIP_COMMMAND_HEADER}"
  17. # Strip keys
  18. STRIP_ON_KEY = f"{STRIP_COMMAND}01"
  19. STRIP_OFF_KEY = f"{STRIP_COMMAND}02"
  20. COLOR_TEMP_KEY = f"{STRIP_COMMAND}11"
  21. RGB_BRIGHTNESS_KEY = f"{STRIP_COMMAND}12"
  22. BRIGHTNESS_KEY = f"{STRIP_COMMAND}14"
  23. DEVICE_GET_VERSION_KEY = "570003"
  24. _LOGGER = logging.getLogger(__name__)
  25. EFFECT_DICT = {
  26. "Christmas": [
  27. "570F49070200033C01",
  28. "570F490701000600009902006D0EFF0021",
  29. "570F490701000603009902006D0EFF0021",
  30. ],
  31. "Halloween": ["570F49070200053C04", "570F490701000300FF6A009E00ED00EA0F"],
  32. "Sunset": [
  33. "570F49070200033C3C",
  34. "570F490701000900FF9000ED8C04DD5800",
  35. "570F490701000903FF2E008E0B004F0500",
  36. "570F4907010009063F0010270056140033",
  37. ],
  38. "Vitality": [
  39. "570F49070200053C02",
  40. "570F490701000600C5003FD9530AEC9800",
  41. "570F490701000603FFDF0000895500468B",
  42. ],
  43. "Flashing": [
  44. "570F49070200053C02",
  45. "570F4907010006000000FF00FF00FF0000",
  46. "570F490701000603FFFF0000FFFFA020F0",
  47. ],
  48. "Strobe": ["570F49070200043C02", "570F490701000300FF00E19D70FFFF0515"],
  49. "Fade": [
  50. "570F49070200043C04",
  51. "570F490701000500FF5481FF00E19D70FF",
  52. "570F490701000503FF0515FF7FEB",
  53. ],
  54. "Smooth": [
  55. "570F49070200033C02",
  56. "570F4907010007000036FC00F6FF00ED13",
  57. "570F490701000703F6FF00FF8300FF0800",
  58. "570F490701000706FF00E1",
  59. ],
  60. "Forest": [
  61. "570F49070200033C06",
  62. "570F490701000400006400228B223CB371",
  63. "570F49070100040390EE90",
  64. ],
  65. "Ocean": [
  66. "570F49070200033C06",
  67. "570F4907010007004400FF0061FF007BFF",
  68. "570F490701000703009DFF00B2FF00CBFF",
  69. "570F49070100070600E9FF",
  70. ],
  71. "Autumn": [
  72. "570F49070200043C05",
  73. "570F490701000700D10035922D13A16501",
  74. "570F490701000703AB9100DD8C00F4AA29",
  75. "570F490701000706E8D000",
  76. ],
  77. "Cool": [
  78. "570F49070200043C04",
  79. "570F490701000600001A63006C9A00468B",
  80. "570F490701000603009DA50089BE4378B6",
  81. ],
  82. "Flow": [
  83. "570F49070200033C02",
  84. "570F490701000600FF00D8E100FFAA00FF",
  85. "570F4907010006037F00FF5000FF1900FF",
  86. ],
  87. "Relax": [
  88. "570F49070200033C03",
  89. "570F490701000400FF8C00FF7200FF1D00",
  90. "570F490701000403FF5500",
  91. ],
  92. "Modern": [
  93. "570F49070200043C03",
  94. "570F49070100060089231A5F8969829E5A",
  95. "570F490701000603BCB05EEDBE5AFF9D60",
  96. ],
  97. "Rose": [
  98. "570F49070200043C04",
  99. "570F490701000500FF1969BC215F7C0225",
  100. "570F490701000503600C2B35040C",
  101. ],
  102. }
  103. # Private mapping from device-specific color modes to original ColorMode enum
  104. _STRIP_LIGHT_COLOR_MODE_MAP = {
  105. StripLightColorMode.RGB: ColorMode.RGB,
  106. StripLightColorMode.SCENE: ColorMode.EFFECT,
  107. StripLightColorMode.MUSIC: ColorMode.EFFECT,
  108. StripLightColorMode.CONTROLLER: ColorMode.EFFECT,
  109. StripLightColorMode.COLOR_TEMP: ColorMode.COLOR_TEMP,
  110. StripLightColorMode.UNKNOWN: ColorMode.OFF,
  111. }
  112. class SwitchbotLightStrip(SwitchbotSequenceBaseLight):
  113. """Representation of a Switchbot light strip."""
  114. @property
  115. def color_modes(self) -> set[ColorMode]:
  116. """Return the supported color modes."""
  117. return {ColorMode.RGB}
  118. @property
  119. def color_mode(self) -> ColorMode:
  120. """Return the current color mode."""
  121. device_mode = StripLightColorMode(self._get_adv_value("color_mode") or 10)
  122. return _STRIP_LIGHT_COLOR_MODE_MAP.get(device_mode, ColorMode.OFF)
  123. @property
  124. def get_effect_list(self) -> list[str]:
  125. """Return the list of supported effects."""
  126. return list(EFFECT_DICT.keys())
  127. @update_after_operation
  128. async def turn_on(self) -> bool:
  129. """Turn device on."""
  130. result = await self._send_command(STRIP_ON_KEY)
  131. return self._check_command_result(result, 0, {1})
  132. @update_after_operation
  133. async def turn_off(self) -> bool:
  134. """Turn device off."""
  135. result = await self._send_command(STRIP_OFF_KEY)
  136. return self._check_command_result(result, 0, {1})
  137. @update_after_operation
  138. async def set_brightness(self, brightness: int) -> bool:
  139. """Set brightness."""
  140. assert 0 <= brightness <= 100, "Brightness must be between 0 and 100"
  141. result = await self._send_command(f"{BRIGHTNESS_KEY}{brightness:02X}")
  142. return self._check_command_result(result, 0, {1})
  143. @update_after_operation
  144. async def set_rgb(self, brightness: int, r: int, g: int, b: int) -> bool:
  145. """Set rgb."""
  146. assert 0 <= brightness <= 100, "Brightness must be between 0 and 100"
  147. assert 0 <= r <= 255, "r must be between 0 and 255"
  148. assert 0 <= g <= 255, "g must be between 0 and 255"
  149. assert 0 <= b <= 255, "b must be between 0 and 255"
  150. result = await self._send_command(
  151. f"{RGB_BRIGHTNESS_KEY}{brightness:02X}{r:02X}{g:02X}{b:02X}"
  152. )
  153. return self._check_command_result(result, 0, {1})
  154. @update_after_operation
  155. async def set_effect(self, effect: str) -> bool:
  156. """Set effect."""
  157. effect_template = EFFECT_DICT.get(effect)
  158. if not effect_template:
  159. raise SwitchbotOperationError(f"Effect {effect} not supported")
  160. result = await self._send_multiple_commands(effect_template)
  161. if result:
  162. self._override_state({"effect": effect})
  163. return result
  164. async def get_basic_info(
  165. self,
  166. device_get_basic_info: str = STRIP_REQUEST,
  167. device_get_version_info: str = DEVICE_GET_VERSION_KEY,
  168. ) -> dict[str, Any] | None:
  169. """Get device basic settings."""
  170. if not (_data := await self._get_basic_info(device_get_basic_info)):
  171. return None
  172. if not (_version_info := await self._get_basic_info(device_get_version_info)):
  173. return None
  174. _LOGGER.debug(
  175. "data: %s, version info: %s, address: %s",
  176. _data,
  177. _version_info,
  178. self._device.address,
  179. )
  180. self._state["r"] = _data[3]
  181. self._state["g"] = _data[4]
  182. self._state["b"] = _data[5]
  183. self._state["cw"] = int.from_bytes(_data[7:9], "big")
  184. return {
  185. "isOn": bool(_data[1] & 0b10000000),
  186. "brightness": _data[2] & 0b01111111,
  187. "r": self._state["r"],
  188. "g": self._state["g"],
  189. "b": self._state["b"],
  190. "cw": self._state["cw"],
  191. "color_mode": _data[10] & 0b00001111,
  192. "firmware": _version_info[2] / 10.0,
  193. }
  194. class SwitchbotStripLight3(SwitchbotEncryptedDevice, SwitchbotLightStrip):
  195. """Support for switchbot strip light3 and floor lamp."""
  196. def __init__(
  197. self,
  198. device: BLEDevice,
  199. key_id: str,
  200. encryption_key: str,
  201. interface: int = 0,
  202. model: SwitchbotModel = SwitchbotModel.STRIP_LIGHT_3,
  203. **kwargs: Any,
  204. ) -> None:
  205. super().__init__(device, key_id, encryption_key, model, interface, **kwargs)
  206. @classmethod
  207. async def verify_encryption_key(
  208. cls,
  209. device: BLEDevice,
  210. key_id: str,
  211. encryption_key: str,
  212. model: SwitchbotModel = SwitchbotModel.STRIP_LIGHT_3,
  213. **kwargs: Any,
  214. ) -> bool:
  215. return await super().verify_encryption_key(
  216. device, key_id, encryption_key, model, **kwargs
  217. )
  218. @property
  219. def color_modes(self) -> set[ColorMode]:
  220. """Return the supported color modes."""
  221. return {ColorMode.RGB, ColorMode.COLOR_TEMP}
  222. @update_after_operation
  223. async def set_color_temp(self, brightness: int, color_temp: int) -> bool:
  224. """Set color temp."""
  225. assert 0 <= brightness <= 100
  226. assert self.min_temp <= color_temp <= self.max_temp
  227. result = await self._send_command(
  228. f"{COLOR_TEMP_KEY}{brightness:02X}{color_temp:04X}"
  229. )
  230. return self._check_command_result(result, 0, {1})