diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index e7d13ea0a18..9266709a83a 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -21,20 +21,16 @@ _LOGGER = logging.getLogger(__name__) def discover_chromecast(hass: HomeAssistant, info: ChromecastInfo): """Discover a Chromecast.""" - if info in hass.data[KNOWN_CHROMECAST_INFO_KEY]: - _LOGGER.debug("Discovered previous chromecast %s", info) + if info.uuid is None: + _LOGGER.error("Discovered chromecast without uuid %s", info) + return - # Either discovered completely new chromecast or a "moved" one. - _LOGGER.debug("Discovered chromecast %s", info) + if info.uuid in hass.data[KNOWN_CHROMECAST_INFO_KEY]: + _LOGGER.debug("Discovered update for known chromecast %s", info) + else: + _LOGGER.debug("Discovered chromecast %s", info) - if info.uuid is not None: - # Remove previous cast infos with same uuid from known chromecasts. - same_uuid = { - x for x in hass.data[KNOWN_CHROMECAST_INFO_KEY] if info.uuid == x.uuid - } - hass.data[KNOWN_CHROMECAST_INFO_KEY] -= same_uuid - - hass.data[KNOWN_CHROMECAST_INFO_KEY].add(info) + hass.data[KNOWN_CHROMECAST_INFO_KEY][info.uuid] = info dispatcher_send(hass, SIGNAL_CAST_DISCOVERED, info) @@ -54,47 +50,72 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: # Internal discovery is already running return - def internal_add_callback(name): - """Handle zeroconf discovery of a new chromecast.""" - mdns = listener.services[name] + def internal_add_update_callback(uuid, service_name): + """Handle zeroconf discovery of a new or updated chromecast.""" + service = listener.services[uuid] + + # For support of deprecated IP based white listing + zconf = ChromeCastZeroconf.get_zeroconf() + service_info = None + tries = 0 + while service_info is None and tries < 4: + try: + service_info = zconf.get_service_info( + "_googlecast._tcp.local.", service_name + ) + except OSError: + # If the zeroconf fails to receive the necessary data we abort + # adding the service + break + tries += 1 + + if not service_info: + _LOGGER.warning( + "setup_internal_discovery failed to get info for %s, %s", + uuid, + service_name, + ) + return + + addresses = service_info.parsed_addresses() + host = addresses[0] if addresses else service_info.server + discover_chromecast( hass, ChromecastInfo( - service=name, - host=mdns[0], - port=mdns[1], - uuid=mdns[2], - model_name=mdns[3], - friendly_name=mdns[4], + services=service[0], + uuid=service[1], + model_name=service[2], + friendly_name=service[3], + host=host, + port=service_info.port, ), ) - def internal_remove_callback(name, mdns): + def internal_remove_callback(uuid, service_name, service): """Handle zeroconf discovery of a removed chromecast.""" _remove_chromecast( hass, ChromecastInfo( - service=name, - host=mdns[0], - port=mdns[1], - uuid=mdns[2], - model_name=mdns[3], - friendly_name=mdns[4], + services=service[0], + uuid=service[1], + model_name=service[2], + friendly_name=service[3], ), ) _LOGGER.debug("Starting internal pychromecast discovery.") listener = pychromecast.CastListener( - internal_add_callback, + internal_add_update_callback, internal_remove_callback, - internal_add_callback, # Use internal_add_callback also for updates + internal_add_update_callback, ) browser = pychromecast.start_discovery(listener, ChromeCastZeroconf.get_zeroconf()) def stop_discovery(event): """Stop discovery of new chromecasts.""" _LOGGER.debug("Stopping internal pychromecast discovery.") - pychromecast.stop_discovery(browser) + pychromecast.discovery.stop_discovery(browser) hass.data[INTERNAL_DISCOVERY_RUNNING_KEY].release() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_discovery) diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index 5a99d30f087..cc0ec911bbf 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -14,9 +14,9 @@ class ChromecastInfo: This also has the same attributes as the mDNS fields by zeroconf. """ - host = attr.ib(type=str) - port = attr.ib(type=int) - service = attr.ib(type=Optional[str], default=None) + services = attr.ib(type=Optional[set]) + host = attr.ib(type=Optional[str], default=None) + port = attr.ib(type=Optional[int], default=0) uuid = attr.ib( type=Optional[str], converter=attr.converters.optional(str), default=None ) # always convert UUID to string if not None diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index edf0373dd5d..b0d49681414 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==6.0.0"], + "requirements": ["pychromecast==7.0.1"], "after_dependencies": ["cloud","zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 84917e0194a..3883c5ad725 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -55,7 +55,6 @@ from .const import ( DOMAIN as CAST_DOMAIN, KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, - SIGNAL_CAST_REMOVED, SIGNAL_HASS_CAST_SHOW_VIEW, ) from .discovery import setup_internal_discovery @@ -64,6 +63,7 @@ from .helpers import CastStatusListener, ChromecastInfo, ChromeCastZeroconf _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 = ( @@ -78,11 +78,26 @@ SUPPORT_CAST = ( ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_IGNORE_CEC, default=[]): vol.All(cv.ensure_list, [cv.string]), - } +ENTITY_SCHEMA = vol.All( + cv.deprecated(CONF_HOST, invalidation_version="0.116"), + vol.Schema( + { + vol.Exclusive(CONF_HOST, "device_identifier"): cv.string, + vol.Exclusive(CONF_UUID, "device_identifier"): cv.string, + vol.Optional(CONF_IGNORE_CEC): vol.All(cv.ensure_list, [cv.string]), + } + ), +) + +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_HOST, invalidation_version="0.116"), + PLATFORM_SCHEMA.extend( + { + vol.Exclusive(CONF_HOST, "device_identifier"): cv.string, + vol.Exclusive(CONF_UUID, "device_identifier"): cv.string, + vol.Optional(CONF_IGNORE_CEC): vol.All(cv.ensure_list, [cv.string]), + } + ), ) @@ -111,13 +126,14 @@ def _async_create_cast_device(hass: HomeAssistantType, info: ChromecastInfo): async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None ): - """Set up thet Cast platform. + """Set up the Cast platform. Deprecated. """ _LOGGER.warning( "Setting configuration for Cast via platform is deprecated. " "Configure via Cast integration instead." + "This option will become invalid in version 0.116." ) await _async_setup_platform(hass, config, async_add_entities, discovery_info) @@ -130,7 +146,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # no pending task done, _ = await asyncio.wait( - [_async_setup_platform(hass, cfg, async_add_entities, None) for cfg in config] + [ + _async_setup_platform(hass, ENTITY_SCHEMA(cfg), async_add_entities, None) + for cfg in config + ] ) if any([task.exception() for task in done]): exceptions = [task.exception() for task in done] @@ -146,18 +165,25 @@ async def _async_setup_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, set()) + hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, dict()) info = None if discovery_info is not None: - info = ChromecastInfo(host=discovery_info["host"], port=discovery_info["port"]) + info = ChromecastInfo( + host=discovery_info["host"], port=discovery_info["port"], services=None + ) + elif CONF_UUID in config: + info = ChromecastInfo(uuid=config[CONF_UUID], services=None) elif CONF_HOST in config: - info = ChromecastInfo(host=config[CONF_HOST], port=DEFAULT_PORT) + info = ChromecastInfo(host=config[CONF_HOST], port=DEFAULT_PORT, services=None) @callback def async_cast_discovered(discover: ChromecastInfo) -> None: """Handle discovery of a new chromecast.""" - if info is not None and info.host_port != discover.host_port: + if info is not None and ( + (info.uuid is not None and info.uuid != discover.uuid) + or (info.host is not None and info.host_port != discover.host_port) + ): # Waiting for a specific cast device, this is not it. return @@ -168,7 +194,7 @@ async def _async_setup_platform( 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 list(hass.data[KNOWN_CHROMECAST_INFO_KEY]): + for chromecast in hass.data[KNOWN_CHROMECAST_INFO_KEY].values(): async_cast_discovered(chromecast) ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass)) @@ -187,10 +213,7 @@ class CastDevice(MediaPlayerEntity): """Initialize the cast device.""" self._cast_info = cast_info - self.services = None - if cast_info.service: - self.services = set() - self.services.add(cast_info.service) + self.services = cast_info.services self._chromecast: Optional[pychromecast.Chromecast] = None self.cast_status = None self.media_status = None @@ -211,9 +234,6 @@ class CastDevice(MediaPlayerEntity): self._add_remove_handler = async_dispatcher_connect( self.hass, SIGNAL_CAST_DISCOVERED, self._async_cast_discovered ) - self._del_remove_handler = async_dispatcher_connect( - self.hass, SIGNAL_CAST_REMOVED, self._async_cast_removed - ) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_stop) self.hass.async_create_task( async_create_catching_coro(self.async_set_cast_info(self._cast_info)) @@ -245,42 +265,26 @@ class CastDevice(MediaPlayerEntity): self._cast_info = cast_info - if self.services is not None: - if cast_info.service not in self.services: - _LOGGER.debug( - "[%s %s (%s:%s)] Got new service: %s (%s)", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - cast_info.service, - self.services, - ) - - self.services.add(cast_info.service) - if self._chromecast is not None: # Only setup the chromecast once, added elements to services # will automatically be picked up. return _LOGGER.debug( - "[%s %s (%s:%s)] Connecting to cast device by service %s", + "[%s %s] Connecting to cast device by service %s", self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, self.services, ) chromecast = await self.hass.async_add_executor_job( pychromecast.get_chromecast_from_service, ( self.services, - ChromeCastZeroconf.get_zeroconf(), cast_info.uuid, cast_info.model_name, cast_info.friendly_name, ), + ChromeCastZeroconf.get_zeroconf(), ) self._chromecast = chromecast @@ -296,30 +300,15 @@ class CastDevice(MediaPlayerEntity): self._chromecast.start() self.async_write_ha_state() - async def async_del_cast_info(self, cast_info): - """Remove the service.""" - self.services.discard(cast_info.service) - _LOGGER.debug( - "[%s %s (%s:%s)] Remove service: %s (%s)", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - cast_info.service, - self.services, - ) - async def _async_disconnect(self): """Disconnect Chromecast object if it is set.""" if self._chromecast is None: # Can't disconnect if not connected. return _LOGGER.debug( - "[%s %s (%s:%s)] Disconnecting from chromecast socket.", + "[%s %s] Disconnecting from chromecast socket.", self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, ) self._available = False self.async_write_ha_state() @@ -359,11 +348,9 @@ class CastDevice(MediaPlayerEntity): def new_connection_status(self, connection_status): """Handle updates of connection status.""" _LOGGER.debug( - "[%s %s (%s:%s)] Received cast device connection status: %s", + "[%s %s] Received cast device connection status: %s", self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, connection_status.status, ) if connection_status.status == CONNECTION_STATUS_DISCONNECTED: @@ -378,11 +365,9 @@ class CastDevice(MediaPlayerEntity): # Only update state when availability changed to put less pressure # on state machine. _LOGGER.debug( - "[%s %s (%s:%s)] Cast device availability changed: %s", + "[%s %s] Cast device availability changed: %s", self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, connection_status.status, ) self._available = new_available @@ -391,11 +376,9 @@ class CastDevice(MediaPlayerEntity): def multizone_new_media_status(self, group_uuid, media_status): """Handle updates of audio group media status.""" _LOGGER.debug( - "[%s %s (%s:%s)] Multizone %s media status: %s", + "[%s %s] Multizone %s media status: %s", self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, group_uuid, media_status, ) @@ -738,32 +721,9 @@ class CastDevice(MediaPlayerEntity): # Discovered is not our device. return - if self.services is None: - _LOGGER.warning( - "[%s %s (%s:%s)] Received update for manually added Cast", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - ) - return - _LOGGER.debug("Discovered chromecast with same UUID: %s", discover) await self.async_set_cast_info(discover) - async def _async_cast_removed(self, discover: ChromecastInfo): - """Handle removal of Chromecast.""" - if self._cast_info.uuid is None: - # We can't handle empty UUIDs - return - - if self._cast_info.uuid != discover.uuid: - # Removed is not our device. - return - - _LOGGER.debug("Removed chromecast with same UUID: %s", discover) - await self.async_del_cast_info(discover) - async def _async_stop(self, event): """Disconnect socket on Home Assistant stop.""" await self._async_disconnect() diff --git a/requirements_all.txt b/requirements_all.txt index 9c47005087e..227c4a2ba31 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1255,7 +1255,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==6.0.0 +pychromecast==7.0.1 # homeassistant.components.cmus pycmus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f4876770bdc..cbfa27cbcf2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -567,7 +567,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cast -pychromecast==6.0.0 +pychromecast==7.0.1 # homeassistant.components.coolmaster pycoolmasternet==0.0.4 diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 90b3896396c..9fd30357ea6 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -56,10 +56,24 @@ def get_fake_chromecast_info( ): """Generate a Fake ChromecastInfo with the specified arguments.""" return ChromecastInfo( - host=host, port=port, uuid=uuid, friendly_name="Speaker", service="the-service" + host=host, + port=port, + uuid=uuid, + friendly_name="Speaker", + services={"the-service"}, ) +def get_fake_zconf(host="192.168.178.42", port=8009): + """Generate a Fake Zeroconf object with the specified arguments.""" + parsed_addresses = MagicMock() + parsed_addresses.return_value = [host] + service_info = MagicMock(parsed_addresses=parsed_addresses, port=port) + zconf = MagicMock() + zconf.get_service_info.return_value = service_info + return zconf + + async def async_setup_cast(hass, config=None, discovery_info=None): """Set up the cast platform.""" if config is None: @@ -96,14 +110,13 @@ async def async_setup_cast_internal_discovery(hass, config=None, discovery_info= def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" - listener.services[service_name] = ( - info.host, - info.port, + listener.services[info.uuid] = ( + {service_name}, info.uuid, info.model_name, info.friendly_name, ) - discovery_callback(service_name) + discovery_callback(info.uuid, service_name) return discover_chromecast, add_entities @@ -113,6 +126,7 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas listener = MagicMock(services={}) browser = MagicMock(zc={}) chromecast = get_fake_chromecast(info) + zconf = get_fake_zconf(host=info.host, port=info.port) cast.CastStatusListener = MagicMock() @@ -125,6 +139,9 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas ) as cast_listener, patch( "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, + ), patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf, ): await async_setup_component( hass, @@ -138,14 +155,13 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" - listener.services[service_name] = ( - info.host, - info.port, + listener.services[info.uuid] = ( + {service_name}, info.uuid, info.model_name, info.friendly_name, ) - discovery_callback(service_name) + discovery_callback(info.uuid, service_name) discover_chromecast("the-service", info) await hass.async_block_till_done() @@ -184,7 +200,7 @@ async def test_stop_discovery_called_on_stop(hass): assert start_discovery.call_count == 1 with patch( - "homeassistant.components.cast.discovery.pychromecast.stop_discovery" + "homeassistant.components.cast.discovery.pychromecast.discovery.stop_discovery" ) as stop_discovery: # stop discovery should be called on shutdown hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) @@ -229,15 +245,26 @@ async def test_replay_past_chromecasts(hass): cast_group2 = get_fake_chromecast_info( host="host2", port=42, uuid=UUID("9462202c-e747-4af5-a66b-7dce0e1ebc09") ) + zconf_1 = get_fake_zconf(host="host1", port=42) + zconf_2 = get_fake_zconf(host="host2", port=42) discover_cast, add_dev1 = await async_setup_cast_internal_discovery( hass, discovery_info={"host": "host1", "port": 42} ) - discover_cast("service2", cast_group2) - await hass.async_block_till_done() - assert add_dev1.call_count == 0 - discover_cast("service1", cast_group1) + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_2, + ): + discover_cast("service2", cast_group2) + await hass.async_block_till_done() + assert add_dev1.call_count == 0 + + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_1, + ): + discover_cast("service1", cast_group1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs assert add_dev1.call_count == 1 @@ -249,21 +276,61 @@ async def test_replay_past_chromecasts(hass): assert add_dev2.call_count == 1 -async def test_manual_cast_chromecasts(hass): +async def test_manual_cast_chromecasts_host(hass): """Test only wanted casts are added for manual configuration.""" cast_1 = get_fake_chromecast_info(host="configured_host") cast_2 = get_fake_chromecast_info(host="other_host", uuid=FakeUUID2) + zconf_1 = get_fake_zconf(host="configured_host") + zconf_2 = get_fake_zconf(host="other_host") # Manual configuration of media player with host "configured_host" discover_cast, add_dev1 = await async_setup_cast_internal_discovery( hass, config={"host": "configured_host"} ) - discover_cast("service2", cast_2) + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_2, + ): + discover_cast("service2", cast_2) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs assert add_dev1.call_count == 0 - discover_cast("service1", cast_1) + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_1, + ): + discover_cast("service1", cast_1) + await hass.async_block_till_done() + await hass.async_block_till_done() # having tasks that add jobs + assert add_dev1.call_count == 1 + + +async def test_manual_cast_chromecasts_uuid(hass): + """Test only wanted casts are added for manual configuration.""" + cast_1 = get_fake_chromecast_info(host="host_1", uuid=FakeUUID) + cast_2 = get_fake_chromecast_info(host="host_2", uuid=FakeUUID2) + zconf_1 = get_fake_zconf(host="host_1") + zconf_2 = get_fake_zconf(host="host_2") + + # Manual configuration of media player with host "configured_host" + discover_cast, add_dev1 = await async_setup_cast_internal_discovery( + hass, config={"uuid": FakeUUID} + ) + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_2, + ): + discover_cast("service2", cast_2) + await hass.async_block_till_done() + await hass.async_block_till_done() # having tasks that add jobs + assert add_dev1.call_count == 0 + + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_1, + ): + discover_cast("service1", cast_1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs assert add_dev1.call_count == 1 @@ -273,15 +340,25 @@ async def test_auto_cast_chromecasts(hass): """Test all discovered casts are added for default configuration.""" cast_1 = get_fake_chromecast_info(host="some_host") cast_2 = get_fake_chromecast_info(host="other_host", uuid=FakeUUID2) + zconf_1 = get_fake_zconf(host="some_host") + zconf_2 = get_fake_zconf(host="other_host") # Manual configuration of media player with host "configured_host" discover_cast, add_dev1 = await async_setup_cast_internal_discovery(hass) - discover_cast("service2", cast_2) + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_1, + ): + discover_cast("service2", cast_2) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs assert add_dev1.call_count == 1 - discover_cast("service1", cast_1) + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_2, + ): + discover_cast("service1", cast_1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs assert add_dev1.call_count == 2 @@ -291,15 +368,26 @@ async def test_update_cast_chromecasts(hass): """Test discovery of same UUID twice only adds one cast.""" cast_1 = get_fake_chromecast_info(host="old_host") cast_2 = get_fake_chromecast_info(host="new_host") + zconf_1 = get_fake_zconf(host="old_host") + zconf_2 = get_fake_zconf(host="new_host") # Manual configuration of media player with host "configured_host" discover_cast, add_dev1 = await async_setup_cast_internal_discovery(hass) - discover_cast("service1", cast_1) + + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_1, + ): + discover_cast("service1", cast_1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs assert add_dev1.call_count == 1 - discover_cast("service2", cast_2) + with patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_2, + ): + discover_cast("service2", cast_2) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs assert add_dev1.call_count == 1