-
-
Notifications
You must be signed in to change notification settings - Fork 38.2k
Add allowed UUIDs and ignore CEC to Google Cast options flow #47269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
17151f8
d80b8ea
fe07c9f
cf4d297
be3c4ad
4487596
fa14da8
cc305b9
090194d
7ea9378
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,12 @@ | |
| from homeassistant import config_entries | ||
| from homeassistant.helpers import config_validation as cv | ||
|
|
||
| from .const import CONF_KNOWN_HOSTS, DOMAIN | ||
| from .const import CONF_IGNORE_CEC, CONF_KNOWN_HOSTS, CONF_UUID, DOMAIN | ||
| from .media_player import ENTITY_SCHEMA | ||
|
|
||
| IGNORE_CEC_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) | ||
| KNOWN_HOSTS_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) | ||
| WANTED_UUID_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) | ||
|
|
||
|
|
||
| class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): | ||
|
|
@@ -17,7 +20,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): | |
|
|
||
| def __init__(self): | ||
| """Initialize flow.""" | ||
| self._known_hosts = None | ||
| self._ignore_cec = [] | ||
| self._known_hosts = [] | ||
| self._wanted_uuid = [] | ||
|
|
||
| @staticmethod | ||
| def async_get_options_flow(config_entry): | ||
|
|
@@ -28,7 +33,22 @@ async def async_step_import(self, import_data=None): | |
| """Import data.""" | ||
| if self._async_current_entries(): | ||
| return self.async_abort(reason="single_instance_allowed") | ||
| data = {CONF_KNOWN_HOSTS: self._known_hosts} | ||
|
|
||
| media_player_config = self.hass.data[DOMAIN].get("media_player") or {} | ||
|
MartinHjelmare marked this conversation as resolved.
Outdated
|
||
| if not isinstance(media_player_config, list): | ||
| media_player_config = [media_player_config] | ||
| for cfg in media_player_config: | ||
| try: | ||
| cfg = ENTITY_SCHEMA(cfg) | ||
| if CONF_IGNORE_CEC in cfg: | ||
| self._ignore_cec.extend(cfg[CONF_IGNORE_CEC]) | ||
| if CONF_UUID in cfg: | ||
| self._wanted_uuid.append(cfg[CONF_UUID]) | ||
| except vol.Error: | ||
| # Invalid config, ignore it | ||
| pass | ||
|
MartinHjelmare marked this conversation as resolved.
Outdated
|
||
|
|
||
| data = self._get_data() | ||
| return self.async_create_entry(title="Google Cast", data=data) | ||
|
|
||
| async def async_step_user(self, user_input=None): | ||
|
|
@@ -62,7 +82,8 @@ async def async_step_config(self, user_input=None): | |
| errors["base"] = "invalid_known_hosts" | ||
| bad_hosts = True | ||
| else: | ||
| data[CONF_KNOWN_HOSTS] = known_hosts | ||
| self._known_hosts = known_hosts | ||
| data = self._get_data() | ||
| if not bad_hosts: | ||
| return self.async_create_entry(title="Google Cast", data=data) | ||
|
|
||
|
|
@@ -76,13 +97,20 @@ async def async_step_config(self, user_input=None): | |
| async def async_step_confirm(self, user_input=None): | ||
| """Confirm the setup.""" | ||
|
|
||
| data = {CONF_KNOWN_HOSTS: self._known_hosts} | ||
| data = self._get_data() | ||
|
|
||
| if user_input is not None: | ||
| return self.async_create_entry(title="Google Cast", data=data) | ||
|
|
||
| return self.async_show_form(step_id="confirm") | ||
|
|
||
| def _get_data(self): | ||
| return { | ||
| CONF_IGNORE_CEC: self._ignore_cec, | ||
| CONF_KNOWN_HOSTS: self._known_hosts, | ||
| CONF_UUID: self._wanted_uuid, | ||
| } | ||
|
|
||
|
|
||
| class CastOptionsFlowHandler(config_entries.OptionsFlow): | ||
| """Handle Google Cast options.""" | ||
|
|
@@ -102,35 +130,58 @@ async def async_step_options(self, user_input=None): | |
| errors = {} | ||
| current_config = self.config_entry.data | ||
| if user_input is not None: | ||
| bad_hosts = False | ||
| bad_cec, ignore_cec = _string_to_list( | ||
| user_input.get(CONF_IGNORE_CEC, ""), IGNORE_CEC_SCHEMA | ||
| ) | ||
| bad_hosts, known_hosts = _string_to_list( | ||
| user_input.get(CONF_KNOWN_HOSTS, ""), KNOWN_HOSTS_SCHEMA | ||
| ) | ||
| bad_uuid, wanted_uuid = _string_to_list( | ||
| user_input.get(CONF_UUID, ""), WANTED_UUID_SCHEMA | ||
| ) | ||
|
|
||
| known_hosts = user_input.get(CONF_KNOWN_HOSTS, "") | ||
| known_hosts = [x.strip() for x in known_hosts.split(",") if x.strip()] | ||
| try: | ||
| known_hosts = KNOWN_HOSTS_SCHEMA(known_hosts) | ||
| except vol.Invalid: | ||
| errors["base"] = "invalid_known_hosts" | ||
| bad_hosts = True | ||
| if not bad_hosts: | ||
| if not bad_cec and not bad_hosts and not bad_uuid: | ||
| updated_config = {} | ||
| updated_config[CONF_IGNORE_CEC] = ignore_cec | ||
| updated_config[CONF_KNOWN_HOSTS] = known_hosts | ||
| updated_config[CONF_UUID] = wanted_uuid | ||
| self.hass.config_entries.async_update_entry( | ||
| self.config_entry, data=updated_config | ||
| ) | ||
| return self.async_create_entry(title="", data=None) | ||
|
|
||
| fields = {} | ||
| known_hosts_string = "" | ||
| if current_config.get(CONF_KNOWN_HOSTS): | ||
| known_hosts_string = ",".join(current_config.get(CONF_KNOWN_HOSTS)) | ||
| fields[ | ||
| vol.Optional( | ||
| "known_hosts", description={"suggested_value": known_hosts_string} | ||
| ) | ||
| ] = str | ||
| suggested_value = _list_to_string(current_config.get(CONF_KNOWN_HOSTS)) | ||
| _add_with_suggestion(fields, CONF_KNOWN_HOSTS, suggested_value) | ||
| suggested_value = _list_to_string(current_config.get(CONF_UUID)) | ||
| _add_with_suggestion(fields, CONF_UUID, suggested_value) | ||
| suggested_value = _list_to_string(current_config.get(CONF_IGNORE_CEC)) | ||
| _add_with_suggestion(fields, CONF_IGNORE_CEC, suggested_value) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We know if this flow is requested by an advanced user. Let's hide UUID and IGNORE CEC unless users are advanced.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 4487596 |
||
|
|
||
| return self.async_show_form( | ||
| step_id="options", | ||
| data_schema=vol.Schema(fields), | ||
| errors=errors, | ||
| ) | ||
|
|
||
|
|
||
| def _list_to_string(items): | ||
| comma_separated_string = "" | ||
| if items: | ||
| comma_separated_string = ",".join(items) | ||
| return comma_separated_string | ||
|
|
||
|
|
||
| def _string_to_list(string, schema): | ||
| invalid = False | ||
| items = [x.strip() for x in string.split(",") if x.strip()] | ||
| try: | ||
| items = schema(items) | ||
| except vol.Invalid: | ||
| invalid = True | ||
|
|
||
| return invalid, items | ||
|
|
||
|
|
||
| def _add_with_suggestion(fields, key, suggested_value): | ||
| fields[vol.Optional(key, description={"suggested_value": suggested_value})] = str | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| """Provide functionality to interact with Cast devices on the network.""" | ||
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| from datetime import timedelta | ||
| import functools as ft | ||
| import json | ||
|
|
@@ -51,19 +50,19 @@ | |
| STATE_PLAYING, | ||
| ) | ||
| from homeassistant.core import callback | ||
| from homeassistant.exceptions import PlatformNotReady | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.helpers.dispatcher import async_dispatcher_connect | ||
| from homeassistant.helpers.network import NoURLAvailableError, get_url | ||
| from homeassistant.helpers.typing import ConfigType, HomeAssistantType | ||
| from homeassistant.helpers.typing import HomeAssistantType | ||
| import homeassistant.util.dt as dt_util | ||
| from homeassistant.util.logging import async_create_catching_coro | ||
|
|
||
| from .const import ( | ||
| ADDED_CAST_DEVICES_KEY, | ||
| CAST_MULTIZONE_MANAGER_KEY, | ||
| CONF_IGNORE_CEC, | ||
| CONF_UUID, | ||
| DOMAIN as CAST_DOMAIN, | ||
| KNOWN_CHROMECAST_INFO_KEY, | ||
| SIGNAL_CAST_DISCOVERED, | ||
| SIGNAL_CAST_REMOVED, | ||
| SIGNAL_HASS_CAST_SHOW_VIEW, | ||
|
|
@@ -73,8 +72,6 @@ | |
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| CONF_IGNORE_CEC = "ignore_cec" | ||
| CONF_UUID = "uuid" | ||
| CAST_SPLASH = "https://www.home-assistant.io/images/cast/splash.png" | ||
|
|
||
| SUPPORT_CAST = ( | ||
|
|
@@ -128,57 +125,27 @@ def _async_create_cast_device(hass: HomeAssistantType, info: ChromecastInfo): | |
|
|
||
| async def async_setup_entry(hass, config_entry, async_add_entities): | ||
| """Set up Cast from a config entry.""" | ||
| config = hass.data[CAST_DOMAIN].get("media_player") or {} | ||
| if not isinstance(config, list): | ||
| config = [config] | ||
|
|
||
| # no pending task | ||
| done, _ = await asyncio.wait( | ||
| [ | ||
| _async_setup_platform( | ||
| hass, ENTITY_SCHEMA(cfg), async_add_entities, config_entry | ||
| ) | ||
| for cfg in config | ||
| ] | ||
| ) | ||
| if any(task.exception() for task in done): | ||
| exceptions = [task.exception() for task in done] | ||
| for exception in exceptions: | ||
| _LOGGER.debug("Failed to setup chromecast", exc_info=exception) | ||
| raise PlatformNotReady | ||
|
|
||
|
|
||
| async def _async_setup_platform( | ||
| hass: HomeAssistantType, config: ConfigType, async_add_entities, config_entry | ||
| ): | ||
| """Set up the cast platform.""" | ||
| # Import CEC IGNORE attributes | ||
| pychromecast.IGNORE_CEC += config.get(CONF_IGNORE_CEC, []) | ||
| hass.data.setdefault(ADDED_CAST_DEVICES_KEY, set()) | ||
| hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, {}) | ||
|
|
||
| wanted_uuid = None | ||
| if CONF_UUID in config: | ||
| wanted_uuid = config[CONF_UUID] | ||
| # Import CEC IGNORE attributes | ||
| pychromecast.IGNORE_CEC += config_entry.data.get(CONF_IGNORE_CEC) or [] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bit weird that we're modifying a constant.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but that's how the lib is designed. It could be refactored to a more sane API, with a setter function, but not in this PR. |
||
|
|
||
| wanted_uuids = config_entry.data.get(CONF_UUID) or None | ||
|
|
||
| @callback | ||
| def async_cast_discovered(discover: ChromecastInfo) -> None: | ||
| """Handle discovery of a new chromecast.""" | ||
| # If wanted_uuid is set, we're handling a specific cast device identified by UUID | ||
| if wanted_uuid is not None and wanted_uuid != discover.uuid: | ||
| # UUID not matching, this is not it. | ||
| # If wanted_uuids is set, we're only accepting specific cast devices identified | ||
| # by UUID | ||
| if wanted_uuids is not None and discover.uuid not in wanted_uuids: | ||
| # UUID not matching, ignore. | ||
| return | ||
|
|
||
| cast_device = _async_create_cast_device(hass, discover) | ||
| if cast_device is not None: | ||
| async_add_entities([cast_device]) | ||
|
|
||
| async_dispatcher_connect(hass, SIGNAL_CAST_DISCOVERED, async_cast_discovered) | ||
| # Re-play the callback for all past chromecasts, store the objects in | ||
| # a list to avoid concurrent modification resulting in exception. | ||
| for chromecast in hass.data[KNOWN_CHROMECAST_INFO_KEY].values(): | ||
| async_cast_discovered(chromecast) | ||
|
|
||
| ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass)) | ||
| hass.async_add_executor_job(setup_internal_discovery, hass, config_entry) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, we had no config schema to begin with and just passed that to import? 🤔