-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
/
coordinator.py
126 lines (109 loc) · 3.96 KB
/
coordinator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""Provides the switchbot DataUpdateCoordinator."""
from __future__ import annotations
import asyncio
import contextlib
import logging
from typing import TYPE_CHECKING
import switchbot
from switchbot import SwitchbotModel
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth.active_update_coordinator import (
ActiveBluetoothDataUpdateCoordinator,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CoreState, HomeAssistant, callback
if TYPE_CHECKING:
from bleak.backends.device import BLEDevice
_LOGGER = logging.getLogger(__name__)
DEVICE_STARTUP_TIMEOUT = 30
type SwitchbotConfigEntry = ConfigEntry[SwitchbotDataUpdateCoordinator]
class SwitchbotDataUpdateCoordinator(ActiveBluetoothDataUpdateCoordinator[None]):
"""Class to manage fetching switchbot data."""
def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
ble_device: BLEDevice,
device: switchbot.SwitchbotDevice,
base_unique_id: str,
device_name: str,
connectable: bool,
model: SwitchbotModel,
) -> None:
"""Initialize global switchbot data updater."""
super().__init__(
hass=hass,
logger=logger,
address=ble_device.address,
needs_poll_method=self._needs_poll,
poll_method=self._async_update,
mode=bluetooth.BluetoothScanningMode.ACTIVE,
connectable=connectable,
)
self.ble_device = ble_device
self.device = device
self.device_name = device_name
self.base_unique_id = base_unique_id
self.model = model
self._ready_event = asyncio.Event()
self._was_unavailable = True
@callback
def _needs_poll(
self,
service_info: bluetooth.BluetoothServiceInfoBleak,
seconds_since_last_poll: float | None,
) -> bool:
# Only poll if hass is running, we need to poll,
# and we actually have a way to connect to the device
return (
self.hass.state is CoreState.running
and self.device.poll_needed(seconds_since_last_poll)
and bool(
bluetooth.async_ble_device_from_address(
self.hass, service_info.device.address, connectable=True
)
)
)
async def _async_update(
self, service_info: bluetooth.BluetoothServiceInfoBleak
) -> None:
"""Poll the device."""
await self.device.update()
@callback
def _async_handle_unavailable(
self, service_info: bluetooth.BluetoothServiceInfoBleak
) -> None:
"""Handle the device going unavailable."""
super()._async_handle_unavailable(service_info)
self._was_unavailable = True
@callback
def _async_handle_bluetooth_event(
self,
service_info: bluetooth.BluetoothServiceInfoBleak,
change: bluetooth.BluetoothChange,
) -> None:
"""Handle a Bluetooth event."""
self.ble_device = service_info.device
if not (
adv := switchbot.parse_advertisement_data(
service_info.device, service_info.advertisement, self.model
)
):
return
if "modelName" in adv.data:
self._ready_event.set()
_LOGGER.debug(
"%s: Switchbot data: %s", self.ble_device.address, self.device.data
)
if not self.device.advertisement_changed(adv) and not self._was_unavailable:
return
self._was_unavailable = False
self.device.update_from_advertisement(adv)
super()._async_handle_bluetooth_event(service_info, change)
async def async_wait_ready(self) -> bool:
"""Wait for the device to be ready."""
with contextlib.suppress(TimeoutError):
async with asyncio.timeout(DEVICE_STARTUP_TIMEOUT):
await self._ready_event.wait()
return True
return False