Skip to content

Commit

Permalink
Report host+port for discovered casts (#385)
Browse files Browse the repository at this point in the history
* Report host+port for discovered casts

* Update docstrings

* Update documentation
  • Loading branch information
emontnemery authored Jul 2, 2020
1 parent ac426ba commit d76c8f1
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 19 deletions.
15 changes: 12 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ How to use
>> import time
>> import pychromecast
>> chromecasts = pychromecast.get_chromecasts()
>> # List chromecasts on the network, but don't connect
>> services, browser = pychromecast.discovery.discover_chromecasts()
>> # Shut down discovery
>> pychromecast.discovery.stop_discovery(browser)
>> # Discover and connect to chromecasts named Living Room
>> chromecasts, browser = pychromecast.get_listed_chromecasts(friendly_names=["Living Room"])
>> [cc.device.friendly_name for cc in chromecasts]
['Dev', 'Living Room', 'Den', 'Bedroom']
['Living Room']
>> cast = next(cc for cc in chromecasts if cc.device.friendly_name == "Living Room")
>> cast = chromecasts[0]
>> # Start worker thread and wait for cast device to be ready
>> cast.wait()
>> print(cast.device)
Expand All @@ -57,6 +63,9 @@ How to use
>> time.sleep(5)
>> mc.play()
>> # Shut down discovery
>> pychromecast.discovery.stop_discovery(browser)
Adding support for extra namespaces
-----------------------------------

Expand Down
25 changes: 25 additions & 0 deletions examples/discovery_example2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Example that shows how to list available chromecasts.
"""
import argparse
import logging
import time

import pychromecast
import zeroconf

parser = argparse.ArgumentParser(description="Example on how to receive updates on discovered chromecasts.")
parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
args = parser.parse_args()

if args.show_debug:
logging.basicConfig(level=logging.DEBUG)

devices, browser = pychromecast.discovery.discover_chromecasts()
# Shut down discovery
pychromecast.stop_discovery(browser)

print(f"Discovered {len(devices)} device(s):")
for device in devices:
print(f" {device}")

45 changes: 45 additions & 0 deletions examples/discovery_example3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Example that shows how to list chromecasts matching on name or uuid.
"""
import argparse
import logging
import sys
import time
from uuid import UUID

import pychromecast
import zeroconf

parser = argparse.ArgumentParser(description="Example on how to receive updates on discovered chromecasts.")
parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
parser.add_argument(
"--cast", help='Name of wanted cast device")', default=None
)
parser.add_argument(
"--uuid", help='UUID of wanted cast device', default=None
)
args = parser.parse_args()

if args.show_debug:
logging.basicConfig(level=logging.DEBUG)
if args.cast is None and args.uuid is None :
print("Need to supply `cast` or `uuid`")
sys.exit(1)

friendly_names = []
if args.cast:
friendly_names.append(args.cast)

uuids = []
if args.uuid:
uuids.append(UUID(args.uuid))

devices, browser = pychromecast.discovery.discover_listed_chromecasts(friendly_names=friendly_names, uuids=uuids)
#devices, browser = pychromecast.get_listed_chromecasts(friendly_names=friendly_names, uuids=uuids)
# Shut down discovery
pychromecast.stop_discovery(browser)

print(f"Discovered {len(devices)} device(s):")
for device in devices:
print(f" {device}")

48 changes: 33 additions & 15 deletions pychromecast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
from .config import * # noqa
from .error import * # noqa
from . import socket_client
from .discovery import (
from .discovery import ( # noqa
DISCOVER_TIMEOUT,
CastListener,
discover_chromecasts,
start_discovery,
stop_discovery,
)
from .dial import get_device_status, reboot, DeviceStatus
from .const import CAST_MANUFACTURERS, CAST_TYPES, CAST_TYPE_CHROMECAST
Expand Down Expand Up @@ -70,7 +71,7 @@ def get_chromecast_from_service(
# Build device status from the mDNS service name info, this
# information is the primary source and the remaining will be
# fetched later on.
services, uuid, model_name, friendly_name = services
services, uuid, model_name, friendly_name, _, _ = services
_LOGGER.debug("_get_chromecast_from_service %s", services)
cast_type = CAST_TYPES.get(model_name.lower(), CAST_TYPE_CHROMECAST)
manufacturer = CAST_MANUFACTURERS.get(model_name.lower(), "Google Inc.")
Expand Down Expand Up @@ -110,8 +111,15 @@ def get_listed_chromecasts(
Searches the network for chromecast devices matching a list of friendly
names or a list of UUIDs.
Returns a list of discovered chromecast devices matching the criteria,
or an empty list if no matching chromecasts were found.
Returns a tuple of:
A list of Chromecast objects matching the criteria,
or an empty list if no matching chromecasts were found.
A service browser to keep the Chromecast mDNS data updated. When updates
are (no longer) needed, pass the broswer object to
pychromecast.discovery.stop_discover().
To only discover chromcast devices wihtout connecting to them, use
discover_listed_chromecasts instead.
:param friendly_names: A list of wanted friendly names
:param uuids: A list of wanted uuids
Expand All @@ -122,7 +130,7 @@ def get_listed_chromecasts(
devices matching the criteria have been found.
"""

cc_list = set()
cc_list = {}

def callback(uuid, name): # pylint: disable=unused-argument
_LOGGER.debug("Found chromecast %s", uuid)
Expand All @@ -140,10 +148,12 @@ def get_chromecast_from_uuid(uuid):
friendly_name = service[3]
try:
if uuids and uuid in uuids:
cc_list.add(get_chromecast_from_uuid(uuid))
if uuid not in cc_list:
cc_list[uuid] = get_chromecast_from_uuid(uuid)
uuids.remove(uuid)
elif friendly_names and friendly_name in friendly_names:
cc_list.add(get_chromecast_from_uuid(uuid))
if friendly_names and friendly_name in friendly_names:
if uuid not in cc_list:
cc_list[uuid] = get_chromecast_from_uuid(uuid)
friendly_names.remove(friendly_name)
if not friendly_names and not uuids:
discover_complete.set()
Expand All @@ -158,18 +168,26 @@ def get_chromecast_from_uuid(uuid):

# Wait for the timeout or found all wanted devices
discover_complete.wait(discovery_timeout)
return (list(cc_list), browser)
return (cc_list.values(), browser)


# pylint: disable=too-many-locals
def get_chromecasts(
tries=None, retry_wait=None, timeout=None, blocking=True, callback=None
):
"""
Searches the network for chromecast devices and creates a Chromecast instance
Searches the network for chromecast devices and creates a Chromecast object
for each discovered device.
May return an empty list if no chromecasts were found.
Returns a tuple of:
A list of Chromecast objects, or an empty list if no matching chromecasts were
found.
A service browser to keep the Chromecast mDNS data updated. When updates
are (no longer) needed, pass the broswer object to
pychromecast.discovery.stop_discover().
To only discover chromcast devices wihtout connecting to them, use
discover_chromecasts instead.
Parameters tries, timeout, retry_wait and blocking_app_launch controls the
behavior of the created Chromecast instances.
Expand All @@ -189,13 +207,13 @@ def get_chromecasts(
"""
if blocking:
# Thread blocking chromecast discovery
devices, browser = discover_chromecasts()
services, browser = discover_chromecasts()
cc_list = []
for device in devices:
for service in services:
try:
cc_list.append(
get_chromecast_from_service(
device,
service,
browser.zc,
tries=tries,
retry_wait=retry_wait,
Expand Down Expand Up @@ -265,7 +283,7 @@ def __init__(self, host, port=None, device=None, **kwargs):
timeout = kwargs.pop("timeout", None)
retry_wait = kwargs.pop("retry_wait", None)
services = kwargs.pop("services", None)
zconf = kwargs.pop("zconf", True)
zconf = kwargs.pop("zconf", None)

self.logger = logging.getLogger(__name__)

Expand Down
61 changes: 60 additions & 1 deletion pychromecast/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ def get_value(key):
return value
return value.decode("utf-8")

addresses = service.parsed_addresses()
host = addresses[0] if addresses else service.server

model_name = get_value("md")
uuid = get_value("id")
friendly_name = get_value("fn")
Expand All @@ -108,6 +111,8 @@ def get_value(key):
services_for_uuid[1],
model_name,
friendly_name,
host,
service.port,
)

if callback:
Expand Down Expand Up @@ -148,7 +153,16 @@ def stop_discovery(browser):


def discover_chromecasts(max_devices=None, timeout=DISCOVER_TIMEOUT):
""" Discover chromecasts on the network. """
"""
Discover chromecasts on the network.
Returns a tuple of:
A list of chromecast services, or an empty list if no matching chromecasts were
found.
A service browser to keep the Chromecast mDNS data updated. When updates
are (no longer) needed, pass the broswer object to
pychromecast.discovery.stop_discover().
"""
# pylint: disable=unused-argument
def callback(uuid, name):
"""Called when zeroconf has discovered a new chromecast."""
Expand All @@ -166,6 +180,51 @@ def callback(uuid, name):
return (listener.devices, browser)


def discover_listed_chromecasts(
friendly_names=None, uuids=None, discovery_timeout=DISCOVER_TIMEOUT,
):
"""
Searches the network for chromecast devices matching a list of friendly
names or a list of UUIDs.
Returns a tuple of:
A list of chromecast services matching the criteria,
or an empty list if no matching chromecasts were found.
A service browser to keep the Chromecast mDNS data updated. When updates
are (no longer) needed, pass the broswer object to
pychromecast.discovery.stop_discover().
:param friendly_names: A list of wanted friendly names
:param uuids: A list of wanted uuids
:param discovery_timeout: A floating point number specifying the time to wait
devices matching the criteria have been found.
"""

cc_list = {}

def callback(uuid, name): # pylint: disable=unused-argument
service = listener.services[uuid]
friendly_name = service[3]
if uuids and uuid in uuids:
cc_list[uuid] = listener.services[uuid]
uuids.remove(uuid)
if friendly_names and friendly_name in friendly_names:
cc_list[uuid] = listener.services[uuid]
friendly_names.remove(friendly_name)
if not friendly_names and not uuids:
discover_complete.set()

discover_complete = Event()

listener = CastListener(callback)
zconf = zeroconf.Zeroconf()
browser = start_discovery(listener, zconf)

# Wait for the timeout or found all wanted devices
discover_complete.wait(discovery_timeout)
return (cc_list.values(), browser)


def get_info_from_service(service, zconf):
""" Resolve service_info from service. """
service_info = None
Expand Down

0 comments on commit d76c8f1

Please sign in to comment.