art_frame.py 4.9 KB

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