/
__init__.py
130 lines (101 loc) · 4.05 KB
/
__init__.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
"""Support for ecobee."""
import asyncio
from datetime import timedelta
from pyecobee import ECOBEE_API_KEY, ECOBEE_REFRESH_TOKEN, Ecobee, ExpiredTokenError
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import config_validation as cv
from homeassistant.util import Throttle
from .const import (
_LOGGER,
CONF_REFRESH_TOKEN,
DATA_ECOBEE_CONFIG,
DOMAIN,
ECOBEE_PLATFORMS,
)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Optional(CONF_API_KEY): cv.string})}, extra=vol.ALLOW_EXTRA
)
async def async_setup(hass, config):
"""
Ecobee uses config flow for configuration.
But, an "ecobee:" entry in configuration.yaml will trigger an import flow
if a config entry doesn't already exist. If ecobee.conf exists, the import
flow will attempt to import it and create a config entry, to assist users
migrating from the old ecobee component. Otherwise, the user will have to
continue setting up the integration via the config flow.
"""
hass.data[DATA_ECOBEE_CONFIG] = config.get(DOMAIN, {})
if not hass.config_entries.async_entries(DOMAIN) and hass.data[DATA_ECOBEE_CONFIG]:
# No config entry exists and configuration.yaml config exists, trigger the import flow.
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}
)
)
return True
async def async_setup_entry(hass, entry):
"""Set up ecobee via a config entry."""
api_key = entry.data[CONF_API_KEY]
refresh_token = entry.data[CONF_REFRESH_TOKEN]
data = EcobeeData(hass, entry, api_key=api_key, refresh_token=refresh_token)
if not await data.refresh():
return False
await data.update()
if data.ecobee.thermostats is None:
_LOGGER.error("No ecobee devices found to set up")
return False
hass.data[DOMAIN] = data
for component in ECOBEE_PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
return True
class EcobeeData:
"""
Handle getting the latest data from ecobee.com so platforms can use it.
Also handle refreshing tokens and updating config entry with refreshed tokens.
"""
def __init__(self, hass, entry, api_key, refresh_token):
"""Initialize the Ecobee data object."""
self._hass = hass
self._entry = entry
self.ecobee = Ecobee(
config={ECOBEE_API_KEY: api_key, ECOBEE_REFRESH_TOKEN: refresh_token}
)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def update(self):
"""Get the latest data from ecobee.com."""
try:
await self._hass.async_add_executor_job(self.ecobee.update)
_LOGGER.debug("Updating ecobee")
except ExpiredTokenError:
_LOGGER.warning(
"Ecobee update failed; attempting to refresh expired tokens"
)
await self.refresh()
async def refresh(self) -> bool:
"""Refresh ecobee tokens and update config entry."""
_LOGGER.debug("Refreshing ecobee tokens and updating config entry")
if await self._hass.async_add_executor_job(self.ecobee.refresh_tokens):
self._hass.config_entries.async_update_entry(
self._entry,
data={
CONF_API_KEY: self.ecobee.config[ECOBEE_API_KEY],
CONF_REFRESH_TOKEN: self.ecobee.config[ECOBEE_REFRESH_TOKEN],
},
)
return True
_LOGGER.error("Error updating ecobee tokens")
return False
async def async_unload_entry(hass, config_entry):
"""Unload the config entry and platforms."""
hass.data.pop(DOMAIN)
tasks = []
for platform in ECOBEE_PLATFORMS:
tasks.append(
hass.config_entries.async_forward_entry_unload(config_entry, platform)
)
return all(await asyncio.gather(*tasks))