Skip to content

Cast Integration Cleanup#13275

Merged
balloob merged 18 commits intohome-assistant:devfrom
OttoWinter:cast-cleanup
Mar 23, 2018
Merged

Cast Integration Cleanup#13275
balloob merged 18 commits intohome-assistant:devfrom
OttoWinter:cast-cleanup

Conversation

@OttoWinter
Copy link
Copy Markdown
Member

@OttoWinter OttoWinter commented Mar 16, 2018

Description:

Clean up the cast integration.

Most notably the pychromecast.Chromecast objects are now managed by the entities, not globally. This makes handling "moved" hosts, unavailable chromecasts and disconnecting much cleaner.

  • setup_platform and internal discovery now only create "ChromecastInfo" objects (with attrs 👌), not Chromecast objects. Missing information is automatically added via HTTP dial.
  • The CastDevice entities now take care of moved hosts themselves (using dispatcher callbacks).
  • Added available property.
  • Enabled polling mode if an app is active, because we sometimes don't receive media status callbacks from certain apps.
  • Added a ton of comments to hopefully make understanding the source code a bit easier.

Todo:

  • Some more local testing.
  • Fixing tests.
  • Add tests

Out of scope:

  • Move some of the logic into pychromecast (would be good to test the logic with this user base first; but pychromecast could benefit from some logic, like availability, info objects, or half-polling mode; the last one is a bit controversial though)

fixes #13359, fixes #13319, fixes #13090

Example entry for configuration.yaml (if applicable):

media_player:
  - platform: cast

Checklist:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass

If the code does not interact with devices:

  • Tests have been added to verify that the new code works.

"""Helper class to handle pychromecast status callbacks.

Necessary because a CastDevice entity can create a new socket client
and therefore callbacks from multiple chromecast connections can potentially
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (80 > 79 characters)

@OttoWinter
Copy link
Copy Markdown
Member Author

Tested extensively with chromecast, google home, cast audio and audio groups now. On to the tests!

"""Turn on the cast device."""
import pychromecast

if self._chromecast is None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When can this happen? We set the cast info in async_added_to_hass.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the chromecast "switched host". This happens for example when a cast audio group changes its "elected leader"/master. Then we first disconnect the old chromecast object and then connect the new one.

In that process, self._chromecast is briefly changed to None to indicate that the old chromecast object is invalid (any commands on the old object would result in exceptions, as the socket is no longer connected)

Additionally, when starting up, there might be a phase when the chromecast object is not connected yet but the entity is already visible in the frontend. This can be reproduced by manually specifying the cast device by host: in configuration.yaml and disabling any internet connection of your computer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we set the entity to unavailable if it looses the connection to the chromecast? Then you wouldn't need to guard in all the methods.

It sounds weird that we allow entities to be created without a working connection during setup.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we set the entity to unavailable if it looses the connection to the chromecast?

Yes, that's also what this PR does. When self._chromecast is None available will also be False.

Then you wouldn't need to guard in all the methods.

Ok, that's good. I put those checks everywhere to protect for potential race conditions, where the service calls are executed even though we're not connected. If the core can guarantee that these methods will never be called with an unavailable state I can also remove those checks.

It sounds weird that we allow entities to be created without a working connection during setup.

That's true, and also was the previous behavior. The problem with that was that we would block the entire Home Assistant startup waiting for the socket client to connect. If no connection was available, we would even stop startup indefinitely - not good. I tried looking for a solution to that with asyncio for some time, but ultimately I think unavailable is better (and quicker).

I mean if we want to we can also try checking the HTTP API of the chromecast before adding the device, that would fix part of the problem (for standard chromecasts).

"""Return the state of the player."""
if self.media_status is None:
if self._media_status is None:
return STATE_UNKNOWN
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return None for unknown state.

elif self.cast.is_idle:
elif self._chromecast is not None and self._chromecast.is_idle:
return STATE_OFF
return STATE_UNKNOWN
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Gets rid of those pesky "Setup of platform cast is taking over 10 seconds." messages.
@edif30
Copy link
Copy Markdown
Contributor

edif30 commented Mar 17, 2018

Once ready let me know and I can test if you'd like.

@MartinHjelmare
Copy link
Copy Markdown
Member

MartinHjelmare commented Mar 17, 2018

FYI, regarding core guarding from calling services for unavailable entities: Components that use async_extract_from_service handle this. If a platform registers its own services it has to handle that on its own, in the service handler.

Copy link
Copy Markdown
Member Author

@OttoWinter OttoWinter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments here to point out some aspects of the changes that didn't really fit in code comments.

Since yesterday I also went through all sorts of scenarios of chromecasts disconnecting at certain times, no network connection, manually moving hosts around and so on. All of those "physical tests" appear to be working correctly with my set up + the available property now gives immediate feedback as to what is happening.

@edif30 If you want you could test these changes. It should essentially be as simple as copying this file into [config_dir]/custom_components/media_player/cast.py.

vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_IGNORE_CEC): [cv.string],
vol.Optional(CONF_IGNORE_CEC, default=[]): vol.All(cv.ensure_list,
[cv.string])
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if someone with more knowledge of voluptuous could take a look at this changed line. As far as I know, we should use cv.ensure_list wherever possible. Another question is whether to put it in this PR, but it's title "cleanup" after all...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

# Remove previous cast infos with same uuid from known chromecasts.
same_uuid = set(x for x in hass.data[KNOWN_CHROMECAST_INFO_KEY]
if info.uuid == x.uuid)
hass.data[KNOWN_CHROMECAST_INFO_KEY] -= same_uuid
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean there can only be a single uuid removed from this set anyway as non-None UUIDs are unique, but the alternative solutions of using set#remove and a dictionary for hass.data[KNOWN_CHROMECAST_INFO_KEY] were just way messier (the latter one getting particularly complicated because of None UUID handling).

"""Set up the pychromecast internal discovery."""
hass.data.setdefault(INTERNAL_DISCOVERY_RUNNING_KEY, threading.Lock())
if INTERNAL_DISCOVERY_RUNNING_KEY not in hass.data:
hass.data[INTERNAL_DISCOVERY_RUNNING_KEY] = threading.Lock()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to be the non-setdefault syntax because creating a threading.Lock instance is quite expensive.

@@ -139,191 +191,402 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,

# Import CEC IGNORE attributes
pychromecast.IGNORE_CEC += config.get(CONF_IGNORE_CEC, [])
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're wondering why I'm still supplying [] here even though a default is set in the platform schema, it's because discovery sends an empty dict as config.

info)
if info.friendly_name is None:
# HTTP dial failed, so we won't be able to connect.
raise PlatformNotReady
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When manually specifying a host in configuration.yaml like this:

media_player:
  - platform: cast
    host: 192.168.178.42

this will block startup for 10 seconds; though I suppose that is okay when users manually specify the host.

@OttoWinter OttoWinter changed the title [WIP] Cast Integration Cleanup Cast Integration Cleanup Mar 18, 2018
@OttoWinter OttoWinter requested a review from andrey-git as a code owner March 19, 2018 15:16
I like "protected" access, but I like reviewing more :)
@balloob
Copy link
Copy Markdown
Member

balloob commented Mar 23, 2018

Awesome work 🐬

@balloob balloob merged commit 6a625bd into home-assistant:dev Mar 23, 2018
@MaluNoPeleke
Copy link
Copy Markdown

MaluNoPeleke commented Mar 27, 2018

@OttoWinter Is it already merged?
I just got a

Platform cast not ready yet. Retrying in 30 seconds.

with beta 66.

See #13359

@OttoWinter
Copy link
Copy Markdown
Member Author

Yes it is in the beta. And that error message is expected now, it signals that one of Chromecast devices defined in your configuration.yaml can't be reached and therefore can't be setup.

@MaluNoPeleke
Copy link
Copy Markdown

You mean the automatically created entity_registry.yaml?
Yesterday the same setup worked as expected but today not. All devices for the group are connected to power and the network (while not all devices of the list in the above mentioned file are connected to power).
Could the TTS and delay command be a problem?

    action:
      - service: media_player.volume_set
        data_template:
          entity_id: media_player.schlafzimmer
          volume_level: 0.1
      - service: tts.google_say
        data_template:
          entity_id: media_player.schlafzimmer
          message: Guten Morgen. Es ist {{now().hour}} Uhr {{now().minute}}
          cache: false
      - delay: 00:00:15
      - service: media_player.play_media
        data_template:
          entity_id: media_player.wecker
          media_content_id: 'http://fluxfm.hoerradar.de/fluxfmberlin-live-aac-mq?sABC=59s45n23%230%23no8617362n29q2o435p17n54928n16s5%23gharva&amsparams=playerid:tunein;skey:1509186083'
          media_content_type: 'audio/mp3'

@OttoWinter
Copy link
Copy Markdown
Member Author

  1. What are you using to discover your cast devices? discovery: in your configuration.yaml, or media_player: platform: cast or manually by host:?
  2. In what way are they not working? Is it just the error message or do they not turn on/execute the command?
  3. If you have some Chromecast devices in your configuration.yaml that are not connected to power that error message is expected; the other devices should work correctly though. (I hope, at least they did during my testing)
  4. The TTS and delay shouldn't really affect anything here. The entity_regitry.yaml neither.
  5. I don't think this discussion should be done in this PR, as it's not a comment on the code. Please open a separate issue and I will help you out there.

@MaluNoPeleke
Copy link
Copy Markdown

Sorry, I have tried to summarize everything in #13483

@balloob balloob mentioned this pull request Mar 30, 2018
@home-assistant home-assistant locked and limited conversation to collaborators Jul 26, 2018
@OttoWinter OttoWinter deleted the cast-cleanup branch September 29, 2018 10:32
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot connect to Google Home (Casting) Cast fails to recognize chromecast and floods network with DNS requests Google Cast device not recognised

7 participants