Skip to content
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

Bump pychromecast to 10.1.0 #59719

Merged
merged 4 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion homeassistant/components/cast/const.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
16 changes: 9 additions & 7 deletions homeassistant/components/cast/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from .const import (
CAST_BROWSER_KEY,
CONF_KNOWN_HOSTS,
DEFAULT_PORT,
INTERNAL_DISCOVERY_RUNNING_KEY,
SIGNAL_CAST_DISCOVERED,
SIGNAL_CAST_REMOVED,
Expand All @@ -21,15 +20,16 @@
_LOGGER = logging.getLogger(__name__)


def discover_chromecast(hass: HomeAssistant, device_info):
def discover_chromecast(hass: HomeAssistant, cast_info: pychromecast.models.CastInfo):
emontnemery marked this conversation as resolved.
Show resolved Hide resolved
"""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:
Expand Down Expand Up @@ -78,6 +78,8 @@ def remove_cast(self, uuid, service, cast_info):
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,
),
)

Expand Down
93 changes: 26 additions & 67 deletions homeassistant/components/cast/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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)
Expand All @@ -16,89 +16,48 @@ 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,
zconf=ChromeCastZeroconf.get_zeroconf(),
)
if http_group_status is not None:
is_dynamic_group = any(
str(g.uuid) == self.uuid
for g in http_group_status.dynamic_groups
)

return ChromecastInfo(
services=self.services,
uuid=self.uuid,
friendly_name=self.friendly_name,
model_name=self.model_name,
is_audio_group=True,
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()
is_dynamic_group = False
http_group_status = None
http_group_status = dial.get_multizone_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
if http_group_status is not None:
is_dynamic_group = any(
str(g.uuid) == self.uuid for g in http_group_status.dynamic_groups
)

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),
uuid=self.uuid,
friendly_name=self.friendly_name,
model_name=self.model_name,
cast_type=self.cast_type,
manufacturer=self.manufacturer,
is_dynamic_group=is_dynamic_group,
)


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/cast/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/cast/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ async def async_connect_to_chromecast(self):
self._cast_info.friendly_name,
None,
None,
self._cast_info.cast_type,
self._cast_info.manufacturer,
),
ChromeCastZeroconf.get_zeroconf(),
)
Expand Down Expand Up @@ -836,6 +838,8 @@ async def async_connect_to_chromecast(self):
self._cast_info.friendly_name,
None,
None,
self._cast_info.cast_type,
self._cast_info.manufacturer,
),
ChromeCastZeroconf.get_zeroconf(),
)
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions tests/components/cast/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
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

Expand Down
79 changes: 16 additions & 63 deletions tests/components/cast/test_media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import attr
import pychromecast
from pychromecast.const import CAST_TYPE_CHROMECAST, CAST_TYPE_GROUP
import pytest

from homeassistant.components import media_player, tts
Expand Down Expand Up @@ -70,10 +71,10 @@ def __eq__(self, other):
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
Expand All @@ -83,10 +84,12 @@ def __eq__(self, other):
return ExtendedChromecastInfo(
host=host,
port=port,
services={"the-service"},
uuid=uuid,
model_name="Chromecast",
friendly_name="Speaker",
services={"the-service"},
is_audio_group=port != 8009,
cast_type=CAST_TYPE_GROUP if port != 8009 else CAST_TYPE_CHROMECAST,
manufacturer="Nabu Casa",
)


Expand Down Expand Up @@ -142,6 +145,8 @@ def discover_chromecast(service_name: str, info: ChromecastInfo) -> None:
info.friendly_name,
info.host,
info.port,
info.cast_type,
info.manufacturer,
)
discovery_callback(info.uuid, service_name)

Expand All @@ -157,6 +162,8 @@ def remove_chromecast(service_name: str, info: ChromecastInfo) -> None:
info.friendly_name,
info.host,
info.port,
info.cast_type,
info.manufacturer,
),
)

Expand Down Expand Up @@ -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)

Expand All @@ -211,6 +220,8 @@ def discover_chromecast(service_name: str, info: ChromecastInfo) -> None:
info.friendly_name,
info.host,
info.port,
info.cast_type,
info.manufacturer,
)
discovery_callback(info.uuid, service_name)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down