diff --git a/songpal/device.py b/songpal/device.py index 24d5d88..ec443ae 100644 --- a/songpal/device.py +++ b/songpal/device.py @@ -1,6 +1,7 @@ """Module presenting a single supported device.""" import asyncio import itertools +import json import logging from collections import defaultdict from pprint import pformat as pf @@ -43,7 +44,7 @@ class Device: WEBSOCKET_PROTOCOL = "v10.webapi.scalar.sony.com" WEBSOCKET_VERSION = 13 - def __init__(self, endpoint, force_protocol=None, debug=0): + def __init__(self, endpoint, force_protocol=None, debug=0, devinfo_file=None): """Initialize Device. :param endpoint: the main API endpoint. @@ -67,6 +68,28 @@ def __init__(self, endpoint, force_protocol=None, debug=0): self.callbacks = defaultdict(set) + self.devinfo = None + if devinfo_file is not None: + _LOGGER.debug("Using device info file: %s", devinfo_file) + self.devinfo = self._load_devinfo_file(devinfo_file) + + def _load_devinfo_file(self, file): + """Internal method to create getSupportedApiInfo like response. + + This reads an existing devinfo file and creates a minimal data structure + that is enough to construct services objects as long as the service endpoints + expose the getMethodTypes() method. + """ + data = json.load(file) + methods = data["supported_methods"] + devinfo = [] + + for service, values in methods.items(): + serv = {"service": service, "protocols": values["protocols"]} + devinfo.append(serv) + + return devinfo + async def __aenter__(self): """Asynchronous context manager, initializes the list of available methods.""" await self.get_supported_methods() @@ -130,31 +153,35 @@ async def get_supported_methods(self): Calling this as the first thing before doing anything else is necessary to fill the available services table. """ - response = await self.request_supported_methods() + if self.devinfo is None: + response = await self.request_supported_methods() - if "result" in response: - services = response["result"][0] - _LOGGER.debug("Got %s services!" % len(services)) + if "result" in response: + services = response["result"][0] + else: + raise SongpalException("Supported methods responded without result") + else: + services = self.devinfo - for x in services: - serv = await Service.from_payload( - x, self.endpoint, self.idgen, self.debug, self.force_protocol - ) - if serv is not None: - self.services[x["service"]] = serv - else: - _LOGGER.warning("Unable to create service %s", x["service"]) + _LOGGER.debug("Got %s services!" % len(services)) + + for x in services: + serv = await Service.from_payload( + x, self.endpoint, self.idgen, self.debug, self.force_protocol + ) + if serv is not None: + self.services[x["service"]] = serv + else: + _LOGGER.warning("Unable to create service %s", x["service"]) - for service in self.services.values(): + for service in self.services.values(): + if self.debug > 1: + _LOGGER.debug("Service %s", service) + for api in service.methods: + # self.logger.debug("%s > %s" % (service, api)) if self.debug > 1: - _LOGGER.debug("Service %s", service) - for api in service.methods: - # self.logger.debug("%s > %s" % (service, api)) - if self.debug > 1: - _LOGGER.debug("> %s" % api) - return self.services - - return None + _LOGGER.debug("> %s" % api) + return self.services async def get_power(self) -> Power: """Get the device state.""" diff --git a/songpal/main.py b/songpal/main.py index fc80d75..fdf799d 100644 --- a/songpal/main.py +++ b/songpal/main.py @@ -110,10 +110,11 @@ def print_settings(settings, depth=0): @click.option("-d", "--debug", default=False, count=True) @click.option("--post", is_flag=True, required=False) @click.option("--websocket", is_flag=True, required=False) +@click.option("--devinfo-file", type=click.File("r"), required=False) @click.pass_context @click.version_option() @coro -async def cli(ctx, endpoint, debug, websocket, post): +async def cli(ctx, endpoint, debug, websocket, post, devinfo_file): """Songpal CLI.""" lvl = logging.INFO if debug: @@ -139,7 +140,9 @@ async def cli(ctx, endpoint, debug, websocket, post): protocol = ProtocolType.XHRPost logging.debug("Using endpoint %s", endpoint) - x = Device(endpoint, force_protocol=protocol, debug=debug) + x = Device( + endpoint, force_protocol=protocol, debug=debug, devinfo_file=devinfo_file + ) try: await x.get_supported_methods() except SongpalException as ex: diff --git a/songpal/service.py b/songpal/service.py index bfe7a5f..2142bf8 100644 --- a/songpal/service.py +++ b/songpal/service.py @@ -73,7 +73,7 @@ async def from_payload(cls, payload, endpoint, idgen, debug, force_protocol=None protocols = payload["protocols"] _LOGGER.debug("Available protocols for %s: %s", service_name, protocols) - if force_protocol and force_protocol.value in protocols: + if force_protocol: protocol = force_protocol elif "websocket:jsonizer" in protocols: protocol = ProtocolType.WebSocket