art_frame.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. """Device handler for the Art Frame."""
  2. import logging
  3. from typing import Any
  4. from bleak.backends.device import BLEDevice
  5. from ..const import SwitchbotModel
  6. from .device import (
  7. SwitchbotEncryptedDevice,
  8. SwitchbotSequenceDevice,
  9. update_after_operation,
  10. )
  11. _LOGGER = logging.getLogger(__name__)
  12. COMMAND_SET_IMAGE = "570F7A02{}"
  13. class SwitchbotArtFrame(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
  14. """Representation of a Switchbot Art Frame."""
  15. def __init__(
  16. self,
  17. device: BLEDevice,
  18. key_id: str,
  19. encryption_key: str,
  20. interface: int = 0,
  21. model: SwitchbotModel = SwitchbotModel.ART_FRAME,
  22. **kwargs: Any,
  23. ) -> None:
  24. super().__init__(device, key_id, encryption_key, model, interface, **kwargs)
  25. self.response_flag = True
  26. @classmethod
  27. async def verify_encryption_key(
  28. cls,
  29. device: BLEDevice,
  30. key_id: str,
  31. encryption_key: str,
  32. model: SwitchbotModel = SwitchbotModel.ART_FRAME,
  33. **kwargs: Any,
  34. ) -> bool:
  35. return await super().verify_encryption_key(
  36. device, key_id, encryption_key, model, **kwargs
  37. )
  38. async def get_basic_info(self) -> dict[str, Any] | None:
  39. """Get device basic settings."""
  40. if not (_data := await self._get_basic_info()):
  41. return None
  42. _LOGGER.debug("basic info data: %s", _data.hex())
  43. battery_charging = bool(_data[1] & 0x80)
  44. battery = _data[1] & 0x7F
  45. firmware = _data[2] / 10.0
  46. hardware = _data[3]
  47. display_size = (_data[4] >> 4) & 0x0F
  48. display_mode = (_data[4] >> 3) & 0x01
  49. last_network_status = (_data[4] >> 2) & 0x01
  50. current_image_index = _data[5]
  51. total_num_of_images = _data[6]
  52. all_images_index = [_data[x] for x in range(7, 7 + total_num_of_images)]
  53. basic_info = {
  54. "battery_charging": battery_charging,
  55. "battery": battery,
  56. "firmware": firmware,
  57. "hardware": hardware,
  58. "display_size": display_size,
  59. "display_mode": display_mode,
  60. "last_network_status": last_network_status,
  61. "current_image_index": current_image_index,
  62. "total_num_of_images": total_num_of_images,
  63. "all_images_index": all_images_index,
  64. }
  65. _LOGGER.debug("Art Frame %s basic info: %s", self._device.address, basic_info)
  66. return basic_info
  67. def _select_image_index(self, offset: int) -> int:
  68. """Select the image index based on the current index and offset."""
  69. current_index = self.get_current_image_index()
  70. all_images_index = self.get_all_images_index()
  71. if not all_images_index or len(all_images_index) <= 1:
  72. raise RuntimeError("No images available to select from.")
  73. new_position = (all_images_index.index(current_index) + offset) % len(
  74. all_images_index
  75. )
  76. return all_images_index[new_position]
  77. async def _get_current_image_index(self) -> None:
  78. """Validate the current image index."""
  79. if not await self.get_basic_info():
  80. raise RuntimeError("Failed to retrieve basic info for current image index.")
  81. @update_after_operation
  82. async def next_image(self) -> bool:
  83. """Display the next image."""
  84. await self._get_current_image_index()
  85. idx = self._select_image_index(1)
  86. result = await self._send_command(COMMAND_SET_IMAGE.format(f"{idx:02X}"))
  87. return self._check_command_result(result, 0, {1})
  88. @update_after_operation
  89. async def prev_image(self) -> bool:
  90. """Display the previous image."""
  91. await self._get_current_image_index()
  92. idx = self._select_image_index(-1)
  93. result = await self._send_command(COMMAND_SET_IMAGE.format(f"{idx:02X}"))
  94. return self._check_command_result(result, 0, {1})
  95. @update_after_operation
  96. async def set_image(self, index: int) -> bool:
  97. """Set the image by index."""
  98. await self._get_current_image_index()
  99. total_images = self.get_total_images()
  100. if index < 0 or index >= total_images:
  101. raise ValueError(
  102. f"Image index {index} is out of range. Total images: {total_images}."
  103. )
  104. all_images_index = self.get_all_images_index()
  105. img_index = all_images_index[index]
  106. result = await self._send_command(COMMAND_SET_IMAGE.format(f"{img_index:02X}"))
  107. return self._check_command_result(result, 0, {1})
  108. def get_all_images_index(self) -> list[int] | None:
  109. """Return cached list of all image indexes."""
  110. return self._get_adv_value("all_images_index")
  111. def get_current_image_index(self) -> int | None:
  112. """Return cached current image index."""
  113. return self._get_adv_value("current_image_index")
  114. def get_total_images(self) -> int | None:
  115. """Return cached total number of images."""
  116. return self._get_adv_value("total_num_of_images")