Skip to content

Commit

Permalink
Allow forcing getSupportedApiInfo response from local devinfo file
Browse files Browse the repository at this point in the history
Some devices do not support the ability to obtain information about available APIs.
This PR adds new --devinfo-file option that allows using an existing devinfo file to feed the list of available services,
which may be helpful for such devices as long as they implement getMethodTypes() call for the services.

Potential fix for #29
  • Loading branch information
rytilahti committed Oct 6, 2020
1 parent 03677b2 commit cfad4e9
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 25 deletions.
71 changes: 49 additions & 22 deletions songpal/device.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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()
Expand Down Expand Up @@ -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."""
Expand Down
7 changes: 5 additions & 2 deletions songpal/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion songpal/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit cfad4e9

Please sign in to comment.