-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdevice_tracker.py
151 lines (113 loc) · 4.85 KB
/
device_tracker.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""Support for Tado Smart device trackers."""
from __future__ import annotations
import asyncio
from collections import namedtuple
from datetime import timedelta
from http import HTTPStatus
import logging
import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.components.device_tracker import (
DOMAIN,
PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA,
DeviceScanner,
)
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_create_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
CONF_HOME_ID = "home_id"
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30)
PLATFORM_SCHEMA = BASE_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HOME_ID): cv.string,
}
)
def get_scanner(hass: HomeAssistant, config: ConfigType) -> DeviceScanner | None:
"""Return a Tado scanner."""
scanner = TadoDeviceScanner(hass, config[DOMAIN])
return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "name"])
class TadoDeviceScanner(DeviceScanner):
"""This class gets geofenced devices from Tado."""
def __init__(self, hass, config):
"""Initialize the scanner."""
self.hass = hass
self.last_results = []
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
# The Tado device tracker can work with or without a home_id
self.home_id = config[CONF_HOME_ID] if CONF_HOME_ID in config else None
# If there's a home_id, we need a different API URL
if self.home_id is None:
self.tadoapiurl = "https://my.tado.com/api/v2/me"
else:
self.tadoapiurl = "https://my.tado.com/api/v2/homes/{home_id}/mobileDevices"
# The API URL always needs a username and password
self.tadoapiurl += "?username={username}&password={password}"
self.websession = None
self.success_init = asyncio.run_coroutine_threadsafe(
self._async_update_info(), hass.loop
).result()
_LOGGER.info("Scanner initialized")
async def async_scan_devices(self):
"""Scan for devices and return a list containing found device ids."""
await self._async_update_info()
return [device.mac for device in self.last_results]
async def async_get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
filter_named = [
result.name for result in self.last_results if result.mac == device
]
if filter_named:
return filter_named[0]
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
async def _async_update_info(self):
"""
Query Tado for device marked as at home.
Returns boolean if scanning successful.
"""
_LOGGER.debug("Requesting Tado")
if self.websession is None:
self.websession = async_create_clientsession(
self.hass, cookie_jar=aiohttp.CookieJar(unsafe=True)
)
last_results = []
try:
async with async_timeout.timeout(10):
# Format the URL here, so we can log the template URL if
# anything goes wrong without exposing username and password.
url = self.tadoapiurl.format(
home_id=self.home_id, username=self.username, password=self.password
)
response = await self.websession.get(url)
if response.status != HTTPStatus.OK:
_LOGGER.warning("Error %d on %s", response.status, self.tadoapiurl)
return False
tado_json = await response.json()
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Cannot load Tado data")
return False
# Without a home_id, we fetched an URL where the mobile devices can be
# found under the mobileDevices key.
if "mobileDevices" in tado_json:
tado_json = tado_json["mobileDevices"]
# Find devices that have geofencing enabled, and are currently at home.
for mobile_device in tado_json:
if mobile_device.get("location") and mobile_device["location"]["atHome"]:
device_id = mobile_device["id"]
device_name = mobile_device["name"]
last_results.append(Device(device_id, device_name))
self.last_results = last_results
_LOGGER.debug(
"Tado presence query successful, %d device(s) at home",
len(self.last_results),
)
return True