mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 08:17:08 +00:00
Bump pychromecast to 10.1.0 (#59719)
* Prepare for pychromecast 10 * Bump pychromecast to 10.0.0 * Bump pychromecast to 10.1.0 * Update homeassistant/components/cast/discovery.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
a88469ec74
commit
593bc866f0
@ -1,7 +1,6 @@
|
||||
"""Consts for Cast integration."""
|
||||
|
||||
DOMAIN = "cast"
|
||||
DEFAULT_PORT = 8009
|
||||
|
||||
# Stores a threading.Lock that is held by the internal pychromecast discovery.
|
||||
INTERNAL_DISCOVERY_RUNNING_KEY = "cast_discovery_running"
|
||||
|
@ -11,7 +11,6 @@ from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from .const import (
|
||||
CAST_BROWSER_KEY,
|
||||
CONF_KNOWN_HOSTS,
|
||||
DEFAULT_PORT,
|
||||
INTERNAL_DISCOVERY_RUNNING_KEY,
|
||||
SIGNAL_CAST_DISCOVERED,
|
||||
SIGNAL_CAST_REMOVED,
|
||||
@ -21,15 +20,18 @@ from .helpers import ChromecastInfo, ChromeCastZeroconf
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def discover_chromecast(hass: HomeAssistant, device_info):
|
||||
def discover_chromecast(
|
||||
hass: HomeAssistant, cast_info: pychromecast.models.CastInfo
|
||||
) -> None:
|
||||
"""Discover a Chromecast."""
|
||||
|
||||
info = ChromecastInfo(
|
||||
services=device_info.services,
|
||||
uuid=device_info.uuid,
|
||||
model_name=device_info.model_name,
|
||||
friendly_name=device_info.friendly_name,
|
||||
is_audio_group=device_info.port != DEFAULT_PORT,
|
||||
services=cast_info.services,
|
||||
uuid=cast_info.uuid,
|
||||
model_name=cast_info.model_name,
|
||||
friendly_name=cast_info.friendly_name,
|
||||
cast_type=cast_info.cast_type,
|
||||
manufacturer=cast_info.manufacturer,
|
||||
)
|
||||
|
||||
if info.uuid is None:
|
||||
@ -78,6 +80,8 @@ def setup_internal_discovery(hass: HomeAssistant, config_entry) -> None:
|
||||
uuid=cast_info.uuid,
|
||||
model_name=cast_info.model_name,
|
||||
friendly_name=cast_info.friendly_name,
|
||||
cast_type=cast_info.cast_type,
|
||||
manufacturer=cast_info.manufacturer,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,7 @@ from typing import Optional
|
||||
|
||||
import attr
|
||||
from pychromecast import dial
|
||||
from pychromecast.const import CAST_MANUFACTURERS
|
||||
from pychromecast.const import CAST_TYPE_GROUP
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
@ -16,55 +16,30 @@ class ChromecastInfo:
|
||||
"""
|
||||
|
||||
services: set | None = attr.ib()
|
||||
uuid: str | None = attr.ib(
|
||||
converter=attr.converters.optional(str), default=None
|
||||
) # always convert UUID to string if not None
|
||||
_manufacturer = attr.ib(type=Optional[str], default=None)
|
||||
model_name: str = attr.ib(default="")
|
||||
friendly_name: str | None = attr.ib(default=None)
|
||||
is_audio_group = attr.ib(type=Optional[bool], default=False)
|
||||
uuid: str = attr.ib(converter=attr.converters.optional(str))
|
||||
model_name: str = attr.ib()
|
||||
friendly_name: str = attr.ib()
|
||||
cast_type: str = attr.ib()
|
||||
manufacturer: str = attr.ib()
|
||||
is_dynamic_group = attr.ib(type=Optional[bool], default=None)
|
||||
|
||||
@property
|
||||
def is_information_complete(self) -> bool:
|
||||
"""Return if all information is filled out."""
|
||||
want_dynamic_group = self.is_audio_group
|
||||
have_dynamic_group = self.is_dynamic_group is not None
|
||||
have_all_except_dynamic_group = all(
|
||||
attr.astuple(
|
||||
self,
|
||||
filter=attr.filters.exclude(
|
||||
attr.fields(ChromecastInfo).is_dynamic_group
|
||||
),
|
||||
)
|
||||
)
|
||||
return have_all_except_dynamic_group and (
|
||||
not want_dynamic_group or have_dynamic_group
|
||||
)
|
||||
|
||||
@property
|
||||
def manufacturer(self) -> str | None:
|
||||
"""Return the manufacturer."""
|
||||
if self._manufacturer:
|
||||
return self._manufacturer
|
||||
if not self.model_name:
|
||||
return None
|
||||
return CAST_MANUFACTURERS.get(self.model_name.lower(), "Google Inc.")
|
||||
def is_audio_group(self) -> bool:
|
||||
"""Return if the cast is an audio group."""
|
||||
return self.cast_type == CAST_TYPE_GROUP
|
||||
|
||||
def fill_out_missing_chromecast_info(self) -> ChromecastInfo:
|
||||
"""Return a new ChromecastInfo object with missing attributes filled in.
|
||||
|
||||
Uses blocking HTTP / HTTPS.
|
||||
"""
|
||||
if self.is_information_complete:
|
||||
if not self.is_audio_group or self.is_dynamic_group is not None:
|
||||
# We have all information, no need to check HTTP API.
|
||||
return self
|
||||
|
||||
# Fill out missing group information via HTTP API.
|
||||
if self.is_audio_group:
|
||||
is_dynamic_group = False
|
||||
http_group_status = None
|
||||
if self.uuid:
|
||||
http_group_status = dial.get_multizone_status(
|
||||
None,
|
||||
services=self.services,
|
||||
@ -72,8 +47,7 @@ class ChromecastInfo:
|
||||
)
|
||||
if http_group_status is not None:
|
||||
is_dynamic_group = any(
|
||||
str(g.uuid) == self.uuid
|
||||
for g in http_group_status.dynamic_groups
|
||||
str(g.uuid) == self.uuid for g in http_group_status.dynamic_groups
|
||||
)
|
||||
|
||||
return ChromecastInfo(
|
||||
@ -81,26 +55,11 @@ class ChromecastInfo:
|
||||
uuid=self.uuid,
|
||||
friendly_name=self.friendly_name,
|
||||
model_name=self.model_name,
|
||||
is_audio_group=True,
|
||||
cast_type=self.cast_type,
|
||||
manufacturer=self.manufacturer,
|
||||
is_dynamic_group=is_dynamic_group,
|
||||
)
|
||||
|
||||
# Fill out some missing information (friendly_name, uuid) via HTTP dial.
|
||||
http_device_status = dial.get_device_status(
|
||||
None, services=self.services, zconf=ChromeCastZeroconf.get_zeroconf()
|
||||
)
|
||||
if http_device_status is None:
|
||||
# HTTP dial didn't give us any new information.
|
||||
return self
|
||||
|
||||
return ChromecastInfo(
|
||||
services=self.services,
|
||||
uuid=(self.uuid or http_device_status.uuid),
|
||||
friendly_name=(self.friendly_name or http_device_status.friendly_name),
|
||||
manufacturer=(self.manufacturer or http_device_status.manufacturer),
|
||||
model_name=(self.model_name or http_device_status.model_name),
|
||||
)
|
||||
|
||||
|
||||
class ChromeCastZeroconf:
|
||||
"""Class to hold a zeroconf instance."""
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Google Cast",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/cast",
|
||||
"requirements": ["pychromecast==9.4.0"],
|
||||
"requirements": ["pychromecast==10.1.0"],
|
||||
"after_dependencies": [
|
||||
"cloud",
|
||||
"http",
|
||||
|
@ -235,6 +235,8 @@ class CastDevice(MediaPlayerEntity):
|
||||
self._cast_info.friendly_name,
|
||||
None,
|
||||
None,
|
||||
self._cast_info.cast_type,
|
||||
self._cast_info.manufacturer,
|
||||
),
|
||||
ChromeCastZeroconf.get_zeroconf(),
|
||||
)
|
||||
@ -833,6 +835,8 @@ class DynamicCastGroup:
|
||||
self._cast_info.friendly_name,
|
||||
None,
|
||||
None,
|
||||
self._cast_info.cast_type,
|
||||
self._cast_info.manufacturer,
|
||||
),
|
||||
ChromeCastZeroconf.get_zeroconf(),
|
||||
)
|
||||
|
@ -1390,7 +1390,7 @@ pycfdns==1.2.2
|
||||
pychannels==1.0.0
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==9.4.0
|
||||
pychromecast==10.1.0
|
||||
|
||||
# homeassistant.components.pocketcasts
|
||||
pycketcasts==1.0.0
|
||||
|
@ -838,7 +838,7 @@ pybotvac==0.0.22
|
||||
pycfdns==1.2.2
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==9.4.0
|
||||
pychromecast==10.1.0
|
||||
|
||||
# homeassistant.components.climacell
|
||||
pyclimacell==0.18.2
|
||||
|
@ -10,10 +10,6 @@ import pytest
|
||||
def dial_mock():
|
||||
"""Mock pychromecast dial."""
|
||||
dial_mock = MagicMock()
|
||||
dial_mock.get_device_status.return_value.uuid = "fake_uuid"
|
||||
dial_mock.get_device_status.return_value.manufacturer = "fake_manufacturer"
|
||||
dial_mock.get_device_status.return_value.model_name = "fake_model_name"
|
||||
dial_mock.get_device_status.return_value.friendly_name = "fake_friendly_name"
|
||||
dial_mock.get_multizone_status.return_value.dynamic_groups = []
|
||||
return dial_mock
|
||||
|
||||
|
@ -8,6 +8,7 @@ from uuid import UUID
|
||||
|
||||
import attr
|
||||
import pychromecast
|
||||
from pychromecast.const import CAST_TYPE_CHROMECAST, CAST_TYPE_GROUP
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import media_player, tts
|
||||
@ -70,10 +71,10 @@ def get_fake_chromecast_info(
|
||||
ChromecastInfo(
|
||||
services=self.services,
|
||||
uuid=self.uuid,
|
||||
manufacturer=self.manufacturer,
|
||||
model_name=self.model_name,
|
||||
friendly_name=self.friendly_name,
|
||||
is_audio_group=self.is_audio_group,
|
||||
cast_type=self.cast_type,
|
||||
manufacturer=self.manufacturer,
|
||||
is_dynamic_group=self.is_dynamic_group,
|
||||
)
|
||||
== other
|
||||
@ -83,10 +84,12 @@ def get_fake_chromecast_info(
|
||||
return ExtendedChromecastInfo(
|
||||
host=host,
|
||||
port=port,
|
||||
uuid=uuid,
|
||||
friendly_name="Speaker",
|
||||
services={"the-service"},
|
||||
is_audio_group=port != 8009,
|
||||
uuid=uuid,
|
||||
model_name="Chromecast",
|
||||
friendly_name="Speaker",
|
||||
cast_type=CAST_TYPE_GROUP if port != 8009 else CAST_TYPE_CHROMECAST,
|
||||
manufacturer="Nabu Casa",
|
||||
)
|
||||
|
||||
|
||||
@ -142,6 +145,8 @@ async def async_setup_cast_internal_discovery(hass, config=None):
|
||||
info.friendly_name,
|
||||
info.host,
|
||||
info.port,
|
||||
info.cast_type,
|
||||
info.manufacturer,
|
||||
)
|
||||
discovery_callback(info.uuid, service_name)
|
||||
|
||||
@ -157,6 +162,8 @@ async def async_setup_cast_internal_discovery(hass, config=None):
|
||||
info.friendly_name,
|
||||
info.host,
|
||||
info.port,
|
||||
info.cast_type,
|
||||
info.manufacturer,
|
||||
),
|
||||
)
|
||||
|
||||
@ -195,6 +202,8 @@ async def async_setup_media_player_cast(hass: HomeAssistant, info: ChromecastInf
|
||||
info.friendly_name,
|
||||
info.host,
|
||||
info.port,
|
||||
info.cast_type,
|
||||
info.manufacturer,
|
||||
)
|
||||
discovery_callback(info.uuid, service_name)
|
||||
|
||||
@ -211,6 +220,8 @@ async def async_setup_media_player_cast(hass: HomeAssistant, info: ChromecastInf
|
||||
info.friendly_name,
|
||||
info.host,
|
||||
info.port,
|
||||
info.cast_type,
|
||||
info.manufacturer,
|
||||
)
|
||||
discovery_callback(info.uuid, service_name)
|
||||
|
||||
@ -245,64 +256,6 @@ async def test_start_discovery_called_once(hass, castbrowser_mock):
|
||||
assert castbrowser_mock.start_discovery.call_count == 1
|
||||
|
||||
|
||||
async def test_internal_discovery_callback_fill_out(hass):
|
||||
"""Test internal discovery automatically filling out information."""
|
||||
discover_cast, _, _ = await async_setup_cast_internal_discovery(hass)
|
||||
info = get_fake_chromecast_info(host="host1")
|
||||
zconf = get_fake_zconf(host="host1", port=8009)
|
||||
full_info = attr.evolve(
|
||||
info,
|
||||
model_name="google home",
|
||||
friendly_name="Speaker",
|
||||
uuid=FakeUUID,
|
||||
manufacturer="Nabu Casa",
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.cast.helpers.dial.get_device_status",
|
||||
return_value=full_info,
|
||||
), patch(
|
||||
"homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf",
|
||||
return_value=zconf,
|
||||
):
|
||||
signal = MagicMock()
|
||||
|
||||
async_dispatcher_connect(hass, "cast_discovered", signal)
|
||||
discover_cast("the-service", info)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# when called with incomplete info, it should use HTTP to get missing
|
||||
discover = signal.mock_calls[0][1][0]
|
||||
assert discover == full_info
|
||||
|
||||
|
||||
async def test_internal_discovery_callback_fill_out_default_manufacturer(hass):
|
||||
"""Test internal discovery automatically filling out information."""
|
||||
discover_cast, _, _ = await async_setup_cast_internal_discovery(hass)
|
||||
info = get_fake_chromecast_info(host="host1")
|
||||
zconf = get_fake_zconf(host="host1", port=8009)
|
||||
full_info = attr.evolve(
|
||||
info, model_name="google home", friendly_name="Speaker", uuid=FakeUUID
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.cast.helpers.dial.get_device_status",
|
||||
return_value=full_info,
|
||||
), patch(
|
||||
"homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf",
|
||||
return_value=zconf,
|
||||
):
|
||||
signal = MagicMock()
|
||||
|
||||
async_dispatcher_connect(hass, "cast_discovered", signal)
|
||||
discover_cast("the-service", info)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# when called with incomplete info, it should use HTTP to get missing
|
||||
discover = signal.mock_calls[0][1][0]
|
||||
assert discover == attr.evolve(full_info, manufacturer="Google Inc.")
|
||||
|
||||
|
||||
async def test_internal_discovery_callback_fill_out_fail(hass):
|
||||
"""Test internal discovery automatically filling out information."""
|
||||
discover_cast, _, _ = await async_setup_cast_internal_discovery(hass)
|
||||
@ -337,7 +290,7 @@ async def test_internal_discovery_callback_fill_out_group(hass):
|
||||
zconf = get_fake_zconf(host="host1", port=12345)
|
||||
full_info = attr.evolve(
|
||||
info,
|
||||
model_name="",
|
||||
model_name="Chromecast",
|
||||
friendly_name="Speaker",
|
||||
uuid=FakeUUID,
|
||||
is_dynamic_group=False,
|
||||
|
Loading…
x
Reference in New Issue
Block a user