forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig_flow.py
187 lines (152 loc) · 6.12 KB
/
config_flow.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
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
177
178
179
180
181
182
183
184
185
186
187
"""Adds config flow for Bravia TV integration."""
from __future__ import annotations
from contextlib import suppress
import ipaddress
import re
from typing import Any
from bravia_tv import BraviaRC
from bravia_tv.braviarc import NoIPControl
import voluptuous as vol
from homeassistant import config_entries, exceptions
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PIN
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv
from .const import (
ATTR_CID,
ATTR_MAC,
ATTR_MODEL,
CLIENTID_PREFIX,
CONF_IGNORED_SOURCES,
DOMAIN,
NICKNAME,
)
def host_valid(host: str) -> bool:
"""Return True if hostname or IP address is valid."""
with suppress(ValueError):
if ipaddress.ip_address(host).version in [4, 6]:
return True
disallowed = re.compile(r"[^a-zA-Z\d\-]")
return all(x and not disallowed.search(x) for x in host.split("."))
class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for BraviaTV integration."""
VERSION = 1
def __init__(self) -> None:
"""Initialize."""
self.braviarc: BraviaRC | None = None
self.host: str | None = None
self.title = ""
self.mac: str | None = None
async def init_device(self, pin: str) -> None:
"""Initialize Bravia TV device."""
assert self.braviarc is not None
await self.hass.async_add_executor_job(
self.braviarc.connect, pin, CLIENTID_PREFIX, NICKNAME
)
connected = await self.hass.async_add_executor_job(self.braviarc.is_connected)
if not connected:
raise CannotConnect()
system_info = await self.hass.async_add_executor_job(
self.braviarc.get_system_info
)
if not system_info:
raise ModelNotSupported()
await self.async_set_unique_id(system_info[ATTR_CID].lower())
self._abort_if_unique_id_configured()
self.title = system_info[ATTR_MODEL]
self.mac = system_info[ATTR_MAC]
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> BraviaTVOptionsFlowHandler:
"""Bravia TV options callback."""
return BraviaTVOptionsFlowHandler(config_entry)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
if host_valid(user_input[CONF_HOST]):
self.host = user_input[CONF_HOST]
self.braviarc = BraviaRC(self.host)
return await self.async_step_authorize()
errors[CONF_HOST] = "invalid_host"
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_HOST, default=""): str}),
errors=errors,
)
async def async_step_authorize(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Get PIN from the Bravia TV device."""
errors: dict[str, str] = {}
if user_input is not None:
try:
await self.init_device(user_input[CONF_PIN])
except CannotConnect:
errors["base"] = "cannot_connect"
except ModelNotSupported:
errors["base"] = "unsupported_model"
else:
user_input[CONF_HOST] = self.host
user_input[CONF_MAC] = self.mac
return self.async_create_entry(title=self.title, data=user_input)
# Connecting with th PIN "0000" to start the pairing process on the TV.
try:
assert self.braviarc is not None
await self.hass.async_add_executor_job(
self.braviarc.connect, "0000", CLIENTID_PREFIX, NICKNAME
)
except NoIPControl:
return self.async_abort(reason="no_ip_control")
return self.async_show_form(
step_id="authorize",
data_schema=vol.Schema({vol.Required(CONF_PIN, default=""): str}),
errors=errors,
)
class BraviaTVOptionsFlowHandler(config_entries.OptionsFlow):
"""Config flow options for Bravia TV."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize Bravia TV options flow."""
self.config_entry = config_entry
self.pin = config_entry.data[CONF_PIN]
self.ignored_sources = config_entry.options.get(CONF_IGNORED_SOURCES)
self.source_list: dict[str, str] = {}
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
coordinator = self.hass.data[DOMAIN][self.config_entry.entry_id]
braviarc = coordinator.braviarc
connected = await self.hass.async_add_executor_job(braviarc.is_connected)
if not connected:
await self.hass.async_add_executor_job(
braviarc.connect, self.pin, CLIENTID_PREFIX, NICKNAME
)
content_mapping = await self.hass.async_add_executor_job(
braviarc.load_source_list
)
self.source_list = {item: item for item in content_mapping}
return await self.async_step_user()
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Optional(
CONF_IGNORED_SOURCES, default=self.ignored_sources
): cv.multi_select(self.source_list)
}
),
)
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
class ModelNotSupported(exceptions.HomeAssistantError):
"""Error to indicate not supported model."""