1
0

evaporative_humidifier.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import logging
  2. from typing import Any
  3. from bleak.backends.device import BLEDevice
  4. from ..const import SwitchbotModel
  5. from ..const.evaporative_humidifier import (
  6. TARGET_HUMIDITY_MODES,
  7. HumidifierMode,
  8. HumidifierWaterLevel,
  9. )
  10. from ..models import SwitchBotAdvertisement
  11. from .device import SwitchbotEncryptedDevice
  12. _LOGGER = logging.getLogger(__name__)
  13. COMMAND_HEADER = "57"
  14. COMMAND_GET_CK_IV = f"{COMMAND_HEADER}0f2103"
  15. COMMAND_TURN_ON = f"{COMMAND_HEADER}0f430101"
  16. COMMAND_TURN_OFF = f"{COMMAND_HEADER}0f430100"
  17. COMMAND_CHILD_LOCK_ON = f"{COMMAND_HEADER}0f430501"
  18. COMMAND_CHILD_LOCK_OFF = f"{COMMAND_HEADER}0f430500"
  19. COMMAND_AUTO_DRY_ON = f"{COMMAND_HEADER}0f430a01"
  20. COMMAND_AUTO_DRY_OFF = f"{COMMAND_HEADER}0f430a02"
  21. COMMAND_SET_MODE = f"{COMMAND_HEADER}0f4302"
  22. COMMAND_GET_BASIC_INFO = f"{COMMAND_HEADER}000300"
  23. MODES_COMMANDS = {
  24. HumidifierMode.HIGH: "010100",
  25. HumidifierMode.MEDIUM: "010200",
  26. HumidifierMode.LOW: "010300",
  27. HumidifierMode.QUIET: "010400",
  28. HumidifierMode.TARGET_HUMIDITY: "0200",
  29. HumidifierMode.SLEEP: "0300",
  30. HumidifierMode.AUTO: "040000",
  31. }
  32. class SwitchbotEvaporativeHumidifier(SwitchbotEncryptedDevice):
  33. """Representation of a Switchbot Evaporative Humidifier"""
  34. def __init__(
  35. self,
  36. device: BLEDevice,
  37. key_id: str,
  38. encryption_key: str,
  39. interface: int = 0,
  40. model: SwitchbotModel = SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
  41. **kwargs: Any,
  42. ) -> None:
  43. self._force_next_update = False
  44. super().__init__(device, key_id, encryption_key, model, interface, **kwargs)
  45. @classmethod
  46. async def verify_encryption_key(
  47. cls,
  48. device: BLEDevice,
  49. key_id: str,
  50. encryption_key: str,
  51. model: SwitchbotModel = SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
  52. **kwargs: Any,
  53. ) -> bool:
  54. return await super().verify_encryption_key(
  55. device, key_id, encryption_key, model, **kwargs
  56. )
  57. def update_from_advertisement(self, advertisement: SwitchBotAdvertisement) -> None:
  58. """Update device data from advertisement."""
  59. super().update_from_advertisement(advertisement)
  60. _LOGGER.debug(
  61. "%s: update advertisement: %s",
  62. self.name,
  63. advertisement,
  64. )
  65. async def _get_basic_info(self) -> bytes | None:
  66. """Return basic info of device."""
  67. _data = await self._send_command(
  68. key=COMMAND_GET_BASIC_INFO, retry=self._retry_count
  69. )
  70. if _data in (b"\x07", b"\x00"):
  71. _LOGGER.error("Unsuccessful, please try again")
  72. return None
  73. return _data
  74. async def get_basic_info(self) -> dict[str, Any] | None:
  75. """Get device basic settings."""
  76. if not (_data := await self._get_basic_info()):
  77. return None
  78. # Not 100% sure about this data, will verify once a firmware update is available
  79. return {
  80. "firmware": _data[2] / 10.0,
  81. }
  82. async def turn_on(self) -> bool:
  83. """Turn device on."""
  84. result = await self._send_command(COMMAND_TURN_ON)
  85. if ok := self._check_command_result(result, 0, {1}):
  86. self._override_state({"isOn": True})
  87. self._fire_callbacks()
  88. return ok
  89. async def turn_off(self) -> bool:
  90. """Turn device off."""
  91. result = await self._send_command(COMMAND_TURN_OFF)
  92. if ok := self._check_command_result(result, 0, {1}):
  93. self._override_state({"isOn": False})
  94. self._fire_callbacks()
  95. return ok
  96. async def set_mode(
  97. self, mode: HumidifierMode, target_humidity: int | None = None
  98. ) -> bool:
  99. """Set device mode."""
  100. if mode == HumidifierMode.DRYING_FILTER:
  101. return await self.start_drying_filter()
  102. elif mode not in MODES_COMMANDS:
  103. raise ValueError("Invalid mode")
  104. command = COMMAND_SET_MODE + MODES_COMMANDS[mode]
  105. if mode in TARGET_HUMIDITY_MODES:
  106. if target_humidity is None:
  107. raise TypeError("target_humidity is required")
  108. command += f"{target_humidity:02x}"
  109. result = await self._send_command(command)
  110. if ok := self._check_command_result(result, 0, {1}):
  111. self._override_state({"mode": mode})
  112. if mode == HumidifierMode.TARGET_HUMIDITY and target_humidity is not None:
  113. self._override_state({"target_humidity": target_humidity})
  114. self._fire_callbacks()
  115. return ok
  116. async def set_child_lock(self, enabled: bool) -> bool:
  117. """Set child lock."""
  118. result = await self._send_command(
  119. COMMAND_CHILD_LOCK_ON if enabled else COMMAND_CHILD_LOCK_OFF
  120. )
  121. if ok := self._check_command_result(result, 0, {1}):
  122. self._override_state({"child_lock": enabled})
  123. self._fire_callbacks()
  124. return ok
  125. async def start_drying_filter(self):
  126. """Start drying filter."""
  127. result = await self._send_command(COMMAND_TURN_ON + "08")
  128. if ok := self._check_command_result(result, 0, {1}):
  129. self._override_state({"mode": HumidifierMode.DRYING_FILTER})
  130. self._fire_callbacks()
  131. return ok
  132. async def stop_drying_filter(self):
  133. """Stop drying filter."""
  134. result = await self._send_command(COMMAND_TURN_OFF)
  135. if ok := self._check_command_result(result, 0, {0}):
  136. self._override_state({"isOn": False, "mode": None})
  137. self._fire_callbacks()
  138. return ok
  139. def is_on(self) -> bool | None:
  140. """Return state from cache."""
  141. return self._get_adv_value("isOn")
  142. def get_mode(self) -> HumidifierMode | None:
  143. """Return state from cache."""
  144. return self._get_adv_value("mode")
  145. def is_child_lock_enabled(self) -> bool | None:
  146. """Return state from cache."""
  147. return self._get_adv_value("child_lock")
  148. def is_over_humidify_protection_enabled(self) -> bool | None:
  149. """Return state from cache."""
  150. return self._get_adv_value("over_humidify_protection")
  151. def is_tank_removed(self) -> bool | None:
  152. """Return state from cache."""
  153. return self._get_adv_value("tank_removed")
  154. def is_filter_missing(self) -> bool | None:
  155. """Return state from cache."""
  156. return self._get_adv_value("filter_missing")
  157. def is_filter_alert_on(self) -> bool | None:
  158. """Return state from cache."""
  159. return self._get_adv_value("filter_alert")
  160. def is_tilted_alert_on(self) -> bool | None:
  161. """Return state from cache."""
  162. return self._get_adv_value("tilted_alert")
  163. def get_water_level(self) -> HumidifierWaterLevel | None:
  164. """Return state from cache."""
  165. return self._get_adv_value("water_level")
  166. def get_filter_run_time(self) -> int | None:
  167. """Return state from cache."""
  168. return self._get_adv_value("filter_run_time")
  169. def get_target_humidity(self) -> int | None:
  170. """Return state from cache."""
  171. return self._get_adv_value("target_humidity")
  172. def get_humidity(self) -> int | None:
  173. """Return state from cache."""
  174. return self._get_adv_value("humidity")
  175. def get_temperature(self) -> float | None:
  176. """Return state from cache."""
  177. return self._get_adv_value("temperature")