Skip to content

Commit

Permalink
Bump pychromecast to 10.1.0 (#59719)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

Co-authored-by: Martin Hjelmare <[email protected]>
  • Loading branch information
emontnemery and MartinHjelmare authored Nov 17, 2021
1 parent a88469e commit 593bc86
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 145 deletions.
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
18 changes: 11 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,18 @@
_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:
Expand Down Expand Up @@ -78,6 +80,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 @@ -235,6 +235,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 @@ -833,6 +835,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

0 comments on commit 593bc86

Please sign in to comment.