1 """Config flow for Vizio."""
3 from __future__
import annotations
10 from pyvizio
import VizioAsync, async_guess_device_type
11 from pyvizio.const
import APP_HOME
12 import voluptuous
as vol
41 CONF_APPS_TO_INCLUDE_OR_EXCLUDE,
42 CONF_INCLUDE_OR_EXCLUDE,
51 _LOGGER = logging.getLogger(__name__)
55 """Return schema defaults for init step based on user input/config dict.
57 Retain info already provided for future form views by setting them
58 as defaults in schema.
60 if input_dict
is None:
66 CONF_NAME, default=input_dict.get(CONF_NAME, DEFAULT_NAME)
68 vol.Required(CONF_HOST, default=input_dict.get(CONF_HOST)): str,
71 default=input_dict.get(CONF_DEVICE_CLASS, DEFAULT_DEVICE_CLASS),
75 vol.In([MediaPlayerDeviceClass.TV, MediaPlayerDeviceClass.SPEAKER]),
78 CONF_ACCESS_TOKEN, default=input_dict.get(CONF_ACCESS_TOKEN,
"")
81 extra=vol.REMOVE_EXTRA,
86 """Return schema defaults for pairing data based on user input.
88 Retain info already provided for future form views by setting
89 them as defaults in schema.
91 if input_dict
is None:
95 {vol.Required(CONF_PIN, default=input_dict.get(CONF_PIN,
"")): str}
100 """Check if host1 and host2 are the same."""
101 host1 = host1.split(
":")[0]
102 host1 = host1
if is_ip_address(host1)
else socket.gethostbyname(host1)
103 host2 = host2.split(
":")[0]
104 host2 = host2
if is_ip_address(host2)
else socket.gethostbyname(host2)
105 return host1 == host2
109 """Handle Vizio options."""
112 self, user_input: dict[str, Any] |
None =
None
113 ) -> ConfigFlowResult:
114 """Manage the vizio options."""
115 if user_input
is not None:
116 if user_input.get(CONF_APPS_TO_INCLUDE_OR_EXCLUDE):
117 user_input[CONF_APPS] = {
118 user_input[CONF_INCLUDE_OR_EXCLUDE]: user_input[
119 CONF_APPS_TO_INCLUDE_OR_EXCLUDE
123 user_input.pop(CONF_INCLUDE_OR_EXCLUDE)
124 user_input.pop(CONF_APPS_TO_INCLUDE_OR_EXCLUDE)
128 options = vol.Schema(
133 CONF_VOLUME_STEP, DEFAULT_VOLUME_STEP
135 ): vol.All(vol.Coerce(int), vol.Range(min=1, max=10))
140 default_include_or_exclude = (
146 options = options.extend(
149 CONF_INCLUDE_OR_EXCLUDE,
150 default=default_include_or_exclude.title(),
152 vol.In([CONF_INCLUDE.title(), CONF_EXCLUDE.title()]), vol.Lower
155 CONF_APPS_TO_INCLUDE_OR_EXCLUDE,
157 default_include_or_exclude, []
164 for app
in self.hass.data[DOMAIN][CONF_APPS].data
171 return self.
async_show_formasync_show_form(step_id=
"init", data_schema=options)
175 """Handle a Vizio config flow."""
182 """Get the options flow for this handler."""
186 """Initialize config flow."""
189 self.
_ch_type_ch_type: str |
None =
None
191 self.
_data_data: dict[str, Any] |
None =
None
192 self.
_apps_apps: dict[str, list] = {}
194 async
def _create_entry(self, input_dict: dict[str, Any]) -> ConfigFlowResult:
195 """Create vizio config entry."""
197 input_dict.pop(CONF_APPS_TO_INCLUDE_OR_EXCLUDE,
None)
198 input_dict.pop(CONF_INCLUDE_OR_EXCLUDE,
None)
201 input_dict[CONF_APPS] = self.
_apps_apps
206 self, user_input: dict[str, Any] |
None =
None
207 ) -> ConfigFlowResult:
208 """Handle a flow initialized by the user."""
209 errors: dict[str, str] = {}
211 if user_input
is not None:
215 unique_id = await VizioAsync.get_unique_id(
216 user_input[CONF_HOST],
217 user_input[CONF_DEVICE_CLASS],
224 errors[CONF_HOST] =
"cannot_connect"
227 unique_id=unique_id, raise_on_progress=
True
231 errors[CONF_HOST] =
"existing_config_entry_found"
234 if self.
_must_show_form_must_show_form
and self.context[
"source"] == SOURCE_ZEROCONF:
240 ] == MediaPlayerDeviceClass.SPEAKER
or user_input.get(
244 if not await VizioAsync.validate_ha_config(
245 user_input[CONF_HOST],
246 user_input.get(CONF_ACCESS_TOKEN),
247 user_input[CONF_DEVICE_CLASS],
250 errors[
"base"] =
"cannot_connect"
254 elif self.
_must_show_form_must_show_form
and self.context[
"source"] == SOURCE_IMPORT:
261 "Couldn't complete configuration.yaml import: '%s' key is "
262 "missing. Either provide '%s' key in configuration.yaml or "
263 "finish setup by completing configuration via frontend"
270 self.
_data_data = copy.deepcopy(user_input)
275 if errors
and self.context[
"source"] == SOURCE_IMPORT:
278 "Importing from configuration.yaml failed: %s",
279 ", ".join(errors.values()),
285 """Import a config entry from configuration.yaml."""
289 if entry.source == SOURCE_IGNORE:
292 if await self.hass.async_add_executor_job(
293 _host_is_same, entry.data[CONF_HOST], import_data[CONF_HOST]
295 updated_options: dict[str, Any] = {}
296 updated_data: dict[str, Any] = {}
299 if entry.data[CONF_HOST] != import_data[CONF_HOST]:
300 updated_data[CONF_HOST] = import_data[CONF_HOST]
302 if entry.data[CONF_NAME] != import_data[CONF_NAME]:
303 updated_data[CONF_NAME] = import_data[CONF_NAME]
307 if entry.data.get(CONF_APPS) != import_data.get(CONF_APPS):
308 if not import_data.get(CONF_APPS):
311 updated_options[CONF_APPS] = import_data[CONF_APPS]
313 if entry.data.get(CONF_VOLUME_STEP) != import_data[CONF_VOLUME_STEP]:
314 updated_options[CONF_VOLUME_STEP] = import_data[CONF_VOLUME_STEP]
316 if updated_options
or updated_data
or remove_apps:
317 new_data = entry.data.copy()
318 new_options = entry.options.copy()
321 new_data.pop(CONF_APPS)
322 new_options.pop(CONF_APPS)
325 new_data.update(updated_data)
329 new_data.update(updated_options)
330 new_options.update(updated_options)
332 self.hass.config_entries.async_update_entry(
333 entry=entry, data=new_data, options=new_options
342 if import_data.get(CONF_APPS):
343 self.
_apps_apps = copy.deepcopy(import_data[CONF_APPS])
347 self, discovery_info: zeroconf.ZeroconfServiceInfo
348 ) -> ConfigFlowResult:
349 """Handle zeroconf discovery."""
350 host = discovery_info.host
353 host = f
"{host}:{discovery_info.port}"
357 num_chars_to_strip = len(discovery_info.type) + 1
358 name = discovery_info.name[:-num_chars_to_strip]
360 device_class = await async_guess_device_type(host)
363 unique_id = await VizioAsync.get_unique_id(
372 await self.
async_set_unique_idasync_set_unique_id(unique_id=unique_id, raise_on_progress=
True)
382 CONF_DEVICE_CLASS: device_class,
387 self, user_input: dict[str, Any] |
None =
None
388 ) -> ConfigFlowResult:
389 """Start pairing process for TV.
391 Ask user for PIN to complete pairing process.
393 errors: dict[str, str] = {}
394 assert self.
_data_data
400 self.
_data_data[CONF_HOST],
401 self.
_data_data[CONF_NAME],
403 self.
_data_data[CONF_DEVICE_CLASS],
406 pair_data = await dev.start_pair()
416 errors={
"base":
"cannot_connect"},
420 if user_input
and user_input.get(CONF_PIN):
423 self.
_data_data[CONF_HOST],
424 self.
_data_data[CONF_NAME],
426 self.
_data_data[CONF_DEVICE_CLASS],
429 pair_data = await dev.pair(
434 self.
_data_data[CONF_ACCESS_TOKEN] = pair_data.auth_token
437 if self.context[
"source"] == SOURCE_IMPORT:
445 errors[CONF_PIN] =
"complete_pairing_failed"
454 """Handle config flow completion."""
455 assert self.
_data_data
462 description_placeholders={
"access_token": self.
_data_data[CONF_ACCESS_TOKEN]},
466 self, user_input: dict[str, Any] |
None =
None
467 ) -> ConfigFlowResult:
468 """Complete non-import sourced config flow.
470 Display final message to user confirming pairing.
475 self, user_input: dict[str, Any] |
None =
None
476 ) -> ConfigFlowResult:
477 """Complete import sourced config flow.
479 Display final message to user confirming pairing and displaying
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
ConfigFlowResult async_step_pairing_complete_import(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _create_entry(self, dict[str, Any] input_dict)
ConfigFlowResult async_step_pair_tv(self, dict[str, Any]|None user_input=None)
VizioOptionsConfigFlow async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_pairing_complete(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _pairing_complete(self, str step_id)
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|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)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=None)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=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)
web.Response get(self, web.Request request, str config_key)
vol.Schema _get_pairing_schema(dict[str, Any]|None input_dict=None)
bool _host_is_same(str host1, str host2)
vol.Schema _get_config_schema(dict[str, Any]|None input_dict=None)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
bool is_ip_address(str address)