-
-
Notifications
You must be signed in to change notification settings - Fork 31.5k
/
update_coordinator.py
122 lines (106 loc) · 3.65 KB
/
update_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
"""Update coordinator for the Bluetooth integration."""
from __future__ import annotations
from abc import ABC, abstractmethod
import logging
from habluetooth import BluetoothScanningMode
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from .api import (
async_address_present,
async_last_service_info,
async_register_callback,
async_track_unavailable,
)
from .match import BluetoothCallbackMatcher
from .models import BluetoothChange, BluetoothServiceInfoBleak
class BasePassiveBluetoothCoordinator(ABC):
"""Base class for passive bluetooth coordinator for bluetooth advertisements.
The coordinator is responsible for tracking devices.
"""
def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
address: str,
mode: BluetoothScanningMode,
connectable: bool,
) -> None:
"""Initialize the coordinator."""
self.hass = hass
self.logger = logger
self.address = address
self.connectable = connectable
self._on_stop: list[CALLBACK_TYPE] = []
self.mode = mode
self._last_unavailable_time = 0.0
self._last_name = address
# Subclasses are responsible for setting _available to True
# when the abstractmethod _async_handle_bluetooth_event is called.
self._available = async_address_present(hass, address, connectable)
@callback
def async_start(self) -> CALLBACK_TYPE:
"""Start the data updater."""
self._async_start()
return self._async_stop
@callback
@abstractmethod
def _async_handle_bluetooth_event(
self,
service_info: BluetoothServiceInfoBleak,
change: BluetoothChange,
) -> None:
"""Handle a bluetooth event."""
@property
def name(self) -> str:
"""Return last known name of the device."""
if service_info := async_last_service_info(
self.hass, self.address, self.connectable
):
return service_info.name
return self._last_name
@property
def last_seen(self) -> float:
"""Return the last time the device was seen."""
# If the device is unavailable it will not have a service
# info and fall through below.
if service_info := async_last_service_info(
self.hass, self.address, self.connectable
):
return service_info.time
# This is the time from the last advertisement that
# was set when the unavailable callback was called.
return self._last_unavailable_time
@callback
def _async_start(self) -> None:
"""Start the callbacks."""
self._on_stop.append(
async_register_callback(
self.hass,
self._async_handle_bluetooth_event,
BluetoothCallbackMatcher(
address=self.address, connectable=self.connectable
),
self.mode,
)
)
self._on_stop.append(
async_track_unavailable(
self.hass,
self._async_handle_unavailable,
self.address,
self.connectable,
)
)
@callback
def _async_stop(self) -> None:
"""Stop the callbacks."""
for unsub in self._on_stop:
unsub()
self._on_stop.clear()
@callback
def _async_handle_unavailable(
self, service_info: BluetoothServiceInfoBleak
) -> None:
"""Handle the device going unavailable."""
self._last_unavailable_time = service_info.time
self._last_name = service_info.name
self._available = False