1
0

bot.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. """Library to handle connection with Switchbot."""
  2. from __future__ import annotations
  3. import logging
  4. from typing import Any
  5. from .device import (
  6. DEVICE_SET_EXTENDED_KEY,
  7. DEVICE_SET_MODE_KEY,
  8. SwitchbotDeviceOverrideStateDuringConnection,
  9. SwitchbotOperationError,
  10. update_after_operation,
  11. )
  12. _LOGGER = logging.getLogger(__name__)
  13. BOT_COMMAND_HEADER = "5701"
  14. # Bot keys
  15. PRESS_KEY = f"{BOT_COMMAND_HEADER}00"
  16. ON_KEY = f"{BOT_COMMAND_HEADER}01"
  17. OFF_KEY = f"{BOT_COMMAND_HEADER}02"
  18. DOWN_KEY = f"{BOT_COMMAND_HEADER}03"
  19. UP_KEY = f"{BOT_COMMAND_HEADER}04"
  20. class Switchbot(SwitchbotDeviceOverrideStateDuringConnection):
  21. """Representation of a Switchbot."""
  22. def __init__(self, *args: Any, **kwargs: Any) -> None:
  23. """Switchbot Bot/WoHand constructor."""
  24. super().__init__(*args, **kwargs)
  25. self._inverse: bool = kwargs.pop("inverse_mode", False)
  26. @update_after_operation
  27. async def turn_on(self) -> bool:
  28. """Turn device on."""
  29. result = await self._send_command(ON_KEY)
  30. ret = self._check_command_result(result, 0, {1, 5})
  31. self._override_state({"isOn": True})
  32. _LOGGER.debug(
  33. "%s: Turn on result: %s -> %s",
  34. self.name,
  35. result.hex() if result else None,
  36. self._override_adv_data,
  37. )
  38. self._fire_callbacks()
  39. return ret
  40. @update_after_operation
  41. async def turn_off(self) -> bool:
  42. """Turn device off."""
  43. result = await self._send_command(OFF_KEY)
  44. ret = self._check_command_result(result, 0, {1, 5})
  45. self._override_state({"isOn": False})
  46. _LOGGER.debug(
  47. "%s: Turn off result: %s -> %s",
  48. self.name,
  49. result.hex() if result else None,
  50. self._override_adv_data,
  51. )
  52. self._fire_callbacks()
  53. return ret
  54. @update_after_operation
  55. async def hand_up(self) -> bool:
  56. """Raise device arm."""
  57. result = await self._send_command(UP_KEY)
  58. return self._check_command_result(result, 0, {1, 5})
  59. @update_after_operation
  60. async def hand_down(self) -> bool:
  61. """Lower device arm."""
  62. result = await self._send_command(DOWN_KEY)
  63. return self._check_command_result(result, 0, {1, 5})
  64. @update_after_operation
  65. async def press(self) -> bool:
  66. """Press command to device."""
  67. result = await self._send_command(PRESS_KEY)
  68. return self._check_command_result(result, 0, {1, 5})
  69. @update_after_operation
  70. async def set_switch_mode(
  71. self, switch_mode: bool = False, strength: int = 100, inverse: bool = False
  72. ) -> bool:
  73. """Change bot mode."""
  74. mode_key = format(switch_mode, "b") + format(inverse, "b")
  75. strength_key = f"{strength:0{2}x}" # to hex with padding to double digit
  76. result = await self._send_command(DEVICE_SET_MODE_KEY + strength_key + mode_key)
  77. return self._check_command_result(result, 0, {1})
  78. async def set_inverse_direction(self, inverse: bool) -> bool:
  79. """Set the Bot's inverse direction while preserving its other mode settings."""
  80. if not (settings := await self.get_basic_info()):
  81. raise SwitchbotOperationError(
  82. f"{self.name}: Unable to get current Bot mode settings"
  83. )
  84. return await self.set_switch_mode(
  85. switch_mode=settings["switchMode"],
  86. strength=settings["strength"],
  87. inverse=inverse,
  88. )
  89. @update_after_operation
  90. async def set_long_press(self, duration: int = 0) -> bool:
  91. """Set bot long press duration."""
  92. duration_key = f"{duration:0{2}x}" # to hex with padding to double digit
  93. result = await self._send_command(DEVICE_SET_EXTENDED_KEY + "08" + duration_key)
  94. return self._check_command_result(result, 0, {1})
  95. async def get_basic_info(self) -> dict[str, Any] | None:
  96. """Get device basic settings."""
  97. if not (_data := await self._get_basic_info()):
  98. return None
  99. return {
  100. "battery": _data[1],
  101. "firmware": _data[2] / 10.0,
  102. "strength": _data[3],
  103. "timers": _data[8],
  104. "switchMode": bool(_data[9] & 16),
  105. "inverseDirection": bool(_data[9] & 1),
  106. "holdSeconds": _data[10],
  107. }
  108. def is_on(self) -> bool | None:
  109. """Return switch state from cache."""
  110. # To get actual position call update() first.
  111. value = self._get_adv_value("isOn")
  112. if value is None:
  113. return None
  114. if self._inverse:
  115. return not value
  116. return value
  117. def inverse_direction(self) -> bool | None:
  118. """Return the cached inverse direction setting."""
  119. return self._get_adv_value("inverseDirection")