discovery.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. """Discover switchbot devices."""
  2. from __future__ import annotations
  3. import asyncio
  4. import logging
  5. import bleak
  6. from bleak.backends.device import BLEDevice
  7. from bleak.backends.scanner import AdvertisementData
  8. from .adv_parser import parse_advertisement_data
  9. from .const import (DEFAULT_RETRY_COUNT, DEFAULT_RETRY_TIMEOUT,
  10. DEFAULT_SCAN_TIMEOUT)
  11. from .models import SwitchBotAdvertisement
  12. _LOGGER = logging.getLogger(__name__)
  13. CONNECT_LOCK = asyncio.Lock()
  14. class GetSwitchbotDevices:
  15. """Scan for all Switchbot devices and return by type."""
  16. def __init__(self, interface: int = 0) -> None:
  17. """Get switchbot devices class constructor."""
  18. self._interface = f"hci{interface}"
  19. self._adv_data: dict[str, SwitchBotAdvertisement] = {}
  20. def detection_callback(
  21. self,
  22. device: BLEDevice,
  23. advertisement_data: AdvertisementData,
  24. ) -> None:
  25. """Callback for device detection."""
  26. discovery = parse_advertisement_data(device, advertisement_data)
  27. if discovery:
  28. self._adv_data[discovery.address] = discovery
  29. async def discover(
  30. self, retry: int = DEFAULT_RETRY_COUNT, scan_timeout: int = DEFAULT_SCAN_TIMEOUT
  31. ) -> dict:
  32. """Find switchbot devices and their advertisement data."""
  33. devices = None
  34. devices = bleak.BleakScanner(
  35. # TODO: Find new UUIDs to filter on. For example, see
  36. # https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/4ad138bb09f0fbbfa41b152ca327a78c1d0b6ba9/devicetypes/meter.md
  37. adapter=self._interface,
  38. )
  39. devices.register_detection_callback(self.detection_callback)
  40. async with CONNECT_LOCK:
  41. await devices.start()
  42. await asyncio.sleep(scan_timeout)
  43. await devices.stop()
  44. if devices is None:
  45. if retry < 1:
  46. _LOGGER.error(
  47. "Scanning for Switchbot devices failed. Stop trying", exc_info=True
  48. )
  49. return self._adv_data
  50. _LOGGER.warning(
  51. "Error scanning for Switchbot devices. Retrying (remaining: %d)",
  52. retry,
  53. )
  54. await asyncio.sleep(DEFAULT_RETRY_TIMEOUT)
  55. return await self.discover(retry - 1, scan_timeout)
  56. return self._adv_data
  57. async def _get_devices_by_model(
  58. self,
  59. model: str,
  60. ) -> dict:
  61. """Get switchbot devices by type."""
  62. if not self._adv_data:
  63. await self.discover()
  64. return {
  65. address: adv
  66. for address, adv in self._adv_data.items()
  67. if adv.data.get("model") == model
  68. }
  69. async def get_curtains(self) -> dict[str, SwitchBotAdvertisement]:
  70. """Return all WoCurtain/Curtains devices with services data."""
  71. return await self._get_devices_by_model("c")
  72. async def get_bots(self) -> dict[str, SwitchBotAdvertisement]:
  73. """Return all WoHand/Bot devices with services data."""
  74. return await self._get_devices_by_model("H")
  75. async def get_tempsensors(self) -> dict[str, SwitchBotAdvertisement]:
  76. """Return all WoSensorTH/Temp sensor devices with services data."""
  77. base_meters = await self._get_devices_by_model("T")
  78. plus_meters = await self._get_devices_by_model("i")
  79. return {**base_meters, **plus_meters}
  80. async def get_contactsensors(self) -> dict[str, SwitchBotAdvertisement]:
  81. """Return all WoContact/Contact sensor devices with services data."""
  82. return await self._get_devices_by_model("d")
  83. async def get_device_data(
  84. self, address: str
  85. ) -> dict[str, SwitchBotAdvertisement] | None:
  86. """Return data for specific device."""
  87. if not self._adv_data:
  88. await self.discover()
  89. return {
  90. device: adv
  91. for device, adv in self._adv_data.items()
  92. # MacOS uses UUIDs instead of MAC addresses
  93. if adv.data.get("address") == address
  94. }