1
0

relay_switch.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import logging
  2. import time
  3. from typing import Any
  4. from bleak.backends.device import BLEDevice
  5. from ..const import SwitchbotModel
  6. from ..models import SwitchBotAdvertisement
  7. from .device import SwitchbotEncryptedDevice
  8. _LOGGER = logging.getLogger(__name__)
  9. COMMAND_HEADER = "57"
  10. COMMAND_TURN_OFF = f"{COMMAND_HEADER}0f70010000"
  11. COMMAND_TURN_ON = f"{COMMAND_HEADER}0f70010100"
  12. COMMAND_TOGGLE = f"{COMMAND_HEADER}0f70010200"
  13. COMMAND_GET_VOLTAGE_AND_CURRENT = f"{COMMAND_HEADER}0f7106000000"
  14. COMMAND_GET_SWITCH_STATE = f"{COMMAND_HEADER}0f7101000000"
  15. PASSIVE_POLL_INTERVAL = 10 * 60
  16. class SwitchbotRelaySwitch(SwitchbotEncryptedDevice):
  17. """Representation of a Switchbot relay switch 1pm."""
  18. def __init__(
  19. self,
  20. device: BLEDevice,
  21. key_id: str,
  22. encryption_key: str,
  23. interface: int = 0,
  24. model: SwitchbotModel = SwitchbotModel.RELAY_SWITCH_1PM,
  25. **kwargs: Any,
  26. ) -> None:
  27. self._force_next_update = False
  28. super().__init__(device, key_id, encryption_key, model, interface, **kwargs)
  29. @classmethod
  30. async def verify_encryption_key(
  31. cls,
  32. device: BLEDevice,
  33. key_id: str,
  34. encryption_key: str,
  35. model: SwitchbotModel = SwitchbotModel.RELAY_SWITCH_1PM,
  36. **kwargs: Any,
  37. ) -> bool:
  38. return await super().verify_encryption_key(
  39. device, key_id, encryption_key, model, **kwargs
  40. )
  41. def update_from_advertisement(self, advertisement: SwitchBotAdvertisement) -> None:
  42. """Update device data from advertisement."""
  43. # Obtain voltage and current through command.
  44. adv_data = advertisement.data["data"]
  45. if previous_voltage := self._get_adv_value("voltage"):
  46. adv_data["voltage"] = previous_voltage
  47. if previous_current := self._get_adv_value("current"):
  48. adv_data["current"] = previous_current
  49. current_state = self._get_adv_value("sequence_number")
  50. super().update_from_advertisement(advertisement)
  51. new_state = self._get_adv_value("sequence_number")
  52. _LOGGER.debug(
  53. "%s: update advertisement: %s (seq before: %s) (seq after: %s)",
  54. self.name,
  55. advertisement,
  56. current_state,
  57. new_state,
  58. )
  59. if current_state != new_state:
  60. self._force_next_update = True
  61. async def update(self, interface: int | None = None) -> None:
  62. """Update state of device."""
  63. if info := await self.get_voltage_and_current():
  64. self._last_full_update = time.monotonic()
  65. self._update_parsed_data(info)
  66. self._fire_callbacks()
  67. async def get_voltage_and_current(self) -> dict[str, Any] | None:
  68. """Get voltage and current because advtisement don't have these"""
  69. result = await self._send_command(COMMAND_GET_VOLTAGE_AND_CURRENT)
  70. ok = self._check_command_result(result, 0, {1})
  71. if ok:
  72. return {
  73. "voltage": ((result[9] << 8) + result[10]) / 10,
  74. "current": (result[11] << 8) + result[12],
  75. }
  76. return None
  77. async def get_basic_info(self) -> dict[str, Any] | None:
  78. """Get the current state of the switch."""
  79. result = await self._send_command(COMMAND_GET_SWITCH_STATE)
  80. if self._check_command_result(result, 0, {1}):
  81. return {
  82. "is_on": result[1] & 0x01 != 0,
  83. }
  84. return None
  85. def poll_needed(self, seconds_since_last_poll: float | None) -> bool:
  86. """Return if device needs polling."""
  87. if self._force_next_update:
  88. self._force_next_update = False
  89. return True
  90. if (
  91. seconds_since_last_poll is not None
  92. and seconds_since_last_poll < PASSIVE_POLL_INTERVAL
  93. ):
  94. return False
  95. time_since_last_full_update = time.monotonic() - self._last_full_update
  96. if time_since_last_full_update < PASSIVE_POLL_INTERVAL:
  97. return False
  98. return True
  99. async def turn_on(self) -> bool:
  100. """Turn device on."""
  101. result = await self._send_command(COMMAND_TURN_ON)
  102. ok = self._check_command_result(result, 0, {1})
  103. if ok:
  104. self._override_state({"isOn": True})
  105. self._fire_callbacks()
  106. return ok
  107. async def turn_off(self) -> bool:
  108. """Turn device off."""
  109. result = await self._send_command(COMMAND_TURN_OFF)
  110. ok = self._check_command_result(result, 0, {1})
  111. if ok:
  112. self._override_state({"isOn": False})
  113. self._fire_callbacks()
  114. return ok
  115. async def async_toggle(self, **kwargs) -> bool:
  116. """Toggle device."""
  117. result = await self._send_command(COMMAND_TOGGLE)
  118. status = self._check_command_result(result, 0, {1})
  119. return status
  120. def is_on(self) -> bool | None:
  121. """Return switch state from cache."""
  122. return self._get_adv_value("isOn")