bot.py 4.0 KB

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