bulb.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. from __future__ import annotations
  2. import logging
  3. from typing import Any
  4. from ..const.light import BulbColorMode
  5. from .base_light import SwitchbotSequenceBaseLight
  6. from .device import REQ_HEADER, SwitchbotOperationError, update_after_operation
  7. BULB_COMMAND_HEADER = "4701"
  8. BULB_REQUEST = f"{REQ_HEADER}4801"
  9. BULB_COMMAND = f"{REQ_HEADER}{BULB_COMMAND_HEADER}"
  10. # Bulb keys
  11. BULB_ON_KEY = f"{BULB_COMMAND}01"
  12. BULB_OFF_KEY = f"{BULB_COMMAND}02"
  13. RGB_BRIGHTNESS_KEY = f"{BULB_COMMAND}12"
  14. CW_BRIGHTNESS_KEY = f"{BULB_COMMAND}13"
  15. BRIGHTNESS_KEY = f"{BULB_COMMAND}14"
  16. RGB_KEY = f"{BULB_COMMAND}16"
  17. CW_KEY = f"{BULB_COMMAND}17"
  18. DEVICE_GET_VERSION_KEY = "570003"
  19. DEVICE_GET_BASIC_SETTINGS_KEY = "570f4801"
  20. _LOGGER = logging.getLogger(__name__)
  21. EFFECT_DICT = {
  22. "Colorful": "570F4701010300",
  23. "Flickering": "570F4701010301",
  24. "Breathing": "570F4701010302",
  25. }
  26. class SwitchbotBulb(SwitchbotSequenceBaseLight):
  27. """Representation of a Switchbot bulb."""
  28. @property
  29. def color_modes(self) -> set[BulbColorMode]:
  30. """Return the supported color modes."""
  31. return {BulbColorMode.RGB, BulbColorMode.COLOR_TEMP}
  32. @property
  33. def color_mode(self) -> BulbColorMode:
  34. """Return the current color mode."""
  35. return BulbColorMode(self._get_adv_value("color_mode") or 10)
  36. @property
  37. def get_effect_list(self) -> list[str]:
  38. """Return the list of supported effects."""
  39. return list(EFFECT_DICT.keys())
  40. @update_after_operation
  41. async def turn_on(self) -> bool:
  42. """Turn device on."""
  43. result = await self._send_command(BULB_ON_KEY)
  44. return self._check_command_result(result, 0, {1})
  45. @update_after_operation
  46. async def turn_off(self) -> bool:
  47. """Turn device off."""
  48. result = await self._send_command(BULB_OFF_KEY)
  49. return self._check_command_result(result, 0, {1})
  50. @update_after_operation
  51. async def set_brightness(self, brightness: int) -> bool:
  52. """Set brightness."""
  53. assert 0 <= brightness <= 100, "Brightness must be between 0 and 100"
  54. result = await self._send_command(f"{BRIGHTNESS_KEY}{brightness:02X}")
  55. return self._check_command_result(result, 0, {1})
  56. @update_after_operation
  57. async def set_color_temp(self, brightness: int, color_temp: int) -> bool:
  58. """Set color temp."""
  59. assert 0 <= brightness <= 100, "Brightness must be between 0 and 100"
  60. assert 2700 <= color_temp <= 6500, "Color Temp must be between 2700 and 6500"
  61. result = await self._send_command(
  62. f"{CW_BRIGHTNESS_KEY}{brightness:02X}{color_temp:04X}"
  63. )
  64. return self._check_command_result(result, 0, {1})
  65. @update_after_operation
  66. async def set_rgb(self, brightness: int, r: int, g: int, b: int) -> bool:
  67. """Set rgb."""
  68. assert 0 <= brightness <= 100, "Brightness must be between 0 and 100"
  69. assert 0 <= r <= 255, "r must be between 0 and 255"
  70. assert 0 <= g <= 255, "g must be between 0 and 255"
  71. assert 0 <= b <= 255, "b must be between 0 and 255"
  72. result = await self._send_command(
  73. f"{RGB_BRIGHTNESS_KEY}{brightness:02X}{r:02X}{g:02X}{b:02X}"
  74. )
  75. return self._check_command_result(result, 0, {1})
  76. @update_after_operation
  77. async def set_effect(self, effect: str) -> bool:
  78. """Set effect."""
  79. effect_template = EFFECT_DICT.get(effect)
  80. if not effect_template:
  81. raise SwitchbotOperationError(f"Effect {effect} not supported")
  82. result = await self._send_command(effect_template)
  83. if result:
  84. self._override_state({"effect": effect})
  85. return result
  86. async def get_basic_info(self) -> dict[str, Any] | None:
  87. """Get device basic settings."""
  88. if not (_data := await self._get_basic_info(DEVICE_GET_BASIC_SETTINGS_KEY)):
  89. return None
  90. if not (_version_info := await self._get_basic_info(DEVICE_GET_VERSION_KEY)):
  91. return None
  92. _LOGGER.debug(
  93. "data: %s, version info: %s, address: %s",
  94. _data,
  95. _version_info,
  96. self._device.address,
  97. )
  98. self._state["r"] = _data[3]
  99. self._state["g"] = _data[4]
  100. self._state["b"] = _data[5]
  101. self._state["cw"] = int.from_bytes(_data[6:8], "big")
  102. return {
  103. "isOn": bool(_data[1] & 0b10000000),
  104. "brightness": _data[2] & 0b01111111,
  105. "r": self._state["r"],
  106. "g": self._state["g"],
  107. "b": self._state["b"],
  108. "cw": self._state["cw"],
  109. "color_mode": _data[10] & 0b00001111,
  110. "firmware": _version_info[2] / 10.0,
  111. }