1 """Config flow for Android TV Remote integration."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
9 from androidtvremote2
import (
15 import voluptuous
as vol
35 from .const
import CONF_APP_ICON, CONF_APP_NAME, CONF_APPS, CONF_ENABLE_IME, DOMAIN
36 from .helpers
import create_api, get_enable_ime
38 _LOGGER = logging.getLogger(__name__)
40 APPS_NEW_ID =
"NewApp"
41 CONF_APP_DELETE =
"app_delete"
42 CONF_APP_ID =
"app_id"
44 STEP_USER_DATA_SCHEMA = vol.Schema(
46 vol.Required(
"host"): str,
50 STEP_PAIR_DATA_SCHEMA = vol.Schema(
52 vol.Required(
"pin"): str,
58 """Handle a config flow for Android TV Remote."""
68 self, user_input: dict[str, Any] |
None =
None
69 ) -> ConfigFlowResult:
70 """Handle the initial step."""
71 errors: dict[str, str] = {}
72 if user_input
is not None:
73 self.
hosthost = user_input[CONF_HOST]
76 await api.async_generate_cert_if_missing()
77 self.
namename, self.
macmac = await api.async_get_name_and_mac()
81 except (CannotConnect, ConnectionClosed):
84 errors[
"base"] =
"cannot_connect"
87 data_schema=STEP_USER_DATA_SCHEMA,
92 """Start pairing with the Android TV. Navigate to the pair flow to enter the PIN shown on screen."""
94 await self.
apiapi.async_generate_cert_if_missing()
95 await self.
apiapi.async_start_pairing()
99 self, user_input: dict[str, Any] |
None =
None
100 ) -> ConfigFlowResult:
101 """Handle the pair step."""
102 errors: dict[str, str] = {}
103 if user_input
is not None:
105 pin = user_input[
"pin"]
106 await self.
apiapi.async_finish_pairing(pin)
108 await self.hass.config_entries.async_reload(
115 CONF_HOST: self.
hosthost,
116 CONF_NAME: self.
namename,
117 CONF_MAC: self.
macmac,
123 errors[
"base"] =
"invalid_auth"
124 except ConnectionClosed:
131 except (CannotConnect, ConnectionClosed):
139 data_schema=STEP_PAIR_DATA_SCHEMA,
140 description_placeholders={CONF_NAME: self.
namename},
145 self, discovery_info: zeroconf.ZeroconfServiceInfo
146 ) -> ConfigFlowResult:
147 """Handle zeroconf discovery."""
148 _LOGGER.debug(
"Android TV device found via zeroconf: %s", discovery_info)
149 self.
hosthost = discovery_info.host
150 self.
namename = discovery_info.name.removesuffix(
"._androidtvremote2._tcp.local.")
151 if not (mac := discovery_info.properties.get(
"bt")):
159 if existing_config_entry
and len(discovery_info.ip_addresses) > 1:
160 existing_host = existing_config_entry.data[CONF_HOST]
161 if existing_host != self.
hosthost:
162 if existing_host
in [
163 str(ip_address)
for ip_address
in discovery_info.ip_addresses
165 self.
hosthost = existing_host
167 updates={CONF_HOST: self.
hosthost, CONF_NAME: self.
namename}
169 _LOGGER.debug(
"New Android TV device found via zeroconf: %s", self.
namename)
170 self.context.
update({
"title_placeholders": {CONF_NAME: self.
namename}})
174 self, user_input: dict[str, Any] |
None =
None
175 ) -> ConfigFlowResult:
176 """Handle a flow initiated by zeroconf."""
177 if user_input
is not None:
180 except (CannotConnect, ConnectionClosed):
185 step_id=
"zeroconf_confirm",
186 description_placeholders={CONF_NAME: self.
namename},
190 self, entry_data: Mapping[str, Any]
191 ) -> ConfigFlowResult:
192 """Handle configuration by re-auth."""
193 self.
hosthost = entry_data[CONF_HOST]
194 self.
namename = entry_data[CONF_NAME]
195 self.
macmac = entry_data[CONF_MAC]
199 self, user_input: dict[str, Any] |
None =
None
200 ) -> ConfigFlowResult:
201 """Dialog that informs the user that reauth is required."""
202 errors: dict[str, str] = {}
203 if user_input
is not None:
206 except (CannotConnect, ConnectionClosed):
208 errors[
"base"] =
"cannot_connect"
210 step_id=
"reauth_confirm",
211 description_placeholders={CONF_NAME: self.
namename},
218 config_entry: ConfigEntry,
219 ) -> AndroidTVRemoteOptionsFlowHandler:
220 """Create the options flow."""
225 """Android TV Remote options flow."""
227 def __init__(self, config_entry: ConfigEntry) ->
None:
228 """Initialize options flow."""
229 self._apps: dict[str, Any] =
dict(config_entry.options.get(CONF_APPS, {}))
234 """Save the updated options."""
235 new_data = {k: v
for k, v
in data.items()
if k
not in [CONF_APPS]}
237 new_data[CONF_APPS] = self._apps
242 self, user_input: dict[str, Any] |
None =
None
243 ) -> ConfigFlowResult:
244 """Manage the options."""
245 if user_input
is not None:
246 if sel_app := user_input.get(CONF_APPS):
251 k: f
"{v[CONF_APP_NAME]} ({k})" if CONF_APP_NAME
in v
else k
252 for k, v
in self._apps.items()
259 data_schema=vol.Schema(
263 options=apps, mode=SelectSelectorMode.DROPDOWN
275 self, user_input: dict[str, Any] |
None =
None, app_id: str |
None =
None
276 ) -> ConfigFlowResult:
277 """Handle options flow for apps list."""
278 if app_id
is not None:
279 self.
_conf_app_id_conf_app_id = app_id
if app_id != APPS_NEW_ID
else None
282 if user_input
is not None:
283 app_id = user_input.get(CONF_APP_ID, self.
_conf_app_id_conf_app_id)
285 if user_input.get(CONF_APP_DELETE,
False):
286 self._apps.pop(app_id)
288 self._apps[app_id] = {
289 CONF_APP_NAME: user_input.get(CONF_APP_NAME,
""),
290 CONF_APP_ICON: user_input.get(CONF_APP_ICON,
""),
297 """Return configuration form for apps."""
303 "suggested_value": self._apps[app_id].
get(CONF_APP_NAME,
"")
304 if app_id
in self._apps
311 "suggested_value": self._apps[app_id].
get(CONF_APP_ICON,
"")
312 if app_id
in self._apps
317 if app_id == APPS_NEW_ID:
318 data_schema = vol.Schema({**app_schema, vol.Optional(CONF_APP_ID): str})
320 data_schema = vol.Schema(
321 {**app_schema, vol.Optional(CONF_APP_DELETE, default=
False): bool}
326 data_schema=data_schema,
327 description_placeholders={
328 "app_id": f
"`{app_id}`" if app_id != APPS_NEW_ID
else "",
ConfigFlowResult async_step_pair(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
AndroidTVRemoteOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_zeroconf_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _async_start_pair(self)
ConfigFlowResult _async_apps_form(self, str app_id)
ConfigFlowResult _save_config(self, dict[str, Any] data)
ConfigFlowResult async_step_apps(self, dict[str, Any]|None user_input=None, str|None app_id=None)
None __init__(self, ConfigEntry config_entry)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry _get_reauth_entry(self)
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
AndroidTVRemote create_api(HomeAssistant hass, str host, bool enable_ime)
bool get_enable_ime(ConfigEntry entry)
web.Response get(self, web.Request request, str config_key)
IssData update(pyiss.ISS iss)