mirror of
https://github.com/CCOSTAN/Home-AssistantConfig.git
synced 2025-08-18 11:16:37 +00:00
112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
"""
|
|
Implements the DIAL-protocol to communicate with the Chromecast
|
|
"""
|
|
import xml.etree.ElementTree as ET
|
|
from collections import namedtuple
|
|
from uuid import UUID
|
|
|
|
import requests
|
|
import six
|
|
|
|
XML_NS_UPNP_DEVICE = "{urn:schemas-upnp-org:device-1-0}"
|
|
|
|
FORMAT_BASE_URL = "http://{}:8008"
|
|
|
|
CC_SESSION = requests.Session()
|
|
CC_SESSION.headers['content-type'] = 'application/json'
|
|
|
|
# Regular chromecast, supports video/audio
|
|
CAST_TYPE_CHROMECAST = 'cast'
|
|
# Cast Audio device, supports only audio
|
|
CAST_TYPE_AUDIO = 'audio'
|
|
# Cast Audio group device, supports only audio
|
|
CAST_TYPE_GROUP = 'group'
|
|
|
|
CAST_TYPES = {
|
|
u'chromecast': CAST_TYPE_CHROMECAST,
|
|
u'chromecast audio': CAST_TYPE_AUDIO,
|
|
u'google cast group': CAST_TYPE_GROUP,
|
|
}
|
|
|
|
|
|
def reboot(host):
|
|
""" Reboots the chromecast. """
|
|
CC_SESSION.post(FORMAT_BASE_URL.format(host) + "/setup/reboot",
|
|
data='{"params":"now"}', timeout=10)
|
|
|
|
|
|
def get_device_status(host):
|
|
"""
|
|
:param host: Hostname or ip to fetch status from
|
|
:type host: str
|
|
:return: The device status as a named tuple.
|
|
:rtype: pychromecast.dial.DeviceStatus or None
|
|
"""
|
|
|
|
try:
|
|
req = CC_SESSION.get(
|
|
FORMAT_BASE_URL.format(host) + "/ssdp/device-desc.xml",
|
|
timeout=10)
|
|
|
|
# The Requests library will fall back to guessing the encoding in case
|
|
# no encoding is specified in the response headers - which is the case
|
|
# for the Chromecast.
|
|
# The standard mandates utf-8 encoding, let's fall back to that instead
|
|
# if no encoding is provided, since the autodetection does not always
|
|
# provide correct results.
|
|
if req.encoding is None:
|
|
req.encoding = 'utf-8'
|
|
|
|
status_el = ET.fromstring(req.text.encode("UTF-8"))
|
|
|
|
device_info_el = status_el.find(XML_NS_UPNP_DEVICE + "device")
|
|
api_version_el = status_el.find(XML_NS_UPNP_DEVICE + "specVersion")
|
|
|
|
friendly_name = _read_xml_element(device_info_el, XML_NS_UPNP_DEVICE,
|
|
"friendlyName", "Unknown Chromecast")
|
|
model_name = _read_xml_element(device_info_el, XML_NS_UPNP_DEVICE,
|
|
"modelName", "Unknown model name")
|
|
manufacturer = _read_xml_element(device_info_el, XML_NS_UPNP_DEVICE,
|
|
"manufacturer",
|
|
"Unknown manufacturer")
|
|
udn = _read_xml_element(device_info_el, XML_NS_UPNP_DEVICE,
|
|
"UDN",
|
|
None)
|
|
|
|
api_version = (int(_read_xml_element(api_version_el,
|
|
XML_NS_UPNP_DEVICE, "major", -1)),
|
|
int(_read_xml_element(api_version_el,
|
|
XML_NS_UPNP_DEVICE, "minor", -1)))
|
|
|
|
cast_type = CAST_TYPES.get(model_name.lower(),
|
|
CAST_TYPE_CHROMECAST)
|
|
|
|
uuid = None
|
|
if udn and udn.startswith('uuid:'):
|
|
uuid = UUID(udn[len('uuid:'):].replace("-", ""))
|
|
|
|
return DeviceStatus(friendly_name, model_name, manufacturer,
|
|
api_version, uuid, cast_type)
|
|
|
|
except (requests.exceptions.RequestException, ET.ParseError):
|
|
return None
|
|
|
|
|
|
def _read_xml_element(element, xml_ns, tag_name, default=""):
|
|
""" Helper method to read text from an element. """
|
|
try:
|
|
text = element.find(xml_ns + tag_name).text
|
|
if isinstance(text, six.text_type):
|
|
return text
|
|
else:
|
|
return text.decode('utf-8')
|
|
|
|
except AttributeError:
|
|
return default
|
|
|
|
|
|
DeviceStatus = namedtuple("DeviceStatus",
|
|
["friendly_name", "model_name",
|
|
"manufacturer", "api_version",
|
|
"uuid", "cast_type"])
|