-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig_flow.py
More file actions
176 lines (145 loc) · 5.83 KB
/
Copy pathconfig_flow.py
File metadata and controls
176 lines (145 loc) · 5.83 KB
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""Config flow for SwitchBot API integration."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
from homeassistant import config_entries, exceptions
from homeassistant.core import callback
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .api import SwitchBotApiError, async_request
from .const import CONF_SECRET, CONF_TOKEN, DOMAIN
from .services import fetch_devices
_LOGGER = logging.getLogger(__name__)
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_TOKEN): str,
vol.Required(CONF_SECRET): str,
}
)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate credentials by calling the SwitchBot API."""
token = data[CONF_TOKEN].strip()
secret = data[CONF_SECRET].strip()
if not token or not secret:
raise InvalidAuth
try:
await async_request(hass, "GET", "/devices", token, secret)
except SwitchBotApiError as exc:
if exc.auth_failed:
raise InvalidAuth from exc
raise CannotConnect from exc
return {"title": "SwitchBot API"}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SwitchBot API."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
async def async_step_user(
self, user_input: dict[str, Any] | None = None
):
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
await validate_input(self.hass, user_input)
await self.async_set_unique_id(DOMAIN)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title="SwitchBot API",
data={
CONF_TOKEN: user_input[CONF_TOKEN].strip(),
CONF_SECRET: user_input[CONF_SECRET].strip(),
},
)
except InvalidAuth:
errors["base"] = "invalid_auth"
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
return self.async_show_form(
step_id="user",
data_schema=DATA_SCHEMA,
errors=errors,
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
):
"""Handle credential update when the user reconfigures the integration."""
errors: dict[str, str] = {}
if user_input is not None:
try:
await validate_input(self.hass, user_input)
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data={
CONF_TOKEN: user_input[CONF_TOKEN].strip(),
CONF_SECRET: user_input[CONF_SECRET].strip(),
},
)
except InvalidAuth:
errors["base"] = "invalid_auth"
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
return self.async_show_form(
step_id="reconfigure",
data_schema=DATA_SCHEMA,
errors=errors,
)
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> SwitchBotAuthOptionsFlowHandler:
"""Get the options flow for this handler."""
return SwitchBotAuthOptionsFlowHandler()
class SwitchBotAuthOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle SwitchBot API options - displays device summary when user opens integration."""
async def async_step_init(self, user_input: dict[str, Any] | None = None):
"""Display device summary when user opens integration config."""
if user_input is not None:
return self.async_create_entry(data={})
try:
result = await fetch_devices(self.hass, self.config_entry)
except SwitchBotApiError as exc:
result = {
"device_count": 0,
"physical_device_count": 0,
"infrared_remote_count": 0,
"devices": [],
}
_LOGGER.warning("Could not fetch devices in options flow: %s", exc)
physical = []
infrared = []
for device in result.get("devices", []):
line = f"• **{device['device_name']}** ({device['device_type']}) `{device['device_id']}`"
if device.get("is_infrared"):
infrared.append(line)
else:
physical.append(line)
sections = []
if physical:
sections.append("**Physical devices:**\n" + "\n".join(physical))
if infrared:
sections.append("**Infrared remotes:**\n" + "\n".join(infrared))
if not sections:
sections.append("No devices found in this SwitchBot account.")
devices_text = "\n\n".join(sections)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({}),
description_placeholders={
"device_count": str(result["device_count"]),
"physical_device_count": str(result["physical_device_count"]),
"infrared_remote_count": str(result["infrared_remote_count"]),
"devices": devices_text,
},
)
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect to the API."""
class InvalidAuth(exceptions.HomeAssistantError):
"""Error to indicate invalid authentication."""